Spring AOP Cannot work for #Around - spring

I am currently working on AOP but some how the AOP cannot working, The #around is not invoking after and before the execution on specific controller.Folloeing is my code.
// my controller
package com.rest.controllers;
class ManCon{
#PostMapping(EndPointReferrer.UPDATE_DEALER_SCHEME_MAPPING_DETAILS)
public ResponseEntity<BaseResponse> updateDealerSchemeMappingDetails(
#RequestBody #NotNull #Valid Myclass obj){
return null;
}
}
//configuration
#Configuration
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ComponentScan(basePackages = {"com.aop"})
public class AOPConfiguration {
#Bean(autowire = Autowire.BY_TYPE)
public SchemeMappingPortalAspect schemeMappingPortalAspect(){ return new SchemeMappingPortalAspect(); }
}
//Aspect
#Aspect
public class SchemeMappingPortalAspect {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SchemeMappingPortalAspect.class);
#Pointcut("execution(* com.rest.controllers.ManCon.updateDealerSchemeMappingDetails(..))")
public void schemeMappingPointcut(){
}
/*
* This is used for scheme mapping portal loging
* */
#Around(value = "schemeMappingPointcut()")
public Object loggingSchemePortalDetail(ProceedingJoinPoint joinPoint) throws Throwable{
logger.debug("-------------------------");
logger.debug("In before calling");
logger.debug("-------------------------");
Object o=null;
try {
o= joinPoint.proceed();
System.out.println("JSON::"+ new Gson().toJson(o));
}catch (Exception e){
e.printStackTrace();
}
logger.debug("-------------------------");
logger.debug("In After Calling");
logger.debug("-------------------------");
return o;
}
}

Related

How to test a try...finally method only been called once in SpringBoot?

I am following this article to implement a database read/write separation feature by calling different methods. However, I got the error:
Missing method call for verify(mock) here: verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY);
when doing the testing.
My test case is trying to verify DatabaseEnvironment.READONLY has been set once when using TransactionReadonlyAspect AOP annotation:
// TransactionReadonlyAspectTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {LoadServiceImpl.class, TransactionReadonlyAspect.class})
public class TransactionReadonlyAspectTest {
#Autowired
private TransactionReadonlyAspect transactionReadonlyAspect;
#MockBean
private LoadServiceImpl loadService;
#Test
public void testReadOnlyTransaction() throws Throwable {
ProceedingJoinPoint mockProceedingJoinPoint = mock(ProceedingJoinPoint.class);
Transactional mockTransactional = mock(Transactional.class);
DatabaseContextHolder spyDatabaseContextHolder = mock(DatabaseContextHolder.class);
when(mockTransactional.readOnly()).thenReturn(true);
when(loadService.findById(16)).thenReturn(null);
when(mockProceedingJoinPoint.proceed()).thenAnswer(invocation -> loadService.findById(16));
transactionReadonlyAspect.proceed(mockProceedingJoinPoint, mockTransactional);
verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY); // got the error: Missing method call for verify(mock)
verify(loadService, times(1)).findById(16);
assertEquals(DatabaseContextHolder.getEnvironment(), DatabaseEnvironment.UPDATABLE);
}
}
//TransactionReadonlyAspect.java
#Aspect
#Component
#Order(0)
#Slf4j
public class TransactionReadonlyAspect {
#Around("#annotation(transactional)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint,
org.springframework.transaction.annotation.Transactional transactional) throws Throwable {
try {
if (transactional.readOnly()) {
log.info("Inside method " + proceedingJoinPoint.getSignature());
DatabaseContextHolder.set(DatabaseEnvironment.READONLY);
}
return proceedingJoinPoint.proceed();
} finally {
DatabaseContextHolder.reset();
}
}
}
// DatabaseContextHolder.java
public class DatabaseContextHolder {
private static final ThreadLocal<DatabaseEnvironment> CONTEXT = new ThreadLocal<>();
public static void set(DatabaseEnvironment databaseEnvironment) {
CONTEXT.set(databaseEnvironment);
}
public static DatabaseEnvironment getEnvironment() {
DatabaseEnvironment context = CONTEXT.get();
System.out.println("context: " + context);
return CONTEXT.get();
}
public static void reset() {
CONTEXT.set(DatabaseEnvironment.UPDATABLE);
}
}
//DatabaseEnvironment.java
public enum DatabaseEnvironment {
UPDATABLE,READONLY
}
// LoadServiceImpl.java
#Service
public class LoadServiceImpl implements LoadService {
#Override
#Transactional(readOnly = true)
public LoadEntity findById(Integer Id) {
return this.loadDAO.findById(Id);
}
...
}
I just want to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) has been used once then in the TransactionReadonlyAspect finally block it will be reset to DatabaseEnvironment.UPDATABLE which make sense.
However, how to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) gets called once? Why does this error occur? Is there a better way to test TransactionReadonlyAspect?

Bean not getting overridden in Spring boot

I am trying to write and test an application that used spring-cloud with azure functions following this tutorial.
https://github.com/markusgulden/aws-tutorials/tree/master/spring-cloud-function/spring-cloud-function-azure/src/main/java/de/margul/awstutorials/springcloudfunction/azure
I am tryign to write a testcase and override the bean.
Here is the application class having function and handler Bean function.
#SpringBootApplication
#ComponentScan(basePackages = { "com.package" })
public class DataFunctions extends AzureSpringBootRequestHandler<GenericMessage<Optional<String>>, Data> {
#FunctionName("addData")
public HttpResponseMessage addDataRun(
#HttpTrigger(name = "add", methods = {
HttpMethod.POST }, authLevel = AuthorizationLevel.FUNCTION) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) throws JsonParseException, JsonMappingException, IOException {
context.getLogger().info("Java HTTP trigger processed a POST request.");
try {
handleRequest(new GenericMessage<Optional<String>>(request.getBody()), context);
} catch (ServiceException ex) {
ErrorMessage em = new ErrorMessage();
return request.createResponseBuilder(handleException(ex, em)).body(em).build();
}
return request.createResponseBuilder(HttpStatus.CREATED).build();
}
#Autowired
MyService mService;
#Bean
public Consumer<GenericMessage<Optional<String>>> addData() {
ObjectMapper mapper = new ObjectMapper();
return req -> {
SomeModel fp = null;
try {
fp = mapper.readValue(req.getPayload().get(), SomeModel.class);
} catch (Exception e) {
throw new ServiceException(e);
}
mService.addData(fp);
};
}
}
I want to test by overriding the above bean.
Cosmosdb spring configuration
#Configuration
#EnableDocumentDbRepositories
public class CosmosDBConfig extends AbstractDocumentDbConfiguration {
#Value("${cosmosdb.collection.endpoint}")
private String uri;
#Value("${cosmosdb.collection.key}")
private String key;
#Value("${cosmosdb.collection.dbname}")
private String dbName;
#Value("${cosmosdb.connect.directly}")
private Boolean connectDirectly;
#Override
public DocumentDBConfig getConfig() {
ConnectionPolicy cp = ConnectionPolicy.GetDefault();
if (connectDirectly) {
cp.setConnectionMode(ConnectionMode.DirectHttps);
} else {
cp.setConnectionMode(ConnectionMode.Gateway);
}
return DocumentDBConfig.builder(uri, key, dbName).connectionPolicy(cp).build();
}
}
Here is the configuration
#TestConfiguration
#PropertySource(value = "classpath:application.properties", encoding = "UTF-8")
#Profile("test")
#Import({DataFunctions.class})
public class TestConfig {
#Bean(name="addData")
#Primary
public Consumer<GenericMessage<Optional<String>>> addData() {
return req -> {
System.out.println("data mock");
};
}
#Bean
#Primary
public DocumentDBConfig getConfig() {
return Mockito.mock(DocumentDBConfig.class);
}
}
Finally the test class
#RunWith(SpringRunner.class)
//#SpringBootTest //Enabling this gives initialization error.
#ActiveProfiles("test")
public class TempTest {
#InjectMocks
DataFunctions func;
#Mock
MyService mService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
private Optional<String> createRequestString(final String res) throws IOException {
InputStream iStream = TempTest.class.getResourceAsStream(res);
String charset="UTF-8";
try (BufferedReader br = new BufferedReader(new InputStreamReader(iStream, charset))) {
return Optional.of(br.lines().collect(Collectors.joining(System.lineSeparator())));
}
}
#Test
public void testHttpPostTriggerJava() throws Exception {
#SuppressWarnings("unchecked")
final HttpRequestMessage<Optional<String>> req = mock(HttpRequestMessage.class);
final Optional<String> queryBody = createRequestString("/test-data.json");
doNothing().when(mService).addData(Mockito.any(SomeModel.class));
doReturn(queryBody).when(req).getBody();
doAnswer(new Answer<HttpResponseMessage.Builder>() {
#Override
public HttpResponseMessage.Builder answer(InvocationOnMock invocation) {
HttpStatus status = (HttpStatus) invocation.getArguments()[0];
return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
}
}).when(req).createResponseBuilder(any(HttpStatus.class));
final ExecutionContext context = mock(ExecutionContext.class);
doReturn(Logger.getGlobal()).when(context).getLogger();
doReturn("addData").when(context).getFunctionName();
// Invoke
final HttpResponseMessage ret = func.addDataRun(req, context);
// Verify
assertEquals(ret.getStatus(), HttpStatus.CREATED);
}
}
For this case instead of test configuration addData the actual bean is called from DataFunctions class. Also the database connection is also created when it should use the mocked bean from my test configuration. Can somebody please point out what is wrong in my test configuration?
I was able to resolve the first part of cosmos db config loading by marking it with
#Configuration
#EnableDocumentDbRepositories
#Profile("!test")
public class CosmosDBConfig extends AbstractDocumentDbConfiguration {
...
}
Also had to mark the repository bean as optional in the service.
public class MyService {
#Autowired(required = false)
private MyRepository myRepo;
}
Didn't use any spring boot configuration other than this.
#ActiveProfiles("test")
public class FunctionTest {
...
}
For the second part of providing mock version of Mock handlers, I simply made the test config file as spring application as below.
#SpringBootApplication
#ComponentScan(basePackages = { "com.boeing.da.helix.utm.traffic" })
#Profile("test")
public class TestConfiguration {
public static void main(final String[] args) {
SpringApplication.run(TestConfiguration.class, args);
}
#Bean(name="addData")
#Primary
public Consumer<GenericMessage<Optional<String>>> addData() {
return req -> {
System.out.println("data mock");
};
}
}
and made use of this constructor from azure functions library in spring cloud in my constructor
public class AppFunctions
extends AzureSpringBootRequestHandler<GenericMessage<Optional<String>>, List<Data>> {
public AppFunctions(Class<?> configurationClass) {
super(configurationClass);
}
}
public AzureSpringBootRequestHandler(Class<?> configurationClass) {
super(configurationClass);
}
Hope it helps someone.

Spring AOP with custom annotation on #Bean method

I'm trying to use AOP with annotation triggering as you can see in this pointcut
package mypackage.aop;
// ...
#Aspect
#Component
public class ErrorHandlerAspect {
private final static Logger LOGGER = LoggerFactory.getLogger(ErrorHandlerAspect.class);
#Pointcut("within(mypackage.config.steps..*) && #annotation(mypackage.aop.SaveAndErrors)")
private void pointcut(){ }
#Around("pointcut()")
private Object errorHandler(ProceedingJoinPoint pjp) throws Throwable{
try{
return pjp.proceed();
} catch (Throwable e){
LOGGER.error("Handling error");
throw e;
}
}
}
Here are declaration of annotation :
package mypackage.aop;
// ...
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface SaveAndErrors { }
... and usage in #Configuration class (for spring batch step configuration) :
package mypackage.config.steps;
// ...
#Configuration
public class StepConfiguration {
public final static String STEP_NAME = "xStep";
// ...
#SaveAndErrors
#Bean(name="xFileReader")
#StepScope
public ItemStreamReader<Object> xFileReader(#Value("#{stepExecutionContext['fileName']}") String resourceName // Inside a partitionner) throws xException {
try {
// ...
// return ...
} catch (yException e) {
throw new xException("new Exception :", e);
}
}
// ...
}
And my Application class :
#SpringBootApplication
public class Application { //...
}
Unfortunately, this is not working.
Removing && #annotation(mypackage.aop.SaveAndErrors) from pointcut, my aop proxy is working.
Where is the mistake?

Spring MVC Annotated Controller Interface with #PathVariable

Is there any reason not to map Controllers as interfaces?
In all the examples and questions I see surrounding controllers, all are concrete classes. Is there a reason for this? I would like to separate the request mappings from the implementation. I hit a wall though when I tried to get a #PathVariable as a parameter in my concrete class.
My Controller interface looks like this:
#Controller
#RequestMapping("/services/goal/")
public interface GoalService {
#RequestMapping("options/")
#ResponseBody
Map<String, Long> getGoals();
#RequestMapping(value = "{id}/", method = RequestMethod.DELETE)
#ResponseBody
void removeGoal(#PathVariable String id);
}
And the implementing class:
#Component
public class GoalServiceImpl implements GoalService {
/* init code */
public Map<String, Long> getGoals() {
/* method code */
return map;
}
public void removeGoal(String id) {
Goal goal = goalDao.findByPrimaryKey(Long.parseLong(id));
goalDao.remove(goal);
}
}
The getGoals() method works great; the removeGoal(String id) throws an exception
ExceptionHandlerExceptionResolver - Resolving exception from handler [public void
todo.webapp.controllers.services.GoalServiceImpl.removeGoal(java.lang.String)]:
org.springframework.web.bind.MissingServletRequestParameterException: Required
String parameter 'id' is not present
If I add the #PathVariable annotation to the concrete class everything works as expected, but why should i have to re-declare this in the concrete class? Shouldn't it be handled by whatever has the #Controller annotation?
Apparently, when a request pattern is mapped to a method via the #RequestMapping annotation, it is mapped to to the concrete method implementation. So a request that matches the declaration will invoke GoalServiceImpl.removeGoal() directly rather than the method that originally declared the #RequestMapping ie GoalService.removeGoal().
Since an annotation on an interface, interface method, or interface method parameter does not carry over to the implementation there is no way for Spring MVC to recognize this as a #PathVariable unless the implementing class declares it explicitly. Without it, any AOP advice that targets #PathVariable parameters will not be executed.
The feature of defining all bindings on interface actually got implement recently in Spring 5.1.5.
Please see this issue: https://github.com/spring-projects/spring-framework/issues/15682 - it was a struggle :)
Now you can actually do:
#RequestMapping("/random")
public interface RandomDataController {
#RequestMapping(value = "/{type}", method = RequestMethod.GET)
#ResponseBody
RandomData getRandomData(
#PathVariable(value = "type") RandomDataType type, #RequestParam(value = "size", required = false, defaultValue = "10") int size);
}
#Controller
public class RandomDataImpl implements RandomDataController {
#Autowired
private RandomGenerator randomGenerator;
#Override
public RandomData getPathParamRandomData(RandomDataType type, int size) {
return randomGenerator.generateRandomData(type, size);
}
}
You can even use this library: https://github.com/ggeorgovassilis/spring-rest-invoker
To get a client-proxy based on that interface, similarly to how RestEasys client framework works in the JAX-RS land.
It works in newer version of Spring.
import org.springframework.web.bind.annotation.RequestMapping;
public interface TestApi {
#RequestMapping("/test")
public String test();
}
Implement the interface in the Controller
#RestController
#Slf4j
public class TestApiController implements TestApi {
#Override
public String test() {
log.info("In Test");
return "Value";
}
}
It can be used as:
Rest client
Recently I had the same problem. Following has worked for me:
public class GoalServiceImpl implements GoalService {
...
public void removeGoal(#PathVariableString id) {
}
}
i resolved this problem.
ON CLIENT SIDE:
I'm using this library https://github.com/ggeorgovassilis/spring-rest-invoker/. This library generate a proxy from interface to invoke spring rest service.
I extended this library:
I created an annotations and a factory client class:
Identify a Spring Rest Service
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface SpringRestService {
String baseUri();
}
This class generates a client rest from interfaces
public class RestFactory implements BeanFactoryPostProcessor,EmbeddedValueResolverAware {
StringValueResolver resolver;
#Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.resolver = resolver;
}
private String basePackage = "com";
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
createBeanProxy(beanFactory,SpringRestService.class);
createBeanProxy(beanFactory,JaxrsRestService.class);
}
private void createBeanProxy(ConfigurableListableBeanFactory beanFactory,Class<? extends Annotation> annotation) {
List<Class<Object>> classes;
try {
classes = AnnotationUtils.findAnnotatedClasses(basePackage, annotation);
} catch (Exception e) {
throw new BeanInstantiationException(annotation, e.getMessage(), e);
}
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
for (Class<Object> classType : classes) {
Annotation typeService = classType.getAnnotation(annotation);
GenericBeanDefinition beanDef = new GenericBeanDefinition();
beanDef.setBeanClass(getQueryServiceFactory(classType, typeService));
ConstructorArgumentValues cav = new ConstructorArgumentValues();
cav.addIndexedArgumentValue(0, classType);
cav.addIndexedArgumentValue(1, baseUri(classType,typeService));
beanDef.setConstructorArgumentValues(cav);
registry.registerBeanDefinition(classType.getName() + "Proxy", beanDef);
}
}
private String baseUri(Class<Object> c,Annotation typeService){
String baseUri = null;
if(typeService instanceof SpringRestService){
baseUri = ((SpringRestService)typeService).baseUri();
}else if(typeService instanceof JaxrsRestService){
baseUri = ((JaxrsRestService)typeService).baseUri();
}
if(baseUri!=null && !baseUri.isEmpty()){
return baseUri = resolver.resolveStringValue(baseUri);
}else{
throw new IllegalStateException("Impossibile individuare una baseUri per l'interface :"+c);
}
}
private static Class<? extends FactoryBean<?>> getQueryServiceFactory(Class<Object> c,Annotation typeService){
if(typeService instanceof SpringRestService){
return it.eng.rete2i.springjsonmapper.spring.SpringRestInvokerProxyFactoryBean.class;
}else if(typeService instanceof JaxrsRestService){
return it.eng.rete2i.springjsonmapper.jaxrs.JaxRsInvokerProxyFactoryBean.class;
}
throw new IllegalStateException("Impossibile individuare una classe per l'interface :"+c);
}
}
I configure my factory:
<bean class="it.eng.rete2i.springjsonmapper.factory.RestFactory">
<property name="basePackage" value="it.giancarlo.rest.services" />
</bean>
ON REST SERVICE SIGNATURE
this is an example interface:
package it.giancarlo.rest.services.spring;
import ...
#SpringRestService(baseUri="${bookservice.url}")
public interface BookService{
#Override
#RequestMapping("/volumes")
QueryResult findBooksByTitle(#RequestParam("q") String q);
#Override
#RequestMapping("/volumes/{id}")
Item findBookById(#PathVariable("id") String id);
}
ON REST SERVICE IMPLEMENTATION
Service implementation
#RestController
#RequestMapping("bookService")
public class BookServiceImpl implements BookService {
#Override
public QueryResult findBooksByTitle(String q) {
// TODO Auto-generated method stub
return null;
}
#Override
public Item findBookById(String id) {
// TODO Auto-generated method stub
return null;
}
}
To resolve annotation on parameters I create a custom RequestMappingHandlerMapping that looks all interfaces annotated with #SpringRestService
public class RestServiceRequestMappingHandlerMapping extends RequestMappingHandlerMapping{
public HandlerMethod testCreateHandlerMethod(Object handler, Method method){
return createHandlerMethod(handler, method);
}
#Override
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
HandlerMethod handlerMethod;
if (handler instanceof String) {
String beanName = (String) handler;
handlerMethod = new RestServiceHandlerMethod(beanName,getApplicationContext().getAutowireCapableBeanFactory(), method);
}
else {
handlerMethod = new RestServiceHandlerMethod(handler, method);
}
return handlerMethod;
}
public static class RestServiceHandlerMethod extends HandlerMethod{
private Method interfaceMethod;
public RestServiceHandlerMethod(Object bean, Method method) {
super(bean,method);
changeType();
}
public RestServiceHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
super(bean,methodName,parameterTypes);
changeType();
}
public RestServiceHandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
super(beanName,beanFactory,method);
changeType();
}
private void changeType(){
for(Class<?> clazz : getMethod().getDeclaringClass().getInterfaces()){
if(clazz.isAnnotationPresent(SpringRestService.class)){
try{
interfaceMethod = clazz.getMethod(getMethod().getName(), getMethod().getParameterTypes());
break;
}catch(NoSuchMethodException e){
}
}
}
MethodParameter[] params = super.getMethodParameters();
for(int i=0;i<params.length;i++){
params[i] = new RestServiceMethodParameter(params[i]);
}
}
private class RestServiceMethodParameter extends MethodParameter{
private volatile Annotation[] parameterAnnotations;
public RestServiceMethodParameter(MethodParameter methodParameter){
super(methodParameter);
}
#Override
public Annotation[] getParameterAnnotations() {
if (this.parameterAnnotations == null){
if(RestServiceHandlerMethod.this.interfaceMethod!=null) {
Annotation[][] annotationArray = RestServiceHandlerMethod.this.interfaceMethod.getParameterAnnotations();
if (this.getParameterIndex() >= 0 && this.getParameterIndex() < annotationArray.length) {
this.parameterAnnotations = annotationArray[this.getParameterIndex()];
}
else {
this.parameterAnnotations = new Annotation[0];
}
}else{
this.parameterAnnotations = super.getParameterAnnotations();
}
}
return this.parameterAnnotations;
}
}
}
}
I created a configuration class
#Configuration
public class WebConfig extends WebMvcConfigurationSupport{
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RestServiceRequestMappingHandlerMapping handlerMapping = new RestServiceRequestMappingHandlerMapping();
handlerMapping.setOrder(0);
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());
PathMatchConfigurer configurer = getPathMatchConfigurer();
if (configurer.isUseSuffixPatternMatch() != null) {
handlerMapping.setUseSuffixPatternMatch(configurer.isUseSuffixPatternMatch());
}
if (configurer.isUseRegisteredSuffixPatternMatch() != null) {
handlerMapping.setUseRegisteredSuffixPatternMatch(configurer.isUseRegisteredSuffixPatternMatch());
}
if (configurer.isUseTrailingSlashMatch() != null) {
handlerMapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
}
if (configurer.getPathMatcher() != null) {
handlerMapping.setPathMatcher(configurer.getPathMatcher());
}
if (configurer.getUrlPathHelper() != null) {
handlerMapping.setUrlPathHelper(configurer.getUrlPathHelper());
}
return handlerMapping;
}
}
and I configurated it
<bean class="....WebConfig" />

Get parent bean in prototype bean that gets injected

I would like to have a Bean and a SubBean like this:
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
#Component
public class SubBean implements ApplicationContextAware{
private Object parent;
public void setApplicationContext(ApplicationContext ctx){
this.parent = doSomeMagicToGetMyParent(ctx);
}
public Object getParent(){
return parent;
}
}
#Component
public class SomeBean implements InitializingBean{
#Resource
private SubBean sub;
public void afterPropertiesSet(){
Assert.isTrue(this == sub.getParent());
}
}
The trick I want to achieve is, that the SubBean automagically gets a reference to the Bean it got injected into. Because the scope of the subbean is prototype, it will get injected as a new instance in every parent that wants it to get injected.
My big idea is to exploit this pattern to write a LoggerBean which can be injected into normal beans. The subbean should work just like a SLF4J Logger.
So does anyone know the magic to make this work? :)
EDIT: I've found a solution to do this with a custom BeanPostProcessor:
#Component
public class DependencyInjectionAwareBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
for (Field f : bean.getClass().getFields()) {
if (f.getType().isInstance(IDependencyInjectionAware.class)) {
ReflectionUtils.makeAccessible(f);
try {
IDependencyInjectionAware diAware = (IDependencyInjectionAware) f.get(bean);
diAware.injectedInto(bean);
} catch (IllegalArgumentException e) {
ReflectionUtils.handleReflectionException(e);
} catch (IllegalAccessException e) {
ReflectionUtils.handleReflectionException(e);
}
}
}
return bean;
}
}
Here is the Interface:
public interface IDependencyInjectionAware {
void injectedInto(Object parent);
}
And here a Bean using it:
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
#Component
public class SomeAwareBean implements IDependencyInjectionAware {
private Object parent;
public void injectedInto(Object parent){
this.parent = parent;
}
public Object getParent(){
return parent;
}
}
Here a test with a normal Bean which works perfectly:
#Component
public class UsingBean implements InitializingBean {
#Resource
private SomeAwareBean b;
public void afterPropertiesSet(){
Assert.notNull(b); //works
Assert.isTrue(b.getParent() == this); //works
}
}
Though, when using the same with a normal class which gets the depedencies injected via #Configurable, the test fails:
#Configurable
public class UsingPlainClass implements InitializingBean {
#Resource
private SomeAwareBean b;
public void afterPropertiesSet(){
Assert.notNull(b); //works
Assert.isTrue(b.getParent() == this); //fails because null is returned
}
}
So this seems to have gotten me to another question: Why won't my custom BeanPostProcessor run on a #Configurable classes? Maybe I have to resort to AspectJ afterall...
EDIT: Just to update the status. I did not implement this afterall because this is overengineering...
I find this simpler:
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
#Component
public class SubBean implements ApplicationContextAware{
private Object parent;
public void setApplicationContext(ApplicationContext ctx){
...
}
public Object getParent(){
return parent;
}
//ADDED CODE
public void setParent(Object parent) {
this.parent = parent;
}
//END ADDED CODE
}
#Component
public class SomeBean implements InitializingBean{
private SubBean sub;
//ADDED CODE
#Resource
public void setSub(SubBean sub) {
this.sub = sub;
sub.setParent(this);
}
//END ADDED CODE
public void afterPropertiesSet(){
Assert.isTrue(this == sub.getParent());
}
}
Fixed several bugs with the solution given by the original poster:
import java.lang.reflect.Field;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.ReflectionUtils;
public interface DependencyInjectionAware {
void injectedInto(final Object bean, final String beanName);
public static class DependencyInjectionAwareBeanPostProcessor implements
BeanPostProcessor {
private static final Logger logger = Logger.getLogger(DependencyInjectionAwareBeanPostProcessor.class);
#Override
public Object postProcessBeforeInitialization(final Object bean,
final String beanName) {
return bean;
}
#Override
public Object postProcessAfterInitialization(final Object bean,
final String beanName) {
for (final Field f : bean.getClass().getDeclaredFields()) {
logger.info("scanning field " + f.getName() + " of bean " + beanName + " (class= " + bean.getClass() + ")");
if (DependencyInjectionAware.class.isAssignableFrom(f.getType())) {
ReflectionUtils.makeAccessible(f);
try {
final DependencyInjectionAware diAware = (DependencyInjectionAware) f.get(bean);
diAware.injectedInto(bean, beanName);
} catch (final IllegalArgumentException e) {
ReflectionUtils.handleReflectionException(e);
} catch (final IllegalAccessException e) {
ReflectionUtils.handleReflectionException(e);
}
}
}
return bean;
}
}
}

Resources