How can I find all beans with the custom annotation #Foo? - spring

I have this spring configuration:
#Lazy
#Configuration
public class MyAppConfig {
#Foo #Bean
public IFooService service1() { return new SpecialFooServiceImpl(); }
}
How can I get a list of all beans that are annotated with #Foo?
Note: #Foo is a custom annotation defined by me. It's not one of the "official" Spring annotations.
[EDIT] Following the suggestions of Avinash T., I wrote this test case:
import static org.junit.Assert.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import java.lang.reflect.Method;
import java.util.Map;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
public class CustomAnnotationsTest {
#Test
public void testFindByAnnotation() throws Exception {
AnnotationConfigApplicationContext appContext = new AnnotationConfigApplicationContext( CustomAnnotationsSpringCfg.class );
Method m = CustomAnnotationsSpringCfg.class.getMethod( "a" );
assertNotNull( m );
assertNotNull( m.getAnnotation( Foo.class ) );
BeanDefinition bdf = appContext.getBeanFactory().getBeanDefinition( "a" );
// Is there a way to list all annotations of bdf?
Map<String, Object> beans = appContext.getBeansWithAnnotation( Foo.class );
assertEquals( "[a]", beans.keySet().toString() );
}
#Retention( RetentionPolicy.RUNTIME )
#Target( ElementType.METHOD )
public static #interface Foo {
}
public static class Named {
private final String name;
public Named( String name ) {
this.name = name;
}
#Override
public String toString() {
return name;
}
}
#Lazy
#Configuration
public static class CustomAnnotationsSpringCfg {
#Foo #Bean public Named a() { return new Named( "a" ); }
#Bean public Named b() { return new Named( "b" ); }
}
}
but it fails with org.junit.ComparisonFailure: expected:<[[a]]> but was:<[[]]>. Why?

Use getBeansWithAnnotation() method to get beans with annotation.
Map<String,Object> beans = applicationContext.getBeansWithAnnotation(Foo.class);
Here is similar discussion.

UPDATE: Spring 5.2 changed the behavior of context.getBeansWithAnnotation(...) and it now correctly handles beans created via factory methods. So simply use that.
Original answer
While the accepted answer and Grzegorz's answer contain approaches that will work in all cases, I found a much much simpler one that worked equally well for the most common cases.
Meta-annotate #Foo with #Qualifier:
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface Foo {
}
Sprinkle #Foo onto the factory methods, as described in the question:
#Foo
#Bean
public IFooService service1() {
return new SpecialFooServiceImpl();
}
But it will also work on the type level:
#Foo
#Component
public class EvenMoreSpecialFooServiceImpl { ... }
Then, inject all the instances qualified by #Foo, regardless of their type and creation method:
#Autowired
#Foo
List<Object> fooBeans;
fooBeans will then contain all the instances produced by a #Foo-annotated method (as required in the question), or created from a discovered #Foo annotated class.
The list can additionally be filtered by type if needed:
#Autowired
#Foo
List<SpecialFooServiceImpl> fooBeans;
The good part is that it will not interfere with any other #Qualifier (meta)annotations on the methods, nor #Component and others on the type level. Nor does it enforce any particular name or type on the target beans.

With the help of a couple of Spring experts, I found a solution: The source property of a BeanDefinition can be AnnotatedTypeMetadata. This interface has a method getAnnotationAttributes() which I can use to get the annotations of a bean method:
public List<String> getBeansWithAnnotation( Class<? extends Annotation> type, Predicate<Map<String, Object>> attributeFilter ) {
List<String> result = Lists.newArrayList();
ConfigurableListableBeanFactory factory = applicationContext.getBeanFactory();
for( String name : factory.getBeanDefinitionNames() ) {
BeanDefinition bd = factory.getBeanDefinition( name );
if( bd.getSource() instanceof AnnotatedTypeMetadata ) {
AnnotatedTypeMetadata metadata = (AnnotatedTypeMetadata) bd.getSource();
Map<String, Object> attributes = metadata.getAnnotationAttributes( type.getName() );
if( null == attributes ) {
continue;
}
if( attributeFilter.apply( attributes ) ) {
result.add( name );
}
}
}
return result;
}
gist with full code of helper class and test case

Short story
It is not enough to put #Foo on the a() method in order to make the a bean annotated with #Foo.
Long story
I didn't realize it before I started debugging Spring code, a breakpoint at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(String, Class<A>) helped me understand it.
Of course, if you moved your annotation to the Named class:
#Foo
public static class Named {
...
and fixed some minor details of your test (annotation target, etc.) the test works.
After giving it a second thought, it's quite natural. When getBeansWithAnnotation() is called, the only information Spring has are the beans. And beans are objects, objects have classes. And Spring doesn't seem to need to store any additional information, incl. what was the factory method used to create the bean annotated with, etc.
EDIT There is an issue which requests to preserve annotations for #Bean methods: https://jira.springsource.org/browse/SPR-5611
It has been closed as "Won't fix" with the following workaround:
Employ a BeanPostProcessor
Use the beanName provided to the BPP methods to look up the associated BeanDefinition from the enclosing BeanFactory
Query that BeanDefinition for its factoryBeanName (the #Configuration bean) and factoryMethodName (the #Bean name)
use reflection to get hold of the Method the bean originated from
use reflection to interrogate any custom annotations from that method

To get all annotated beans:
context.getBeansWithAnnotation(Foo.class)
This returns a Map<String, Object> where the key is the bean name.
To get the annotation class:
context.findAnnotationOnBean(beanName, Foo.class);
This can be helpful when the annotation has values (#Foo(weight=100)).

This is how to get annotated beans.
#Autowired
private ApplicationContext ctx;
public void processAnnotation() {
// Getting annotated beans with names
Map<String, Object> allBeansWithNames = ctx.getBeansWithAnnotation(TestDetails.class);
//If you want the annotated data
allBeansWithNames.forEach((beanName, bean) -> {
TestDetails testDetails = (TestDetails) ctx.findAnnotationOnBean(beanName, TestDetails.class);
LOGGER.info("testDetails: {}", testDetails);
});
}

In my case getBeansWithAnnotation was returning an empty list. My mistake was to not adding retention and target on my custom annotation.
Adding these lines no top of my annotation fixed it.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)

Related

Mapstruct - How can I inject a spring dependency in the Generated Mapper class

I need to inject a spring service class in the generated mapper implementation, so that I can use it via
#Mapping(target="x", expression="java(myservice.findById(id))")"
Is this applicable in Mapstruct-1.0?
As commented by brettanomyces, the service won't be injected if it is not used in mapping operations other than expressions.
The only way I found to this is :
Transform my mapper interface into an abstract class
Inject the service in the abstract class
Make it protected so the "implementation" of the abstract class has access
I'm using CDI but it should be the samel with Spring :
#Mapper(
unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE,
componentModel = "spring",
uses = {
// My other mappers...
})
public abstract class MyMapper {
#Autowired
protected MyService myService;
#Mappings({
#Mapping(target="x", expression="java(myservice.findById(obj.getId())))")
})
public abstract Dto myMappingMethod(Object obj);
}
It should be possible if you declare Spring as the component model and add a reference to the type of myservice:
#Mapper(componentModel="spring", uses=MyService.class)
public interface MyMapper { ... }
That mechanism is meant for providing access to other mapping methods to be called by generated code, but you should be able to use them in the expression that way, too. Just make sure you use the correct name of the generated field with the service reference.
Since 1.2 this can be solved with a combination of #AfterMapping and #Context.. Like this:
#Mapper(componentModel="spring")
public interface MyMapper {
#Mapping(target="x",ignore = true)
// other mappings
Target map( Source source, #Context MyService service);
#AfterMapping
default void map( #MappingTarget Target.X target, Source.ID source, #Context MyService service) {
target.set( service.findById( source.getId() ) );
}
}
The service can be passed as context.
A nicer solution would be to use an #Context class which wrap MyService in stead of passing MyService directly. An #AfterMapping method can be implemented on this "context" class: void map( #MappingTarget Target.X target, Source.ID source ) keeping the mapping logic clear of lookup logic. Checkout this example in the MapStruct example repository.
What's worth to add in addition to the answers above is that there is more clean way to use spring service in mapstruct mapper, that fits more into "separation of concerns" design concept, called "qualifier". Easy re-usability in other mappers as a bonus.
For sake of simplicity I prefer named qualifier as noted here http://mapstruct.org/documentation/stable/reference/html/#selection-based-on-qualifiers
Example would be:
import org.mapstruct.Mapper;
import org.mapstruct.Named;
import org.springframework.stereotype.Component;
#Component
#Mapper
public class EventTimeQualifier {
private EventTimeFactory eventTimeFactory; // ---> this is the service you want yo use
public EventTimeQualifier(EventTimeFactory eventTimeFactory) {
this.eventTimeFactory = eventTimeFactory;
}
#Named("stringToEventTime")
public EventTime stringToEventTime(String time) {
return eventTimeFactory.fromString(time);
}
}
This is how you use it in your mapper:
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
#Mapper(componentModel = "spring", uses = EventTimeQualifier.class)
public interface EventMapper {
#Mapping(source = "checkpointTime", target = "eventTime", qualifiedByName = "stringToEventTime")
Event map(EventDTO eventDTO);
}
I am using Mapstruct 1.3.1 and I have found this problem is easy to solve using a decorator.
Example:
#Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE,
componentModel = "spring")
#DecoratedWith(FooMapperDecorator.class)
public interface FooMapper {
FooDTO map(Foo foo);
}
public abstract class FooMapperDecorator implements FooMapper{
#Autowired
#Qualifier("delegate")
private FooMapper delegate;
#Autowired
private MyBean myBean;
#Override
public FooDTO map(Foo foo) {
FooDTO fooDTO = delegate.map(foo);
fooDTO.setBar(myBean.getBar(foo.getBarId());
return fooDTO;
}
}
Mapstruct will generate 2 classes and mark the FooMapper that extends FooMapperDecorator as the #Primary bean.
I can't use componentModel="spring" because I work in a large project that doesn't use it. Many mappers includes my mapper with Mappers.getMapper(FamilyBasePersonMapper.class), this instance is not the Spring bean and the #Autowired field in my mapper is null.
I can't modifiy all mappers that use my mapper. And I can't use particular constructor with the injections or the Spring's #Autowired dependency injection.
The solution that I found: Using a Spring bean instance without using Spring directly:
Here is the Spring Component that regist itself first instance (the Spring instance):
#Component
#Mapper
public class PermamentAddressMapper {
#Autowired
private TypeAddressRepository typeRepository;
#Autowired
private PersonAddressRepository personAddressRepository;
static protected PermamentAddressMapper FIRST_INSTANCE;
public PermamentAddressMapper() {
if(FIRST_INSTANCE == null) {
FIRST_INSTANCE = this;
}
}
public static PermamentAddressMapper getFirstInstance(){
return FIRST_INSTANCE;
}
public static AddressDTO idPersonToPermamentAddress(Integer idPerson) {
//...
}
//...
}
Here is the Mapper that use the Spring Bean accross getFirstInstance method:
#Mapper(uses = { NationalityMapper.class, CountryMapper.class, DocumentTypeMapper.class })
public interface FamilyBasePersonMapper {
static FamilyBasePersonMapper INSTANCE = Mappers.getMapper(FamilyBasePersonMapper.class);
#Named("idPersonToPermamentAddress")
default AddressDTO idPersonToPermamentAddress(Integer idPerson) {
return PermamentAddressMapper.getFirstInstance()
.idPersonToPermamentAddress(idPersona);
}
#Mapping(
source = "idPerson",
target="permamentAddres",
qualifiedByName="idPersonToPermamentAddress" )
#Mapping(
source = "idPerson",
target = "idPerson")
FamilyDTO toFamily(PersonBase person);
//...
Maybe this is not the best solution. But it has helped to decrement the impact of changes in the final resolution.

Get bean from ApplicationContext by qualifier

Given this code:
public interface Service {}
#Component
#Qualifier("NotWanted")
public class NotWantedService implements Service {}
#Component
#Qualifier("Wanted")
public class WantedService implements Service {}
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(NotWantedService.class);
ctx.register(WantedService.class);
ctx.refresh()
How do I now do:
ctx.getBean(Service.class)
in a way that will only get the one with #Qualifier("Wanted") and not the one with #Qualifier("NotWanted")? I'm specifically asking if it's possible to do it using getBean, not injecting to a class, then using that one as a kind of proxy.
You can use
BeanFactoryAnnotationUtils.qualifiedBeanOfType(ctx.getBeanFactory(), Service.class, "Wanted")
It's important to use ctx.getBeanFactory(), not ctx itself, because the 'qualifiedBeanOfType' method can resolve qualifiers only for ConfigurableListenableBeanFactory.
It's not the #Qualifier annotation's purpose to use it when getting beans via ApplicationContext. But since you need such or similar functionality for some reasons, I suggest a workaround.
Create #Wanted and #NotWanted annotation:
#Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Wanted {
}
and
#Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface NotWanted {
}
Annotate your bean classes with these new annotations:
#Component
#NotWanted
public class NotWantedService implements Service {}
and
#Component
#Wanted
public class WantedService implements Service {}
Then you should add 2 methods somewhere where you have access to the ApplicationContext :
ApplicationContext applicationContext;
private <T> Collection<T> getBeansByTypeAndAnnotation(Class<T> clazz, Class<? extends Annotation> annotationType){
Map<String, T> typedBeans = applicationContext.getBeansOfType(clazz);
Map<String, Object> annotatedBeans = applicationContext.getBeansWithAnnotation(annotationType);
typedBeans.keySet().retainAll(annotatedBeans.keySet());
return typedBeans.values();
}
private <T> Optional<T> getBeanByTypeAndAnnotation(Class<T> clazz, Class<? extends Annotation> annotationType) {
Collection<T> beans = getBeansByTypeAndAnnotation(clazz, annotationType);
return beans.stream().findFirst();
}
And now you can use them to get beans or one bean by annotation and type like this:
Collection<Service> services = getBeansByTypeAndAnnotation(Service.class, Wanted.class);
or
Service service = getBeanByTypeAndAnnotation(Service.class, Wanted.class);
Possibly it's not the best way to deal with the problem. But since we are unable to get beans from ApplicationContext by qualifier and type 'out of box', that's one of the ways to do this.
If you want to get bean from context not injecting, better way to define bean name in #Component annotation and get it by name from context. In most cases #Qualifier is used for injections.
In my case, I have two qualified bean of one class, e.g
#Configuration
public class AConfig {
#Bean(name = "a")
public Hello hello1() {
return new Hello();
}
#Bean(name = "b")
public Hello hello2() {
return new Hello();
}
}
Then I can get a specific bean by
ApplicationContext context = SpringApplication.run(AutoConfiguration.class);
var aHello = context.getBean("a", Hello.class);
var bHello = context.getBean("b", Hello.class);
This is the simplest way of doing this. Or you can do the same thing as below:
var aHello = context.getBeansOfType(Hello.class).getBean("a");
var bHello = context.getBeansOfType(Hello.class).getBean("b");
Or you can also do as #Dmitry Ovchinnikov says:
BeanFactoryAnnotationUtils.qualifiedBeanOfType(ctx.getBeanFactory(), Service.class, "Wanted")
In this case, ctx.getBeanFactory() can be replaced by context.getAutowireCapableBeanFactory().
The closest canonical way to do this in Spring is with the utility class BeanFactoryAnnotationUtils ... but this sadly only works with #Qualifier annotation value argument directly (hence why the argument is a string).
What #Rozart is recommending is the best approach and really something like that should be in BeanFactoryAnnotationUtils. I only included the above answer in the case someone lands here and does want to use #Qualifier directly (and all the bean aliasing that comes along with it).
I recommend filing a feature request in Spring (I would but I think they might be tired of me bugging them :-) ).

Spring #Required properties when creating #Bean annotated beans

I'm developing a Spring Boot application and am trying out using Java annotation-based bean creation (using #Configuration and #Bean) rather than the familiar old XML-based bean creation. I'm puzzled though. If I attempt to create a bean in XML but fail to set an #Required property I get a BeanInitializationException when the application context is created. In my trials so far with annotation-based bean creation though this does not seem to be the case.
For example:
public class MyClass {
...
#Required
public void setSomeProp(String val){
}
}
Then in Spring XML:
<bean class="MyClass"/>
This will blow up during application startup (and IntelliJ flags it) because the required property is not set. But the same does not seem to be true of this:
#Configuration
public class MyConfig {
#Bean
public MyClass myClass() {
return new MyClass();
}
}
This application starts up just fine even though the required property is not ever set. I must be missing something here, because this seems like a pretty key feature in Spring.
UPDATE
I did some digging & debugging and it turns out that the bean definition is somehow being flagged to skip checking that #Required fields are set. In the Spring class 'RequiredAnnotationBeanPostProcessor' the boolean method 'shouldSkip()' is returning true for beans created this way. When I used the debugger to force that method to return false bean creation did indeed blow up with the expected exception.
Seeing as I'm making a pretty basic Spring Boot application I'm inclined (as Zergleb suggests) to submit this as a bug.
UPDATE 2
Some further debugging has revealed that even if the field is getting set forcing the check still throws the same exception, as if it hadn't been set. So perhaps dunni is correct and there is no way for this to work with #Bean notation.
As you said I also could not get #Required to run as expected this may be a bug and needs to be reported. I have a few other suggestions that did work for me.
Class annotated with #Configuration
//With the bean set up as usual These all worked
#Bean
public MyClass myClass() {
return new MyClass();
}
When you annotate the class #Component and load using component scanning works as expected.(The component scanning part is important you either need your #Configuration class to either have #ComponentScan or perhaps remove #Configuration and replace with #SpringBootApplication and this will enable scanning for components without needing to wire them up using #Bean configs)
#Component // Added this
public class MyClass {
...
#Required //Failed as expected
public void setSomeProp(String val){
}
}
Use #Autowired(required=true) //Fails with BeanCreationException //No qualifying bean of type [java.lang.String] found for dependency
//No more #Component
public class MyClass {
...
#Autowired(required=true) //Fails
public void setSomeProp(String val){
}
}
#Autowired required=false //Does not crash
public class MyClass {
...
#Autowired(required=false) //Simply never gets called if missing
public void setSomeProp(String val){
}
}
#Value //Does not work if test.property is missing // Could not resolve placeholder 'test.property' in string value "${test.property}
public class MyClass {
#Value("${test.property}")
String someProp;
//This getter is not neccesary neither is a setter
public String getSomeProp() {
return this.someProp;
}
}
#Value with default value//Does not crash // When getSomeProp is called it returns "My Default Value"(Unless you have test.property=Anything in your application.properties file then it returns "Anything"
public class MyClass {
#Value("${test.property:My Default Value}")
String someProp;
//This getter is not neccesary neither is a setter
public String getSomeProp() {
return this.someProp; //Returns "My Default Value"
}
}
Inside your #Configuration file also fails if it cannot find anything to populate String someProp in the myClass method
#Bean
public MyClass myClass(String someProp) { //Fails being unable to populate this arg
MyClass myObj = new MyClass();
myObj.setSomeProp(someProp);
return ;
}
If course this won't work, since you create the object of MyClass yourself (new MyClass()), thus the annotations are not evaluated. If you create a bean with a #Bean method, the container will only make sure, that all dependencies are there (method parameters) and that the bean scope is adhered to, meaning if it's a singleton bean, only one bean is created per application context. The creation of the bean/object itself is solely the responsibility of the developer.
The equivalent of the xml <bean> tag is annotating the class with #Component, where the bean is created completely by the container, thus the annotations are evaluated.
As it is being said that when you are having your own #Configuration class where you are creating the bean by itself, #Required doesn't apply there.
When you already have a #Component, let Spring Boot do the component scan and at the required setter property you can add #Autowired and it will work fine.
Found this link on web- https://www.boraji.com/spring-required-annotation-example
For example:
I have a Component called Employee having Id and Name.
#Component
public class Employee {
int id;
String name;
public int getId() {
return id;
}
#Autowired
#Required
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I have a Configuration class called AppConfig.java
#Configuration
public class AppConfig {
#Bean
public int getId() {
return 1;
}
}
So now we see, that component Employee needs an Id property for binding during startup, so I wrote bean method of type Integer, which will get autowired during runtime. If you do not write a bean of type Integer, it will result a BeanCreationException.
And here is my main class file.
#SpringBootApplication
public class SingletonApplication {
public static void main(String[] args) {
ApplicationContext ctx =
SpringApplication.run(SingletonApplication.class, args);
Employee emp = (Employee)ctx.getBean(Employee.class);
System.out.println(emp.getId());
}
}

Spring annotations - #Configuration to invoke spring bean auto-building

If I declare a class using #Bean and then component scan for the class, spring will instantiate the class by invoking it's constructor and injecting constructor args and injecting any fields marked with #Inject. For simplicity's sake, lets call this spring auto-building.
I dislike component scan and wish to avoid it completely (I don't wish to discuss my reasons for not liking it). I would like to use a #Configuration object instead but would still like to have the auto-building functionality available to me. Is it possible to invoke spring to auto-build my objects instead of explicitly having to pass all the constructor arguments in my #Configuration object?
Lets assume that I have a bean:
public class MyServiceImpl implements MyService {
public MyServiceImpl(Dependency1 d1, Dependency d2) { ... }
....
}
I could define a configuration object like this:
#Configuration
public class MyConfiguration {
// lets assume d1 and d2 are defined in another #Configuration
#Inject
Dependency1 d1;
#Inject
Dependency2 d2;
#Bean
public MyService myService() {
// I dislike how I have to explicitly call the constructor here
return new MyServiceImpl(d1, d2);
}
}
But now, I have explicitly had to call the MyServiceImpl constructor myself so I will have to keep updating this as my constructor changes over time.
I was hoping that I could declare an abstract method so that spring auto-building could take place:
#Configuration
public abstract class MyConfiguration {
#Bean
public abstract MyServiceImpl myService();
}
But this doesn't work. Is there a way that I can invoke spring auto-building without using a component scan?
In Google Guice, this can be done via the Binder:
https://google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/Binder.html
In Tapestry IOC, this can be done via the ServiceBinder:
http://tapestry.apache.org/ioc-cookbook-basic-services-and-injection.html#IoCCookbook-BasicServicesandInjection-SimpleServices
Update
Based on spod's answer, I was able to achieve what I was after (thanks!). Test case included for anyone that wants to do the same:
import java.util.Date;
import javax.inject.Inject;
import junit.framework.Assert;
import org.junit.Test;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class AutoBuildConfigurationTest {
#Configuration
public static class MyConfiguration {
#Inject
private AutowireCapableBeanFactory beanFactory;
#Bean
public Date date() {
return new Date(12345);
}
#Bean
public MyService myService() {
return autoBuild(MyService.class);
}
protected <T> T autoBuild(Class<T> type) {
return type.cast(beanFactory.createBean(type, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, true));
}
}
public static class MyService {
private Date date;
public MyService(Date date) {
this.date = date;
}
public Date getDate() {
return date;
}
}
#Test
public void testAutoBuild() {
ApplicationContext appContext = new AnnotationConfigApplicationContext(MyConfiguration.class);
MyService myService = appContext.getBean(MyService.class);
Assert.assertEquals(12345, myService.getDate().getTime());
}
}
The java based container configuration doesnt depend on doing a component scan in any way. Its merely a different approach for the XML based component configuration. With the XML configuration you'd just have to declare your bean with the MyServiceImpl class in case its already #inject annotated. Spring would recognize the annotations and take care of them. If you really want to instanciate MyServiceImpl from a #Configuration java class without calling the constructor yourself, then you'd have to make use of the bean factory (havent tested it, just give it a try):
#Configuration
public class MyConfiguration {
#Autowired AutowireCapableBeanFactory beanFactory;
#Bean public MyService myService() {
return beanFactory.createBean(MyServiceImpl.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, true);
}
}

Using Spring beans as a key with #Cacheable annotation

How to make following to work:
- a spring bean that has a method that should be cached with #Cacheable annotation
- another spring bean that creates keys for the cache (KeyCreatorBean).
So the code looks something like this.
#Inject
private KeyCreatorBean keyCreatorBean;
#Cacheable(value = "cacheName", key = "{#keyCreatorBean.createKey, #p0}")
#Override
public List<Examples> getExamples(ExampleId exampleId) {
...
However the above code doesn't work: it gives following exception:
Caused by: org.springframework.expression.spel.SpelEvaluationException:
EL1057E:(pos 2): No bean resolver registered in the context to resolve access to bean 'keyCreatorBean'
I checked the underlying cache resolution implementation, there doesn't appear to be a simple way to inject in a BeanResolver which is required for resolving the beans and evaluating expressions like #beanname.method.
So I would also recommend a somewhat hacky way along the lines of one which #micfra has recommended.
Along what he has said, have a KeyCreatorBean along these lines, but internally delegate it to the keycreatorBean that you registered in your application:
package pkg.beans;
import org.springframework.stereotype.Repository;
public class KeyCreatorBean implements ApplicationContextAware{
private static ApplicationContext aCtx;
public void setApplicationContext(ApplicationContext aCtx){
KeyCreatorBean.aCtx = aCtx;
}
public static Object createKey(Object target, Method method, Object... params) {
//store the bean somewhere..showing it like this purely to demonstrate..
return aCtx.getBean("keyCreatorBean").createKey(target, method, params);
}
}
In the case you have a static class function, it will work like this
#Cacheable(value = "cacheName", key = "T(pkg.beans.KeyCreatorBean).createKey(#p0)")
#Override
public List<Examples> getExamples(ExampleId exampleId) {
...
}
with
package pkg.beans;
import org.springframework.stereotype.Repository;
public class KeyCreatorBean {
public static Object createKey(Object o) {
return Integer.valueOf((o != null) ? o.hashCode() : 53);
}
}

Resources