Problem
There is a Processor class which processes something based on its typesToProcess:
import org.springframework.stereotype.Component;
#Component
public class Processor {
private String typesToProcess;
public Processor(String typesToProcess) {
this.typesToProcess = typesToProcess;
}
public void process(String type) {
if (typesToProcess.equals(type)) {
// process
}
}
}
I need to create some Processor instances to work at different places. The problem is I don't know how many Processor instances should be created when the Spring Application is bootstrapping, as well as their 'typesToProcess'. Further more, the number of Processor instances might change when the application is running. I need to read the types from some configuration file which is stored in database periodically.
Attempts
One way to solve this problem I can thing out is using typesToProcess + Processor as the bean name, though I know it wont work. Is there any other way?
import com.meituan.picture.selection.processor.impl.Processor;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class ProcessorContainer implements ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public Processor getProcessor(String type) {
String realBeanName = "processor" + type;
// obviouslly this wont work
return applicationContext.getBean(realBeanName, Processor.class);
}
}
I solved similar kind of problem by specifying scope of my bean as prototype without using bean name. Below example explains my implementation:
Create your Processor class as shown below:
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import lombok.Getter;
import lombok.Setter;
#Component
#Getter
#Setter
#Scope("prototype")
public class Processor {
private String typesToProcess;
public Processor(String typesToProcess) {
this.typesToProcess = typesToProcess;
}
public void process(String type) {
if (typesToProcess.equals(type)) {
// process
}
}
}
Implement ApplicationContextAware to generate beans at runtime:
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
#Service
public class ApplicationContextAwareImpl implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
ApplicationContextAwareImpl.initApplicationContext(applicationContext);
}
private static void initApplicationContext(ApplicationContext applicationContext) {
ApplicationContextAwareImpl.context = applicationContext;
}
public static <T> T getBean(Class<T> requiredType) {
return context.getBean(requiredType);
}
public static Processor getProcessor(String typesToProcess) {
Processor processor = ApplicationContextAwareImpl.getBean(Processor.class);
processor.setTypesToProcess(typesToProcess);
return processor;
}
}
Use ApplicationContextAwareImpl.getProcessor() method to generate beans programmatically:
Processor type1Processor = ApplicationContextAwareImpl.getProcessor("type1");
Related
Consider a simple Lambda written in Java:
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class Hello implements RequestHandler<Integer, String>{
public String handleRequest(int myCount, Context context) {
return String.valueOf(myCount);
}
}
The handler interface is defined as RequestHandler<InputType, OutputType>, but when my Lambda reacts to events and just does some side effects, is the output type unnecessary and I have to write something like this:
public class Hello implements RequestHandler<SNSEvent, Void>{
public Void handleRequest(SNSEvent snsEvent, Context context) {
...
return null;
}
}
Which is annoying.
Is there an alternative to RequestHandler for a void handler?:
public class Hello implements EventHandler<SNSEvent>{
public void handleEvent(SNSEvent snsEvent, Context context) {
...
}
}
You don't need to implement an interface for your Lambda entry point. Your handler class can just be a POJO with a signature that fulfils the requirements explained in the documentation.
For example:
package example;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.SNSEvent;
public class Hello {
public void handleEvent(SNSEvent event, Context context) {
// Process the event
}
}
In this case you should use example.Hello::handleEvent as the handler configuration.
See also this example from the official docs:
package example;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
public class Hello {
public String myHandler(int myCount, Context context) {
LambdaLogger logger = context.getLogger();
logger.log("received : " + myCount);
return String.valueOf(myCount);
}
}
I have a Qualifier where I read from
public class TestController{
#Autowired
#Qualifier("jdbc")
private JdbcTemplate jtm;
//.....
}
The qualifier "jdbc" is the bean defined as
#Bean(name = "jdbc")
#Autowired
public JdbcTemplate masterJdbcTemplate(#Qualifier("prod") DataSource prod) {
return new JdbcTemplate(prod);
}
This is the which returns the datasource for that qualifier and works fine.
Now I want to make the Qualifier name to be read from the application.properties. So I changed my code to
public class TestController{
#Autowired
#Qualifier("${database.connector.name}")
private JdbcTemplate jtm;
//.....
}
where database.connector.name=jdbc in my application.properties.
But when i do this this throws an error of
APPLICATION FAILED TO START
Description:
Field userService in main.java.rest.TestController required a bean of
type 'org.springframework.jdbc.core.JdbcTemplate' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.jdbc.core.JdbcTemplate' in
your configuration.
Any help is appreciated.
Qualifier doesn't resolve placeholder. You can write your TestController class as
public class TestController {
#Value("${database.connector.name}")
private String name;
private JdbcTemplate jtm;
#Autowired
public void setJdbcTemplate(ApplicationContext context) {
jtm = (JdbcTemplate) context.getBean(name);
}
}
As #Hemant already mentioned default QualifierCandidateResolver does not resolve properties.
But you can make one, which does:
import java.lang.annotation.Annotation;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.stereotype.Component;
#Component
public static class AutowireCandidateResolverConfigurer implements BeanFactoryPostProcessor {
private static class EnvironmentAwareQualifierAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver {
private static class ResolvedQualifier implements Qualifier {
private final String value;
ResolvedQualifier(String value) { this.value = value; }
#Override
public String value() { return this.value; }
#Override
public Class<? extends Annotation> annotationType() { return Qualifier.class; }
}
#Override
protected boolean checkQualifier(BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) {
if (annotation instanceof Qualifier) {
Qualifier qualifier = (Qualifier) annotation;
if (qualifier.value().startsWith("${") && qualifier.value().endsWith("}")) {
DefaultListableBeanFactory bf = (DefaultListableBeanFactory) this.getBeanFactory();
ResolvedQualifier resolvedQualifier = new ResolvedQualifier(bf.resolveEmbeddedValue(qualifier.value()));
return super.checkQualifier(bdHolder, resolvedQualifier, typeConverter);
}
}
return super.checkQualifier(bdHolder, annotation, typeConverter);
}
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
DefaultListableBeanFactory bf = (DefaultListableBeanFactory) beanFactory;
bf.setAutowireCandidateResolver(new EnvironmentAwareQualifierAnnotationAutowireCandidateResolver());
}
}
With that you will be able to use #Qualifier in a way you've asked #Qualifier("${database.connector.name}")
Full example:
#SpringBootApplication
public class SO50208018Application {
public static void main(String[] args) { SpringApplication.run(SO50208018Application.class, args); }
interface MyBean { }
static class MyBeanImpl1 implements MyBean { }
static class MyBeanImpl2 implements MyBean { }
#Bean #Qualifier("impl1")
MyBean bean1() { return new MyBeanImpl1(); }
#Bean #Qualifier("impl2")
MyBean bean2() { return new MyBeanImpl2(); }
#Component
public static class AutowireCandidateResolverConfigurer implements BeanFactoryPostProcessor {
// configurer from above
}
#Bean
CommandLineRunner run(#Qualifier("${spring.application.bean}") MyBean bean) {
return (args) -> System.out.println(bean.getClass().getName());
}
}
Run with spring.application.bean=impl1:
com.stackoverflow.java.SO50208018Application$MyBeanImpl1
Run with spring.application.bean=impl2:
com.stackoverflow.java.SO50208018Application$MyBeanImpl2
If you keep the #Qualifier("jdbc"),
you can vary the bean that is injected by providing different test configuration files and loading the desired one for each test class.
I am using next dozer custom converter
public class MyCustomDozerConverter extends DozerConverter<MyObject, String> {
#Autowired
private AppConfig appConfig;
public MyCustomDozerConverter() {
super(MyObject.class, String.class);
}
#Override
public String convertTo(MyObject source, String destination) {
String myProperty = appConfig.getWhatever();
// business logic
return destination;
}
#Override
public MyObject convertFrom(String source, MyObject destination) {
// business logic
return null;
}
}
My problem is when it goes through convertTo method inside the converter, I always got appConfig instance with null value which of course cause a null pointer exception
Note: my spring boot class have these annotations above:
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan({"com.xxx"})
#EntityScan("com.xxx")
#EnableJpaRepositories("com.xxx")
I solved this by next trick:
1- Using static with appConfig property.
2- instantiate it by spring so when dozer use default empty constructor it will find appConfig have
a value already (which assigned before to it by spring)
And here are the code i used for this:
#Component //import org.springframework.stereotype.Component;
public class MyCustomDozerConverter extends DozerConverter<MyObject, String> {
private static AppConfig appConfig;
// dozer needs this constructor to create an instance of converter (so it's a mandatory constructor)
public MyCustomDozerConverter() {
super(MyObject.class, String.class);
}
#Autowired // Spring will pass appConfig to constructor
public MyCustomDozerConverter(AppConfig appConfig) {
this();
this.appConfig = appConfig;
}
#Override
public String convertTo(MyObject source, String destination) {
String myProperty = appConfig.getWhatever();
// business logic
return destination;
}
#Override
public MyObject convertFrom(String source, MyObject destination) {
// business logic
return null;
}
}
UPDATE: Another solution
Another trick is using Spring ApplicationContextAware to get a singleton object from getBean method:
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
}
Then create a static method inside AppConfig class and return an instance of the single bean matching the required type:
import org.springframework.context.annotation.Configuration;
import com.tripbru.ms.experiences.ApplicationContextHolder;
#Configuration
public class AppConfig {
// Static method used to return an instatnce
public static AppConfig getInstance() {
return ApplicationContextHolder.getContext().getBean(AppConfig.class);
}
// Properties
}
Then calling it direct inside the dozer converter by AppConfig.getInstance();
public class MyCustomDozerConverter extends DozerConverter<MyObject, String> {
private static AppConfig appConfig;
public MyCustomDozerConverter() {
super(MyObject.class, String.class);
appConfig = AppConfig.getInstance(); // Here are we intializing it by calling the static method we created.
}
#Override
public String convertTo(MyObject source, String destination) {
String myProperty = appConfig.getWhatever();
// business logic
return destination;
}
#Override
public MyObject convertFrom(String source, MyObject destination) {
// business logic
return null;
}
}
Try constructor dependency injection
private AppConfig appConfig;
#Autowired
MyCustomerDozerConverter(AppConfig appConfig)
{
this.appConfig = appConfig;
}
You can put following line in the CustomConverter so that Spring will autowire it.
public class MyCustomDozerConverter extends DozerConverter<MyObject, String> {
#Autowired
private AppConfig appConfig;
public MyCustomDozerConverter() {
super(MyObject.class, String.class);
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
...
}
I've been able to inject into my jersey resource from a filter as per How to inject an object into jersey request context?. This allows me to successfully inject into a method parameter:
#GET
public Response getTest(#Context MyObject myObject) { // this works
However, for setter/field/constructor injection, the HK2 Factory is invoked before the jersey filter, which means the provide() method returns null:
#Override
public MyObject provide() {
// returns null because the filter has not yet run,
// and the property has not yet been set
return (MyObject)context.getProperty("myObject");
}
Is there a way to define when the HK2 Factory will run so that it is invoke after the filter runs? If not, then the workaround is to define MyObject as an interface and define an additional implementation that takes a ContainerRequestContext in its constructor; any attempt to actually use the instance would then lazily delegate to the implementation that gets set on the ContainerRequestContext's property (presumably you wouldn't actually use the instance until after the filter runs -- at which point the property would be set).
But I would like to understand if it is possible to delay the point at which the HK2 Factory runs so that it runs after the filter (it already runs after the filter in the case of method parameter injection). If it is not possible, then I would like to understand if there is a fundamental reason why.
Oddly it only works for me with #PreMatching on the filter (which limits access to some things you may or may not need). Not quite sure what's going on under the hood, that cause it not to work without it :-(. Below is a complete test using Jersey Test Framework.
import java.io.IOException;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Assert;
import org.junit.Test;
public class FilterInjectionTest extends JerseyTest {
private static final String MESSAGE = "Inject OK";
private static final String OBJ_PROP = "myObject";
public static class MyObject {
private final String value;
public MyObject(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
#PreMatching
#Provider
public static class MyObjectFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext context) throws IOException {
MyObject obj = new MyObject(MESSAGE);
context.setProperty(OBJ_PROP, obj);
}
}
public static class MyObjectFactory
extends AbstractContainerRequestValueFactory<MyObject> {
#Override
#RequestScoped
public MyObject provide() {
return (MyObject) getContainerRequest().getProperty(OBJ_PROP);
}
#Override
public void dispose(MyObject t) {
}
}
#Path("method-param")
public static class MethodParamResource {
#GET
public String getResponse(#Context MyObject myObject) {
return myObject.getValue();
}
}
#Path("constructor")
public static class ConstructorResource {
private final MyObject myObject;
#Inject
public ConstructorResource(#Context MyObject myObject) {
this.myObject = myObject;
}
#GET
public String getResponse() {
return myObject.getValue();
}
}
#Path("field")
public static class FieldResource {
#Inject
private MyObject myObject;
#GET
public String getResponse() {
return myObject.getValue();
}
}
#Override
public Application configure() {
ResourceConfig config = new ResourceConfig();
config.register(MethodParamResource.class);
config.register(MyObjectFilter.class);
config.register(ConstructorResource.class);
config.register(FieldResource.class);
config.register(new AbstractBinder() {
#Override
protected void configure() {
bindFactory(MyObjectFactory.class)
.to(MyObject.class).in(Singleton.class);
}
});
return config;
}
#Test
public void methoParamInjectionOk() {
String response = target("method-param").request().get(String.class);
Assert.assertEquals(MESSAGE, response);
System.out.println(response);
}
#Test
public void costructorInjectionOk() {
String response = target("constructor").request().get(String.class);
Assert.assertEquals(MESSAGE, response);
System.out.println(response);
}
#Test
public void fieldInjectionOk() {
String response = target("field").request().get(String.class);
Assert.assertEquals(MESSAGE, response);
System.out.println(response);
}
}
UPDATE
The solution, without having to make it a #PreMatching filter, is to inject with javax.inject.Provider. This will allow you to lazily retrieve the object. I guess what happens with the constructor and field injection is that right after matching the resource class, it it immediately created and injected. Because the filter hasn't been called yet, there is no object for the factory. It works for the method injection, because it is just like any other method call. The object is passed to it when the method is called. Below is the example with the javax.inject.Provider
#Path("constructor")
public static class ConstructorResource {
private final javax.inject.Provider<MyObject> myObjectProvider;
#Inject
public ConstructorResource(javax.inject.Provider<MyObject> myObjectProvider) {
this.myObjectProvider = myObjectProvider;
}
#GET
public String getResponse() {
return myObjectProvider.get().getValue();
}
}
#Path("field")
public static class FieldResource {
#Inject
private javax.inject.Provider<MyObject> myObjectProvider;;
#GET
public String getResponse() {
return myObjectProvider.get().getValue();
}
}
I have a backing bean called e.g. PeopleListBean. Purpose is simple: return a list of people from a repository.
public class PeopleListBean {
#Autowired
private PersonRepository personRepository;
private List<Person> people;
#PostConstruct
private void initializeBean() {
this.people = loadPeople();
}
public List<User> getPeople() {
return this.people;
}
private List<Person> loadPeople() {
return personRepository.getPeople();
}
}
I want to create a unit test for this bean, using Junit and Mockito.
Example test class below:
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.example.PersonRepository;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:/test-application-context.xml" })
public class PeopleListBeanTest {
#Autowired
private PeopleListBean peopleListBean;
#Autowired
private PersonRepository mockPersonRepository;
#Before
public void init() {
reset(mockPersonRepository);
}
#Test
public void canListPeople() {
List<Person> people = getDummyList();
when(mockPersonRepository.getPeople().thenReturn(people);
assertTrue(peopleListBean.getPeople().size() == people.size());
}
}
My issue is, when/how to mock the repository since the loading takes place in the initializeBean method (#PostConstruct). So after the class is constructed, the "getPeople" method is called before I can actually mock the method resulting in an assertion mismatch.
I'd really appreciate some help/guidance!
Use JUnit's #BeforeClass annotation
Your code would therefore look as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:/test-application-context.xml" })
public class PeopleListBeanTest {
#Autowired
private PeopleListBean peopleListBean;
#Autowired
private PersonRepository mockPersonRepository;
#BeforeClass
public static void initialise() {
}
// .
// .
// .
}