Pages

Tuesday, 11 March 2014

SOLVED: Proxy54 Error on Autowiring when AOP PointCut Matches Interface as Well as Impl Service

When using AOP pointcut with a regular expression to provide a transaction advice to all Beans ending with Service, problems can be encountered if the Service interface as well as the Service concrete class match the regular expression.

Point Cut Defined:
<aop:config>
            <aop:pointcut id="allServices" expression="bean(*Service)" />
            <aop:advisor advice-ref="txAdvice" pointcut-ref="allServices" />
</aop:config>
The above configuration will work without error as long as we do not have any Interface as well as its concrete class matching the regular expression. Autowiring issues will be faced when the Service interface and the the Service impl match the expression.

The Service Stack that can cause an issue is described below, only the class name has been mentioned for illustration (imports have been skipped). In the below design the Entity/Repository service followed a template pattern where all the concrete Repository Service extended from a template implementing the below service:
1) RepositoryService interface defining the basic Entity operations like, save, update, findByCritera
2) AbstractRepository interface where all the basic methods defined in the interface will be implemented.
3) ConcreteRepositoryService for each interface extending the AbstractRepositryService.

The interface that will be implemented by all Service

package com.gt.general.datajpa.service;
public interface RepositoryService <T AbstractEntity ,R extends JpaRepository <T,Long> &  QueryDslPredicateExecutor <T>> {
}

package com.gt.general.datajpa.serviceimpl;
@Service
public class TilesDefinitionService extends AbstractRepositoryService <AppConfig, AppConfigRepository> {
}


The problem is encountered when the Interface as well as the Impl class match the regular expression.
The reason for the issue is due to the way spring creates proxy as mentioned in the spring documentation (http://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/aop-api.html#aop-pfb-proxy-types) Section (9.5.3 JDK- and CGLIB-based proxies). 
According to the documentation if an Interface Service is found the Proxy is created using JDK proxy which cannot proxy the concrete class.

Solution:
1)  Autowire interfaces instead of the concrete class, which would be the preferred approach as it gives the designer an option to change the service implementation without have to identify all classes referring the services.
2) Change the Name of the Interface so that it does not match the regular expression. In the above example changing RepositoryService and AbstractRepositoryService to say RepositryServiceInterface and AbstractRepositoryServiceInterface respectively.
3) Change the regular expression so that the interface is never matched. E.g bean(*impl*Service) which will ensure that only the implementation class is picked and hence all the proxies created are CGLIB proxies.
4) Change the regular Expression and all the Concrete Service class Names such that the regular expression can refer only to concrete classes.  Say bean(*ServiceImpl) and AppConfigService to AppConfigServiceImpl.
5) In case all the above cannot be changed force the advice to implement the proxy over the concrete class using “<aop:aspectj-autoproxy proxy-target-class="true"/>” in the spring context xml.

Errors:
Error observed when the Service is Autowired directly without setters

Error Class:

@Service
public class TilesViewService {

 private static final Logger logger = LoggerFactory.getLogger(AppConfigController.class);

 private final String wildCardSeparatorString = "/";
 private final String replaceString = "_";

 @Autowired
 TilesHeaderService tilesHeaderService;

 @Autowired
 TilesDefinitionService tilesDefinitionService;
 
 
 public void setTilesHeaderService(TilesHeaderService tilesHeaderService) {
  this.tilesHeaderService = tilesHeaderService;
 }

 
 public void setTilesDefinitionService(TilesDefinitionService tilesDefinitionService) {
  this.tilesDefinitionService = tilesDefinitionService;
 }
}


          Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: com.gt.general.datajpa.serviceimpl.TilesHeaderService com.gt.framework.common.service.TilesViewService.tilesHeaderService; nested exception is java.lang.IllegalArgumentException: Can not set com.gt.general.datajpa.serviceimpl.TilesHeaderService field com.gt.framework.common.service.TilesViewService.tilesHeaderService to $Proxy54
 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:517)
 at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:286)
 ... 55 more
Caused by: java.lang.IllegalArgumentException: Can not set com.gt.general.datajpa.serviceimpl.TilesHeaderService field com.gt.framework.common.service.TilesViewService.tilesHeaderService to $Proxy54
 at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
 at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
 at sun.reflect.UnsafeObjectFieldAccessorImpl.set(Unknown Source)
 at java.lang.reflect.Field.set(Unknown Source)
 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:513)
 ... 57 more



Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: com.gt.general.datajpa.serviceimpl.TilesHeaderService com.gt.framework.common.service.TilesViewService.tilesHeaderService; nested exception is java.lang.IllegalArgumentException: Can not set com.gt.general.datajpa.serviceimpl.TilesHeaderService field com.gt.framework.common.service.TilesViewService.tilesHeaderService to $Proxy54
            at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:517)
            at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
            at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:286)
            ... 55 more
Caused by: java.lang.IllegalArgumentException: Can not set com.gt.general.datajpa.serviceimpl.TilesHeaderService field com.gt.framework.common.service.TilesViewService.tilesHeaderService to $Proxy54
            at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
            at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
            at sun.reflect.UnsafeObjectFieldAccessorImpl.set(Unknown Source)
            at java.lang.reflect.Field.set(Unknown Source)
            at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:513)
            ... 57 more


Using a Setter in the services leads to a different error, which finally lead me to the actual problem

@Service
public class TilesViewService {

            private static final Logger logger = LoggerFactory.getLogger(AppConfigController.class);

            private final String wildCardSeparatorString = "/";
            private final String replaceString = "_";

           
            TilesHeaderService tilesHeaderService;

           
            TilesDefinitionService tilesDefinitionService;
           
            @Autowired
            public void setTilesHeaderService(TilesHeaderService tilesHeaderService) {
                        this.tilesHeaderService = tilesHeaderService;
            }

            @Autowired
            public void setTilesDefinitionService(TilesDefinitionService tilesDefinitionService) {
                        this.tilesDefinitionService = tilesDefinitionService;
            }
}
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void com.gt.framework.common.service.TilesViewService.setTilesHeaderService(com.gt.general.datajpa.serviceimpl.TilesHeaderService); nested exception is java.lang.IllegalArgumentException: argument type mismatch
            at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:604)
            at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
            at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:286)
            ... 55 more
Caused by: java.lang.IllegalArgumentException: argument type mismatch
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
            at java.lang.reflect.Method.invoke(Unknown Source)
            at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:597)
            ... 57 more

No comments:

Post a Comment