2011-10-03 30 views
16

Tôi có một ứng dụng đang chạy với Spring và tôi đang sử dụng AOP ở một số nơi. Vì tôi muốn sử dụng chú giải @Transactional ở cấp độ giao diện, tôi phải cho phép Spring tạo các proxy JDK. Vì vậy, tôi không đặt thuộc tính proxy-target-class thành true. Mặt khác, tôi không muốn tạo một giao diện cho mọi lớp đơn lẻ mà tôi muốn: nếu giao diện không có ý nghĩa, tôi chỉ muốn thực hiện, và Spring nên tạo một proxy CGLIB.Trộn các proxy JDK và CGLIB trong Spring

Mọi thứ hoạt động hoàn hảo, giống như tôi đã mô tả. Nhưng tôi muốn có một số chú thích khác (do tôi tạo) đi vào các giao diện và được "kế thừa" bởi các lớp thực hiện (giống như lớp @Transactional). Hóa ra là tôi không thể làm điều đó với sự hỗ trợ tích hợp cho AOP trong Spring (ít nhất tôi không thể tìm ra cách để thực hiện nó sau một số nghiên cứu. Chú thích trong giao diện không hiển thị trong lớp thực hiện, và do đó lớp học đó không được thông báo).

Vì vậy, tôi quyết định thực hiện của riêng tôi pointcutchặn, cho phép chú thích phương pháp khác để đi trên giao diện. Về cơ bản, con trỏ của tôi tìm chú thích trên phương thức và, cho đến khi không tìm thấy, trong cùng một phương thức (cùng tên và kiểu tham số) của các giao diện mà lớp hoặc các lớp siêu thực hiện của nó.

Vấn đề là: khi tôi khai báo một hạt DefaultAdvisorAutoProxyCreator, sẽ áp dụng đúng cách điểm cắt/chặn này, hành vi của các lớp cố vấn không có giao diện bị hỏng. Rõ ràng có điều gì đó sai và Spring cố gắng ủy nhiệm các lớp của tôi hai lần, một lần với CGLIB và sau đó với JDK.

Đây là tập tin cấu hình của tôi:

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

Đây là lớp học Tôi muốn được proxy, không có giao diện:

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

Khi tôi cố gắng autowire nó trong một số đậu khác , như:

public class BaseService { 
    @Autowired 
    private ServiceExecutorImpl serviceExecutorImpl; 

    ... 
} 

Tôi nhận được ngoại lệ sau:

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

Đây là một số dòng sản lượng mùa xuân:

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 [mypackage.Servi[email protected]] 

tôi có thể cung cấp đầy đủ các đầu ra nếu ai đó nghĩ nó sẽ giúp. Tôi không biết tại sao Spring lại cố gắng "nhân đôi" lớp của tôi, và tại sao điều này chỉ xảy ra khi tôi tuyên bố bean DefaultAdvisorAutoProxyCreator.

Tôi đã phải vật lộn với điều này một thời gian, và mọi trợ giúp hoặc ý tưởng sẽ được đánh giá rất nhiều.

EDIT:

Đây là mã nguồn chặn của tôi, theo yêu cầu. Về cơ bản nó đăng nhập thực hiện phương thức (chỉ các phương thức được chú thích bằng @Trace mới bị chặn). Nếu phương thức được chú thích bằng @Trace (sai), quá trình ghi sẽ bị tạm dừng cho đến khi phương thức trả về.

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

Cảm ơn!

Trả lời

3

Nội dung nào đó không khớp ở đây - nếu có là $ProxyXX, điều đó có nghĩa là có giao diện. Đảm bảo không có giao diện. Một số lưu ý khác:

  • trong pointcut của bạn, bạn có thể kiểm tra nếu đối tượng mục tiêu đã là một proxy sử dụng (x instanceof Advised), sau đó bạn có thể cast để Advised

  • bạn có thể sử dụng <aop:scoped-proxy /> để xác định chiến lược Proxy per- bean

+0

Vâng, tôi đoán proxy CGLIB triển khai một số (các) giao diện. Nhưng tôi nghĩ điều này nằm ngoài tầm kiểm soát của tôi, đúng không? Lớp gốc, mà tôi đã viết, không thực hiện bất kỳ giao diện nào, hoặc thậm chí mở rộng bất kỳ lớp nào (ngoại trừ Object, dĩ nhiên). Tôi không biết liệu việc kiểm tra Tư vấn có thể giúp tôi hay không, vì lớp học này đang được Spring khuyên dùng (do @Transactional), và không phải do bất kỳ điểm nào tôi viết. Tôi sẽ xem để xem liệu nó có thể giúp tôi không. Cảm ơn! –

+0

bạn có thể hiển thị mã chặn không? – Bozho

+0

Tôi đã chỉnh sửa câu hỏi của mình với nguồn cho máy đánh chặn. Bất kỳ đầu mối? –

13

Tôi tìm thấy giải pháp sử dụng 'proxy phạm vi' do Bozho đề xuất.

Kể từ khi tôi đang sử dụng hầu như chỉ chú thích, lớp ServiceExecutor của tôi bây giờ trông như thế này:

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

Cho đến nay tất cả mọi thứ seens là làm việc tốt. Tôi không biết tại sao tôi phải nói rõ ràng với Spring rằng lớp này nên được ủy quyền sử dụng CGLIB, vì nó không thực hiện bất kỳ giao diện nào. Có lẽ đó là một lỗi, tôi không biết.

Cảm ơn rất nhiều, Bozho.

Các vấn đề liên quan