2011-10-03 10 views
16

Ho un'applicazione in esecuzione con Spring e sto utilizzando AOP in alcuni punti. Dal momento che voglio utilizzare l'annotazione @Transactional a livello di interfaccia, devo consentire a Spring di creare proxy JDK. Pertanto, non imposto la proprietà proxy-target-target su true. D'altra parte, non voglio creare un'interfaccia per ogni singola classe che desidero: se l'interfaccia non ha senso, voglio avere solo l'implementazione, e Spring dovrebbe creare un proxy CGLIB.Miscelazione di proxy JDK e CGLIB entro la primavera

Tutto funzionava perfettamente, proprio come ho descritto. Ma volevo avere alcune altre annotazioni (create da me) che entrano nelle interfacce e che sono "ereditate" dalle classi di implementazione (proprio come la @Transactional). Risulta che non posso farlo con il supporto integrato per AOP in primavera (almeno non riuscivo a capire come farlo dopo qualche ricerca. L'annotazione nell'interfaccia non è visibile nella classe di implementazione, e quindi quella classe non viene avvisata).

così ho deciso di implementare il mio pointcut e intercettore, consentendo altre annotazioni metodo per farlo andare su interfacce. Fondamentalmente, il mio pointcut cerca l'annotazione sul metodo e, fino a quando non viene trovato, nello stesso metodo (stesso nome e tipi di parametri) delle interfacce implementate dalla classe o dalle sue superclassi.

Il problema è: quando dichiaro un bean DefaultAdvisorAutoProxyCreator, che applicherà correttamente questo pointcut/interceptor, il comportamento di consigliare le classi senza interfacce è interrotto. Apparentemente qualcosa va storto e Spring prova a proxyare le mie classi due volte, una volta con CGLIB e poi con JDK.

Questo è il mio file di configurazione:

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

Questa è la classe che voglio essere proxy, senza interfacce:

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

Quando provo a autowire in qualche altro fagioli , ad esempio:

public class BaseService { 
    @Autowired 
    private ServiceExecutorImpl serviceExecutorImpl; 

    ... 
} 

Ottengo la seguente eccezione:

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

Questo sono alcune linee di uscita Primavera:

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

ho potuto fornire l'output completo se qualcuno pensa che sarà di aiuto. Non ho idea del perché Spring stia provando a "double-proxy" della mia classe, e perché questo accade solo quando dichiaro il bean DefaultAdvisorAutoProxyCreator.

Sono stato alle prese con questo per un po 'di tempo, e qualsiasi aiuto o idee sarebbe molto apprezzato.

EDIT:

Questo è il mio codice sorgente intercettore, come richiesto. Fondamentalmente registra l'esecuzione del metodo (solo i metodi annotati con @Trace vengono intercettati). Se il metodo è annotato con @Trace (false), la registrazione viene sospesa fino al ritorno del metodo.

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

Grazie!

risposta

3

Qualcosa non corrisponde a questo - se c'è un $ProxyXX, significa che c'è un'interfaccia. Assicurati che non ci sia un'interfaccia. Alcune altre note:

  • nel vostro pointcut è possibile verificare se l'oggetto di destinazione è già un proxy utilizzando (x instanceof Advised), allora si può lanciare per Advised

  • è possibile utilizzare <aop:scoped-proxy /> per definire la strategia di delega per- fagiolo

+0

Bene, immagino che i proxy CGLIB implementino alcune interfacce. Ma penso che questo sia fuori dal mio controllo, giusto? La classe originale, che ho scritto, non implementa alcuna interfaccia, né estende alcuna classe (ad eccezione dell'Oggetto, ovviamente). Non so se il controllo di Advised potrebbe aiutarmi, dal momento che questa classe è stata consigliata da Spring (a causa di @Transactional), e non da alcun punto di vista che ho scritto. Dare un'occhiata a per vedere se può aiutarmi. Grazie! –

+0

puoi mostrare il codice dell'intercettore? – Bozho

+0

Ho modificato la mia domanda con la fonte per l'intercettore. Qualche indizio? –

13

Ho trovato una soluzione utilizzando il 'scoped-proxy' suggerito da Bozho.

Dal momento che sto usando quasi solo le annotazioni, la mia classe ServiceExecutor ora assomiglia a questo:

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

Fino ad ora tutto seens funzionare bene. Non so perché devo dire esplicitamente a Spring che questa classe dovrebbe essere trasmessa in proxy usando CGLIB, poiché non implementa alcuna interfaccia. Forse è un bug, non lo so.

Grazie mille, Bozho.

Problemi correlati