2011-10-03 12 views
16

Mam aplikację uruchomioną ze Springem i używam AOP w niektórych miejscach. Ponieważ chcę używać adnotacji @Transactional na poziomie interfejsu, muszę zezwolić Springowi na tworzenie serwerów proxy JDK. Tak więc nie ustawiam wartości właściwości proxy-target-class-class na true. Z drugiej strony, nie chcę tworzyć interfejsu dla każdej klasy, którą chcę doradzić: jeśli interfejs po prostu nie ma sensu, chcę mieć tylko implementację, a Spring powinien utworzyć proxy CGLIB.Mieszanie proxy JDK i CGLIB na wiosnę

Wszystko działało idealnie, tak jak to opisałem. Ale chciałem mieć inne adnotacje (stworzone przeze mnie) wchodzące w interfejsy i będące "odziedziczone" przez klasy implementacji (tak jak @ Transactional). Okazuje się, że nie mogę tego zrobić z wbudowaną obsługą AOP na wiosnę (przynajmniej nie mogłem tego po wykonaniu niektórych badań zrobić. Adnotacja w interfejsie nie jest widoczna w klasie implementacji, i stąd ta klasa nie zostanie powiadomiona).

Więc postanowiłem realizować własne punktu przekroju i przechwytywania, pozwalając inne adnotacje metoda, aby przejść na interfejsach. Zasadniczo mój lookcut szuka adnotacji w metodzie i, dopóki nie zostanie znaleziony, w tej samej metodzie (tej samej nazwie i typach parametrów) interfejsów, które implementuje klasa lub jej nadklasy.

Problem polega na tym, że kiedy deklaruję bean bean DefaultAdvisorAutoProxyCreator, który poprawnie zastosuje ten punkt/przechwytujący, zachowanie klas doradzających bez interfejsów jest zepsute. Najwyraźniej coś poszło nie tak, a Spring próbuje serwerować moje klasy dwukrotnie, raz z CGLIB, a potem z JDK.

To jest mój plik konfiguracyjny:

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

To jest klasa chcę być proxy, bez żadnych interfejsów:

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

Kiedy próbuję autowire to w jakiś inny fasoli , jak:

public class BaseService { 
    @Autowired 
    private ServiceExecutorImpl serviceExecutorImpl; 

    ... 
} 

Otrzymuję następujący wyjątek:

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

To są niektóre linie wyjścia wiosny:

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

mogę dostarczyć pełną moc, jeśli ktoś uważa, że ​​to pomoże. Nie mam pojęcia, dlaczego Spring próbuje "podwójnie proxy" mojej klasy i dlaczego tak się dzieje, gdy deklaruję bean bean DefaultAdvisorAutoProxyCreator.

Od pewnego czasu borykam się z tym problemem, a wszelka pomoc lub pomysły będą bardzo cenne.

EDIT:

To jest mój kod źródłowy przechwytujących, zgodnie z wnioskiem. Zasadniczo rejestruje on wykonanie metody (tylko metody z adnotacją @Trace zostają przechwycone). Jeśli metoda jest opatrzona adnotacją @Trace (fałsz), rejestrowanie jest zawieszane do czasu powrotu metody.

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

Dzięki!

Odpowiedz

3

Coś tu nie pasuje - jeśli jest to $ProxyXX, oznacza to, że istnieje interfejs. Upewnij się, że nie ma interfejsu. Niektóre inne uwagi:

  • w swojej punktu przekroju, można sprawdzić, czy obiekt docelowy jest już proxy przy użyciu (x instanceof Advised), to można oddać do Advised

  • można użyć <aop:scoped-proxy /> do określenia strategii proxy per- fasola

+0

Cóż, domyślam się, że proxy CGLIB implementują niektóre interfejsy. Ale myślę, że to jest poza moją kontrolą, prawda? Oryginalna klasa, którą napisałem, nie implementuje żadnego interfejsu, ani nawet nie rozszerza żadnej klasy (poza Object, oczywiście). Nie wiem, czy sprawdzenie Doradztwa mogłoby mi pomóc, ponieważ ta klasa jest doradzana przez Spring (z powodu @Transactional), a nie przez żaden punkt, który napisałem. Zajrzę do , aby sprawdzić, czy to mi pomoże. Dzięki! –

+0

możesz pokazać kod przechwytywania? – Bozho

+0

Edytowałem moje pytanie ze źródłem dla przechwytywacza. Jakaś wskazówka? –

13

Znalazłem rozwiązanie za pomocą "scoped-proxy" sugerowanego przez Bozho.

Ponieważ używam prawie wyłącznie adnotacje, moja klasa ServiceExecutor teraz wygląda tak:

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

Do tej pory wszystko seens być działa poprawnie. Nie wiem, dlaczego muszę wyraźnie powiedzieć Springowi, że ta klasa powinna być proxy przy użyciu CGLIB, ponieważ nie implementuje żadnego interfejsu. Może to błąd, nie wiem.

Wielkie dzięki, Bozho.