2011-10-03 10 views
16

Tengo una aplicación ejecutándose con Spring, y estoy usando AOP en algunos lugares. Como quiero usar la anotación @Transactional a nivel de interfaz, tengo que permitir que Spring cree proxies JDK. Por lo tanto, no configuro la propiedad proxy-target-class en verdadero. Por otro lado, no quiero crear una interfaz para cada clase que desee: si la interfaz simplemente no tiene sentido, quiero tener solo la implementación, y Spring debería crear un proxy CGLIB.Mezcla de proxies JDK y CGLIB en Spring

Todo estaba funcionando perfectamente, tal como lo describí. Pero quería tener algunas otras anotaciones (creadas por mí) en interfaces y ser "heredadas" por las clases de implementación (al igual que el @Transactional). Resulta que no puedo hacer eso con el soporte integrado para AOP en Spring (al menos no pude entender cómo hacerlo después de algunas investigaciones. La anotación en la interfaz no está visible en la clase de implementación, y por lo tanto, esa clase no se aconseja).

así que decidí aplicar mi propio punto de corte y interceptor, permitiendo que otras anotaciones camino a seguir en las interfaces. Básicamente, mi punto de corte busca la anotación en el método y, hasta que no se encuentra, en el mismo método (mismo nombre y tipos de parámetros) de las interfaces que implementa la clase o sus superclases.

El problema es que cuando declaro un bean DefaultAdvisorAutoProxyCreator, que aplicará correctamente este punto de corte/interceptor, se rompe el comportamiento de las clases de asesoramiento sin interfaces. Aparentemente, algo sale mal y Spring intenta apoderar mis clases dos veces, una vez con CGLIB y luego con JDK.

Esta es mi fichero de configuración:

<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> 

Ésta es la clase quiero ser proxy, sin interfaces:

@Component 
public class ServiceExecutorImpl 
{ 
    @Transactional 
    public Object execute(...) 
    { 
     ... 
    } 
} 

Cuando intento Autowire de alguna otra frijol , como:

public class BaseService { 
    @Autowired 
    private ServiceExecutorImpl serviceExecutorImpl; 

    ... 
} 

obtengo la siguiente excepción:

java.lang.IllegalArgumentException: Can not set mypackage.ServiceExecutorImpl field mypackage.BaseService.serviceExecutor to $Proxy26 

Esta son algunas líneas de la salida de la primavera:

13:51:12,672 [main] DEBUG [org.springframework.aop.framework.Cglib2AopProxy] - Creating CGLIB2 proxy: target source is SingletonTargetSource for target object [[email protected]] 
... 
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 [[email protected]] 

que podía suministrar la salida completa si alguien piensa que le ayudará. No tengo idea de por qué Spring está intentando "duplicar" mi clase, y por qué esto sucede cuando declaro el bean DefaultAdvisorAutoProxyCreator.

He estado luchando con esto desde hace un tiempo, y cualquier ayuda o idea sería muy apreciada.

EDIT:

Este es mi código fuente interceptor, según lo solicitado. Básicamente, registra la ejecución del método (solo se anotan los métodos anotados con @Trace). Si el método está anotado con @Trace (falso), el registro se suspende hasta que el método regrese.

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(); 
    } 
} 

¡Gracias!

Respuesta

3

Algo no concuerda aquí - si hay un $ProxyXX, significa que hay una interfaz. Asegúrate de que no haya interfaz. Algunas otras notas:

  • en su punto de corte se puede comprobar si el objeto de destino es ya un proxy utilizando (x instanceof Advised), entonces se pueden transmitir contenido a Advised

  • puede utilizar <aop:scoped-proxy /> para definir la estrategia de proxy per- Bean

+0

Bueno, supongo que los proxies CGLIB implementan algunas interfaces. Pero creo que esto está fuera de mi control, ¿verdad? La clase original, que escribí, no implementa ninguna interfaz, o incluso extiende cualquier clase (excepto Object, por supuesto). No sé si la verificación de Asesoramiento podría ayudarme, ya que esta clase está siendo recomendada por Spring (debido a @Transactional), y no por ningún punto de corte que escribí. Echaré un vistazo a para ver si me puede ayudar. ¡Gracias! –

+0

¿Puedes mostrar el código del interceptor? – Bozho

+0

Edité mi pregunta con la fuente del interceptor. ¿Cualquier pista? –

13

Encontré una solución usando el 'alcance del proxy' sugerido por Bozho.

Desde que estoy usando casi sólo anotaciones, mi clase ServiceExecutor ahora se ve así:

@Component 
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) 
public class ServiceExecutor 
{ 
    @Transactional 
    public Object execute(...) 
    { 
     ... 
    } 
} 

Hasta ahora todo seens estar funcionando bien. No sé por qué tengo que decirle explícitamente a Spring que esta clase debe ser procesada usando CGLIB, ya que no implementa ninguna interfaz. Tal vez es un error, no sé.

Muchas gracias, Bozho.