我有一个运行Spring的应用程序,我在某些地方使用AOP。由于我想在接口级别使用@Transactional注释,我必须允许Spring创建JDK代理。因此,我没有将 proxy-target-class 属性设置为true。另一方面,我不想为我想要的每个类创建一个接口:如果接口没有意义,我想只有实现,Spring应该创建一个CGLIB代理。
就像我描述的那样,一切都很完美。但我希望在接口中有一些其他的注释(由我创建),并由实现类“继承”(就像@Transactional一样)。事实证明,我无法通过Spring中对AOP的内置支持来做到这一点(至少我无法弄清楚在经过一些研究后如何做到这一点。接口中的注释在实现类中是不可见的,并且因此,不会建议该课程。)
所以我决定实现自己的切入点和拦截器,允许其他方法注释进入接口。基本上,我的切入点查找方法的注释,直到找不到,在类或其超类实现的接口的相同方法(相同的名称和参数类型)中。
问题是:当我声明一个DefaultAdvisorAutoProxyCreator bean,它将正确应用这个切入点/拦截器时,建议没有接口的类的行为被打破。显然出现问题,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(...)
{
...
}
}
当我尝试在其他bean中 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试图“双重代理”我的类,以及为什么这会在我声明DefaultAdvisorAutoProxyCreator bean时发生。
我一直在努力解决这个问题,任何帮助或想法都会非常感激。
编辑:
这是我的拦截器源代码,如要求的那样。它基本上记录了方法执行(只有使用@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();
}
}
谢谢!
答案 0 :(得分:13)
我找到了一个使用Bozho建议的'scoped-proxy'的解决方案。
由于我几乎只使用注释,我的ServiceExecutor类现在看起来像这样:
@Component
@Scope( proxyMode = ScopedProxyMode.TARGET_CLASS )
public class ServiceExecutor
{
@Transactional
public Object execute(...)
{
...
}
}
到目前为止,一切都很好。我不知道为什么我必须明确地告诉Spring这个类应该使用CGLIB代理,因为它没有实现任何接口。也许这是一个错误,我不知道。
非常感谢,Bozho。
答案 1 :(得分:3)
这里有些东西不匹配 - 如果是$ProxyXX
,则表示有接口。确保没有界面。其他一些说明:
,您可以使用(x instanceof Advised)
检查目标对象是否已经是代理,然后您可以转换为Advised
您可以使用<aop:scoped-proxy />
定义每个bean的代理策略