How do I lazy load a spring bean? - java

So, hypothetically speaking, if I have an object, lets say it's a exception response I want to send back from a service BUT only if an exception is thrown as I have a different animal I'm sending back if everything's fine. How do I get it from Spring... but only if I need it? In other words, how do I avoid injecting objects I don't yet need and can't be sure that I will? How do I do lazy loading using Spring?

There are two possibilities:
#Lazy on the bean definition + ObjectFactory or Provider or on the injection point
#Lazy on the bean definition + #Lazy on the injection point
example #2
#Service
#Lazy
class LazyService {
LazyService() {
System.out.println("service");
}
String bar() {
System.out.println("bar");
return "bar";
}
}
#RestController
class Controller {
private final LazyService service;
Controller(#Lazy LazyService service) {
this.service = service;
System.out.println("controller");
}
#GetMapping("/")
String foo() {
System.out.println("foo");
return service.bar();
}
}
LazyService is instantiated when method Controller.foo() is called for the first time.

Related

Difference between #Autowired final setter and non-final setter in spring

Assuming:
abstract class CommonService {
protected VipMapper vipMapper;
#Autowired
public final void setVipMapper(VipMapper vipMapper) {
this.vipMapper = vipMapper;
}
}
#Service
public class BookService extends CommonService {
public int find() {
return vipMapper.findVip(); // return 100
}
}
#SpringBootTest
class BookServiceTest {
#Autowired
private BookService bookService;
#Test
void find() {
VipMapper v = new VipMapper() {
#Override
public int findVip() { // This method will not execute
return 10;
}
};
bookService.setVipMapper(v);
int find = bookService.find(); // find = 100 (not 10)
}
}
1. What is the reason I cannot inject VipMapper when setVipMapper method is final and I can inject when setVipMapper method is not final?
2. How can I inject VipMapper in runtime but still use #Autowired final setter?
Update
I'm using Spring + Mybatis
Source code:
https://bitbucket.org/nguyentanh/stackoverflow
Using the above code, when run that test for findVipCustomerTop3, I get an error connection. But when to remove final in CommonService.java (or #Transactional in BookService.java), the test is success
You issue is not with autowiring. VipMapper get autowired correctly and you are trying to replace the mapper manually via bookService.setVipMapper(v); in your test. It does not replace the vipMapper you passed. To Check this behaviour, define a getter in your service to get the vipMapper and it will return the original vipMapper which was autowired by spring.
Just remember you are not working with an instance of your original BookService class, you are working with a sub class of BookService which is run time generated .
Your original question missed an important piece of info which is #Transactional annotation in your service. As soon as #Transactional annotation is there, Spring actually need to create a proxy. Now spring will choose JDK dynamic proxy or CGLIB proxy to create the proxy for your book service. Since your Service does not have an interface, JDK dynamic proxy choice is not possible so spring is left with CGLIB proxy.
CGLIB proxy has its limitations.
With CGLIB, final methods cannot be advised, as they cannot be overridden in runtime-generated subclasses
Here is technique if you want to actually replace it. Add the following in your test class instead of the line bookService.setVipMapper(v);
((BookService)AopProxyUtils.getSingletonTarget(bookService))
.setVipMapper(v);
The above option is doing it hardcore way but good for understanding the concept. There is another option telling spring to create BookService in your test with a mock vipMapper autowired when it creates BookService.
#SpringBootTest
class BookServiceTest {
#Autowired
private BookService bookService;
#MockBean
private VipMapper vipMapper;
#Test
void find() {
when(vipMapper.findVip()).thenReturn(10);
bookService.setVipMapper(v);
int find = bookService.find();
}
}
Reference
https://docs.spring.io/spring/docs/5.2.8.RELEASE/spring-framework-reference/core.html#aop-proxying
From my understanding, you are using #autowired for setVipMapper() so it already injected VipMapper with default findVip() returing 100. Therefore, defining setVipMapper() as final won't change the value you pass through anymore

Implicitly qualified autowiring in Spring

I have a project with some independent bean X, that is autowired in a bunch of services. Services are used by each other, and finally used in single entry point (controller). Now there is new requirement: implement several versions of X, and decide witch one is to use according to entry point's parameter (enum XType). It would be nice to do it without changing services.
My idea of solution is to create custom scope UsesX and implement BeanFactoryPostProcessor, that will converts each BeanDefinition with UsesX to set of singletons for each XType. Also, it will adds qualifiers to this beans, to make it possible to make factory method for X and parameter-based selection in controller. But how to add this qualifier to #Autowired in services implicitly, without changing their classes?
UPD
Ok, example, I want to use db url "jdbc:mysql://Adb" when A requested, and "jdbc:mysql://Bdb" when B:
enum DatabaseType {A, B}
#Controller
#RequestMapping(/)
class MyController {
#Autowired ServiceProvider provider; // some way to get service by DatabaseType
void foo(#RequestParam DatabaseType dbType) {
ServiceA a = provider.getA(dbType);
a.bar();
ServiceB b = provider.getB(dbType);
b.baz();
}
}
#Service
class ServiceA {
// Don't want to get information about different databases in services
#Autowired ServiceB b;
#Autowired ServiceC c;
#Autowired DaoFoo dao;
//...
}
#Service
class ServiceB {
#Autowired ServiceC c;
#Autowired DaoFoo daoFoo;
#Autowired DaoBar daoBar;
//...
}
#Service
class ServiceC {
#Autowired DaoBar daoBar;
//...
}
#Repository
class DaoFoo {
DaoFoo(String dbURL) {/*...*/}
}
#Repository
class DaoBar {
DaoFoo(String dbURL) {/*...*/}
}
Also, it is required to "jdbc:mysql://Adb" and "jdbc:mysql://Bdb" be configured in XML configuration.
I want to wrap up your requirements so that it would be clear if I'm getting you right.
You have a set of #Services that you don't want to modify.
At this moment you have only one implementation of X type which is used by this services.
The choice of X implementation to be used in services would be defined by XType enum, which in turn would be available from request.
You want X type beans be configurable from xml.
OP: What X implementation should be used in case if one of this services would be called w/o XType?
So if my understanding is correct, it seems like you need Proxy for X type.
Within this Proxy you need to get this XType implicitly (f.ex. through ThreadLocal var).
As #Autowired is used, beans are identified by type in first place. Therefore, you need to use already existing X implementation for proxing and extract your current implementation and new one to different type.
As a result you might end up with following:
interface newX {
void save();
}
#Repository
class DaoFoo implements newX {
public void save() {...};
}
#Repository
class DaoBar implements newX {
public void save() {...};
}
class XImpl implements X, newX {
public final ThreadLocal<XType> currentXType = new ThreadLo...;
Map<XType, newX> mapping = ....
public void save() {mapping.get(currentXType.get()).save();};
}
Your example is a little confusing because your Services are named A and B, but you also use A and B for your DatabaseType. But I think I understand what you want.
I don't think you can do this with Autowired, but you can make your Services as #Scope("prototype") and retrieve them from context. The context should instantiate the Service the first time you request it, then reuse the same bean when the same input is provided.
#Configuration
public class ServiceProvider{
...
#Bean
#Scope("prototype")
public ServiceA serviceA(DatabaseType dbType) {
...
}
#Bean
#Scope("prototype")
public ServiceB serviceB(DatabaseType dbType) {
...
}
}
#Controller
#RequestMapping(/)
class MyController {
#Autowired
ConfigurableApplicationContext context
void foo(#RequestParam DatabaseType dbType) {
AutowireCapableBeanFactory beanFactory = context.getBeanFactory();
ServiceA serviceA = (ServiceA)context.getBean("serviceA", dbType);
...
}
}
Create a service interface like :
interface ServiceInterface{
public boolean isTheOne(String type); // or some suitable name.
}
all your services then need to implement this interface, then in controller
#Controller
#RequestMapping(/)
class MyController {
#Autowired
Set<ServiceInterface> provider;
void foo(#RequestParam DatabaseType dbType) {
ServiceInterface service = provider.stream().filter(s -> s.isTheOne(String dbType));
service.bar();
}
}
You can maintain your XType as an enumerator within a customised Qualifier by developing #Interface.
Please find below a sample which mentions conditional wiring of beans based upon different types of datatype:
#Target({ElementType.FIELD,
ElementType.METHOD,
ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public static #interface DBHost{
public static enum DatabaseType {
A,
B
}
}
#Autowired
#DBHost(DBHost.DatabaseType.A)
ServiceBean serviceInstanceA;
Find additional usage of Qualifier annotation here

Run a void setup method in Spring Context #Configuration

I wish to perform a couple of setup methods within my Spring Context.
I currently have the following code but it doesn't work as I am saying they are beans and have no return type.
#Configuration
#Component
public class MyServerContext {
...
// Works
#Bean
public UserData userData() {
UserData userData = new AWSUserDataFetcher(urlUtil()).fetchUserData();
return userData;
}
// Doesn't work
#Bean
public void setupKeyTrustStores() {
// Setup TrustStore & KeyStore
System.setProperty(SYS_TRUST_STORE, userData().get(TRUST_STORE_PATH));
System.setProperty(SYS_TRUST_STORE_PASSWORD, userData().get(TRUST_STORE_PASSWORD));
System.setProperty(SYS_KEY_STORE, userData().get(KEY_STORE_PATH));
System.setProperty(SYS_KEY_STORE_PASSWORD, userData().get(KEY_STORE_PASSWORD));
// Prevents handshake alert: unrecognized_name
System.setProperty(ENABLE_SNI_EXTENSION, "false");
}
...
}
How can I run this method automatically by the #Configuration context without the #Bean annotation?
You can use the #PostConstruct annotation instead of #Bean:
#Configuration
#Component
public class MyServerContext {
#Autowired
private UserData userData; // autowire the result of userData() bean method
#Bean
public UserData userData() {
UserData userData = new AWSUserDataFetcher(urlUtil()).fetchUserData();
return userData;
}
#PostConstruct
public void setupKeyTrustStores() {
// Setup TrustStore & KeyStore
System.setProperty(SYS_TRUST_STORE, userData.get(TRUST_STORE_PATH));
System.setProperty(SYS_TRUST_STORE_PASSWORD, userData.get(TRUST_STORE_PASSWORD));
System.setProperty(SYS_KEY_STORE, userData.get(KEY_STORE_PATH));
System.setProperty(SYS_KEY_STORE_PASSWORD, userData.get(KEY_STORE_PASSWORD));
// Prevents handshake alert: unrecognized_name
System.setProperty(ENABLE_SNI_EXTENSION, "false");
}
...
}
Use #PostConstruct instead of #bean
#PostConstruct
Due to the Weld Reference injection and initialization happens in this order;
First, the container calls the bean constructor (the default
constructor or the one annotated #Inject), to obtain an instance of
the bean.
Next, the container initializes the values of all injected fields of
the bean.
Next, the container calls all initializer methods of bean (the call
order is not portable, don’t rely on it).
Finally, the #PostConstruct method, if any, is called.
So, the purpose of using #PostConstruct is clear; it gives you a chance to initialize injected beans, resources etc.
public class Person {
// you may have injected beans, resources etc.
public Person() {
System.out.println("Constructor is called...");
}
#PostConstruct
public void init() {
System.out.println("#PostConstruct is called...");
} }
So, the output by injecting a Person bean will be;
Constructor is called...
#PostConstruct is called...
One important point about #PostConstruct is, it won't be invoked if you try to inject and initialize bean through producer methods. Because using a producer method means that you are programmatically creating, initializing, and injecting your bean with new keyword.
Resource Link:
CDI Dependency Injection #PostConstruct and #PreDestroy Example
Why use #PostConstruct?

Spring #Controller and RequestMapping calling different services depending on a given parameter

Let's assume I have this code:
#Controller
#RequestMapping("/something")
public class SomeController {
#Autowired
private SomeService aService;
#RequestMapping("/doStuff")
public void doStuff(#RequestParam("type") String type) {
aService.doStuff();
}
}
In my application I need to call a specific service depending on the specified type. All services implements the same interface. If I understand correctly SomeService cannot be an interface. I could use a service factory and instantiate the service depending on the type every time a new request is done, but this doesn't look very efficient.
Alternatively I could use a different controller for each different type of service (and encode the type in the REST URI), but this would imply a lot of code duplication since all the services basically implements the same interface.
My question is, assuming the called service depends on the passed parameter, what is the best pattern to adopt for this scenario?
Similar to RC.'s answer, instead of using a Map and adding the values by you, just let the Spring BeanFactory handle this for you:
#Controller
#RequestMapping("/something")
public class SomeController {
#Autowired
private BeanFactory beanFactory;
#RequestMapping("/doStuff")
public void login(#RequestParam("type") String type) {
SomeService aService = (SomeService)beanFactory.getBean(type);
aService.doStuff();
}
}
You could use a map here, something along this:
#Controller
#RequestMapping("/something")
public class SomeController {
#Autowired
private SomeService someService;
#Autowired
private SomeOtherService someOtherService;
// ...
private final Map<String, ServiceCommonInterface> serviceMap = new HashMap<>();
#PostConstruct
private void postConstruct() {
serviceMap.put(typeForSomeService, someService);
serviceMap.put(typeForSomeOtherService, someOtherService);
}
#RequestMapping("/doStuff")
public void login(#RequestParam("type") String type) {
// TODO: ensure type is correct (an enum might be handy here)
serviceMap.get(type).doStuff();
}
}
Or better, as stated in comments you can leverage qualifiers:
#Controller
#RequestMapping("/something")
public class SomeController {
#Autowired
private ApplicationContext applicationContext;
#RequestMapping("/doStuff")
public void login(#RequestParam("type") String type) {
// TODO: ensure type is a correct bean name
applicationContext.getBean(type, ServiceCommonInterface.class).doStuff();
}
}
Depending on the number of types you wish to support there are two options I see.
1) Autowire in a factory as you mentioned and lazily create each service as needed. If the services are stateless you could keep a reference to the object after created so would only need to create once per type.
2) Autowire in a Spring Map with the key being your types and the value being the correct service to use. Then when your receive the type, can retrieve the correct service impl for your map.
For example for map: http://www.mkyong.com/spring/spring-collections-list-set-map-and-properties-example/ or see How to inject a Map<String, List> in java springs?
Both of these require you to create an interface for your service which is something you said is possible.

How to dynamically chose a service to execute based on String with DI?

I have a String as input, and based on this I'd like to chose a service to execute.
For the following approach I'd have a delegator class that gets all possible services auto injected using Spring 4. But could improve this? Or is this a good approach to delegate to a speficic service?
Especially I don't know if injecting all my services that may be chosen based on the action in this class.
class Delegator {
public MyService findService(String action) {
switch (action) {
case "A": return serviceA; break;
case "B": return serviceB; break;
//lots of other cases
}
return null;
}
#Autowired
private MyService serviceA;
#Autowired
private MyService serviceB;
}
I guess you could have several solutions the easiest would be to create callback method on the MyService interface, you could then iterate over all MyService implementations and figure out which one to use (i.e. return true from the specified method when it is supported).
class Delegator {
#Autowired
private List<MyService> services;
public MyService findService(String action) {
for (MyService service : services) {
if (service.canHandle(action) ) {
return service
}
}
throw new IllegalArgumentException("Could not find service to handle: "+action);
}
}
This allows you to add MyService implementations without having to modify your Delegator class.
A related/similair solution could be to use a #Qualifier annotation instead of adding a callback method on your MyService implementation. Your Delegator would have to be aware of springs ApplicationContext so that you could lookup the bean needed. The #Qualifier would of course be matched/related to the passed in the action method argument.
class Delegator {
#Autowired
private BeanFactory bf;
public MyService findService(String action) {
return BeanFactoryAnnotationUtils.qualifiedBeanOfType(bf, MyService.class, action);
}
}
#Service
#Qualifier("foo")
public MyService1 implements MyService { ... }
#Service
#Qualifier("bar")
public MyService2 implements MyService { ... }
For action matching foo the bean with #Qualifier foo would be returned. If no matching implementation could be found you will get a NoSuchBeanDefinitionException.
Spring's IoC container is meant to be domain independent. You seem to have very specific logic for determining a specific MyService instance. Your service locator is the appropriate pattern to use here.
Alternatively, if the logic is more complex, you can have your MyService interface declare a supports method. Your classes would implement this method to return true if they can support the action, or false otherwise. You would loop through all the services and return the first one to return true.
For example
#Autowired
private List<MyService> services;
public MyService findService(String action) {
for(MyService service : services) {
if(service.supports(action)) {
return service;
}
}
return null; // or whatever is appropriate
}

Categories