У меня есть приложение, работающее с Spring, и в некоторых местах я использую AOP. Поскольку я хочу использовать аннотацию @Transactional на уровне интерфейса, я должен разрешить Spring создавать прокси-серверы JDK. Поэтому я не устанавливаю для свойства proxy-target-class значение true. С другой стороны, я не хочу создавать интерфейс для каждого отдельного класса, который я бы посоветовал: если интерфейс просто не имеет смысла, я хочу иметь только реализацию, и Spring должен создать прокси CGLIB.
Все работало отлично, как я описал. Но я хотел, чтобы некоторые другие аннотации (созданные мной) шли в интерфейсах и были «унаследованы» классами реализации (как и @Transactional). Оказывается, что я не могу сделать это с помощью встроенной поддержки AOP в Spring (по крайней мере, я не мог понять, как это сделать после некоторых исследований. Аннотация в интерфейсе не видна в классе реализации, и следовательно, этот класс не получают совет).
Поэтому я решил реализовать свои собственные pointcut и interceptor , позволяющие аннотациям других методов идти по интерфейсам. По сути, мой pointcut ищет аннотации для метода и, пока не найден, в том же методе (с теми же типами имен и параметров) интерфейсов, которые реализует класс или его суперклассы.
Проблема в том, что когда я объявляю компонент DefaultAdvisorAutoProxyCreator, который будет правильно применять этот pointcut / interceptor, поведение советующих классов без интерфейсов нарушается. Очевидно, что-то идет не так, и Spring пытается дважды проксировать мои классы, один раз с CGLIB, а затем с JDK.
Это мой файл конфигурации:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">
<!-- Activates various annotations to be detected in bean classes: Spring's
@Required and @Autowired, as well as JSR 250's @Resource. -->
<context:annotation-config />
<context:component-scan base-package="mypackage" />
<!-- Instruct Spring to perform declarative transaction management automatically
on annotated classes. -->
<tx:annotation-driven transaction-manager="transactionManager" />
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
<bean id="logger.advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<constructor-arg>
<bean class="mypackage.MethodAnnotationPointcut">
<constructor-arg value="mypackage.Trace" />
</bean>
</constructor-arg>
<constructor-arg>
<bean class="mypackage.TraceInterceptor" />
</constructor-arg>
</bean>
</beans>
Это класс, который я хочу проксировать без интерфейсов:
@Component
public class ServiceExecutorImpl
{
@Transactional
public Object execute(...)
{
...
}
}
Когда я пытаюсь autowire это в другом бобе, например:
public class BaseService {
@Autowired
private ServiceExecutorImpl serviceExecutorImpl;
...
}
Я получаю следующее исключение:
java.lang.IllegalArgumentException: Can not set mypackage.ServiceExecutorImpl field mypackage.BaseService.serviceExecutor to $Proxy26
Вот некоторые строки вывода Spring:
13:51:12,672 [main] DEBUG [org.springframework.aop.framework.Cglib2AopProxy] - Creating CGLIB2 proxy: target source is SingletonTargetSource for target object [mypackage.ServiceExecutorImpl@1eb515]
...
13:51:12,782 [main] DEBUG [org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'serviceExecutorImpl' with 0 common interceptors and 1 specific interceptors
13:51:12,783 [main] DEBUG [org.springframework.aop.framework.JdkDynamicAopProxy] - Creating JDK dynamic proxy: target source is SingletonTargetSource for target object [mypackage.ServiceExecutorImpl$$EnhancerByCGLIB$$2eb5f51@5f31b0]
Я мог бы предоставить полный вывод, если кто-то думает, что это поможет. Я понятия не имею, почему Spring пытается «дважды прокси» мой класс, и почему это просто происходит, когда я объявляю bean-компонент DefaultAdvisorAutoProxyCreator.
Я уже давно борюсь с этим, и любая помощь или идеи будут очень признательны.
EDIT:
Это мой исходный код перехватчика, как и было запрошено. Он в основном регистрирует выполнение метода (перехватываются только методы, аннотированные @Trace). Если метод аннотирован @Trace (false), запись в журнал приостанавливается до тех пор, пока метод не вернется.
public class TraceInterceptor
implements
MethodInterceptor
{
@Override
public Object invoke(
MethodInvocation invocation )
throws Throwable
{
if( ThreadExecutionContext.getCurrentContext().isLogSuspended() ) {
return invocation.proceed();
}
Method method = AopUtils.getMostSpecificMethod( invocation.getMethod(),
invocation.getThis().getClass() );
Trace traceAnnotation = method.getAnnotation( Trace.class );
if( traceAnnotation != null && traceAnnotation.value() == false ) {
ThreadExecutionContext.getCurrentContext().suspendLogging();
Object result = invocation.proceed();
ThreadExecutionContext.getCurrentContext().resumeLogging();
return result;
}
ThreadExecutionContext.startNestedLevel();
SimpleDateFormat dateFormat = new SimpleDateFormat( "dd/MM/yyyy - HH:mm:ss.SSS" );
Logger.log( "Timestamp: " + dateFormat.format( new Date() ) );
String toString = invocation.getThis().toString();
Logger.log( "Class: " + toString.substring( 0, toString.lastIndexOf( '@' ) ) );
Logger.log( "Method: " + getMethodName( method ) );
Logger.log( "Parameters: " );
for( Object arg : invocation.getArguments() ) {
Logger.log( arg );
}
long before = System.currentTimeMillis();
try {
Object result = invocation.proceed();
Logger.log( "Return: " );
Logger.log( result );
return result;
} finally {
long after = System.currentTimeMillis();
Logger.log( "Total execution time (ms): " + ( after - before ) );
ThreadExecutionContext.endNestedLevel();
}
}
// Just formats a method name, with parameter and return types
private String getMethodName(
Method method )
{
StringBuffer methodName = new StringBuffer( method.getReturnType().getSimpleName() + " "
+ method.getName() + "(" );
Class<?>[] parameterTypes = method.getParameterTypes();
if( parameterTypes.length == 0 ) {
methodName.append( ")" );
} else {
int index;
for( index = 0; index < ( parameterTypes.length - 1 ); index++ ) {
methodName.append( parameterTypes[ index ].getSimpleName() + ", " );
}
methodName.append( parameterTypes[ index ].getSimpleName() + ")" );
}
return methodName.toString();
}
}
Спасибо!