Spring AOP示例與實現原理總結——傳統spring aop、基於切面注入、基於@Aspect注解的實現


一、代碼實踐

1)經典的Spring Aop

經典的spring aop,是基於動態代理技術的。實現方式上,最常用的是實現MethodInterceptor接口來提供環繞通知,創建若干代理,然后使用ProxyBeanFactory配置工廠bean,生成攔截器鏈,完成攔截。示例如下:

 1 package demo.spring;
 2 
 3 import org.aopalliance.intercept.MethodInterceptor;
 4 import org.aopalliance.intercept.MethodInvocation;
 5 import org.junit.Test;
 6 import org.junit.runner.RunWith;
 7 import org.springframework.beans.factory.annotation.Autowired;
 8 import org.springframework.test.context.ContextConfiguration;
 9 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10 
11 @RunWith(SpringJUnit4ClassRunner.class)
12 @ContextConfiguration("classpath:spring-config.xml")
13 public class TraditionalSpringAopDemo {
14     @Autowired
15     private Service proxy;
16 
17     @Test
18     public void test() {
19         proxy.execute("hello world!");
20     }
21 }
22 
23 interface Service {
24     void execute(String str);
25 }
26 
27 class ServiceImpl implements Service {
28     @Override
29     public void execute(String str) {
30         System.out.println("execute invoke: " + str);
31     }
32 }
33 
34 class Interceptor1 implements MethodInterceptor {
35     @Override
36     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
37         System.out.println("interceptor1,before invoke");
38         Object ret = methodInvocation.proceed();
39         System.out.println("interceptor1,after invoke");
40         return ret;
41     }
42 }
43 
44 class Interceptor2 implements MethodInterceptor {
45     @Override
46     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
47         System.out.println("interceptor2,before invoke");
48         Object ret = methodInvocation.proceed();
49         System.out.println("interceptor2,after invoke");
50         return ret;
51     }
52 }

 

xml文件配置:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xmlns:context="http://www.springframework.org/schema/context"
 5        xmlns:aop="http://www.springframework.org/schema/aop"
 6        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
 7 
 8     <context:component-scan base-package="demo.spring"/>
 9 
10     <bean class="demo.spring.ServiceImpl" id="service"></bean>
11     <bean class="demo.spring.Interceptor1" id="interceptor1"></bean>
12     <bean class="demo.spring.Interceptor2" id="interceptor2"></bean>
13     <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxy">
14         <property name="target" ref="service"/>
15         <property name="interceptorNames">
16             <list>
17                 <value>interceptor1</value>
18                 <value>interceptor2</value>
19             </list>
20         </property>
21     </bean>
22 </beans>

 

 結果:

interceptor1,before invoke
interceptor2,before invoke
execute invoke: hello world!
interceptor2,after invoke
interceptor1,after invoke

 

可以看到攔截鏈的執行過程與攔截器順序的關系。

 

2)spring中的聲明式aop

上述經典的spring aop,編碼起來十分繁瑣,spring框架中已經提供了新的方式,更簡潔地完成切面的配置與使用。spring在aop命名空間中,提供了切面、切點、通知簡潔的聲明方式,使得spring aop更加易於配置和使用。另外,還提供了與之配套的切面系列注解。Spring AOP的demo,見下面的小例子。

* 首先是利用<aop:config>元素,聲明切面的方式:

 1 package demo.spring;
 2 
 3 import org.aspectj.lang.ProceedingJoinPoint;
 4 import org.junit.Test;
 5 import org.junit.runner.RunWith;
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.test.context.ContextConfiguration;
 8 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 9 
10 @RunWith(SpringJUnit4ClassRunner.class)
11 @ContextConfiguration("classpath:spring-config.xml")
12 public class AopDemo {
13     @Autowired
14     private Target target;
15 
16     @Test
17     public void testSayHello() {
18         target.doSomething("hello world");
19     }
20 }
21 
22 class Target {
23 
24     public void doSomething(String params) {
25         System.out.println(params);
26     }
27 }
28 
29 class Interceptor3 {
30 
31     public void invoke(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
32         System.out.println("interceptor3, before invoke");
33         proceedingJoinPoint.proceed();
34         System.out.println("interceptor3, after invoke");
35     }
36 }
37 
38 class Interceptor4 {
39 
40     public void invoke(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
41         System.out.println("interceptor4, before invoke");
42         proceedingJoinPoint.proceed();
43         System.out.println("interceptor4, after invoke");
44     }
45 }

 

 

相應xml配置:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xmlns:context="http://www.springframework.org/schema/context"
 5        xmlns:aop="http://www.springframework.org/schema/aop"
 6        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
 7 
 8     <context:component-scan base-package="demo.spring"/>
 9 
10     <bean class="demo.spring.Interceptor3" id="interceptor3"/>
11     <bean class="demo.spring.Interceptor4" id="interceptor4"/>
12 
13     <bean class="demo.spring.Target" id="target"/>
14     <aop:config>
15         <aop:pointcut id="pointcut" expression="execution(* demo.spring.Target.doSomething(String))" />
16         <aop:aspect ref="interceptor3">
17             <aop:around pointcut-ref="pointcut" method="invoke"/>
18         </aop:aspect>
19         <aop:aspect ref="interceptor4">
20             <aop:around pointcut-ref="pointcut" method="invoke"/>
21         </aop:aspect>
22     </aop:config>
23 
24 </beans>

 

3)@Aspect注解

下面是不使用<aop:config>,而是使用更加簡潔的@Aspect注解,完成切面的生成與配置:

 1 package demo.spring;
 2 
 3 import org.aspectj.lang.ProceedingJoinPoint;
 4 import org.aspectj.lang.annotation.Around;
 5 import org.aspectj.lang.annotation.Aspect;
 6 import org.junit.Test;
 7 import org.junit.runner.RunWith;
 8 import org.springframework.beans.factory.annotation.Autowired;
 9 import org.springframework.stereotype.Component;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12 
13 @RunWith(SpringJUnit4ClassRunner.class)
14 @ContextConfiguration("classpath:spring-config.xml")
15 public class AopDemo {
16     @Autowired
17     private Target target;
18 
19     @Test
20     public void testSayHello() {
21         target.doSomething("hello world");
22     }
23 }
24 
25 @Component
26 class Target {
27 
28     public void doSomething(String params) {
29         System.out.println(params);
30     }
31 }
32 
33 @Aspect
34 @Component
35 class Interceptor5 {
36 
37     @Around("execution(* demo.spring.Target.doSomething(String))")
38     public void invoke(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
39         System.out.println("interceptor5, before invoke");
40         proceedingJoinPoint.proceed();
41         System.out.println("interceptor5, after invoke");
42     }
43 }

 

spring-config.xml文件配置如下:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xmlns:context="http://www.springframework.org/schema/context"
 5        xmlns:aop="http://www.springframework.org/schema/aop"
 6        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
 7
 8     <context:component-scan base-package="demo.spring"/>
 9     <aop:aspectj-autoproxy />
10 
11 </beans>

結果:

 
         

interceptor5, before invoke
hello world
interceptor5, after invoke

 

 

 

值得說明的點:

* context:component-scan,使用@Component自動發布bean,需要配置這個元素;

* aop:aspectj-autoproxy,使用@AspectJ及其它AOP注解需要配置,否則無法使用注解;@AspectJ注解,將@Component自動發布出來的"interceptor" bean轉換為一個aspectj切面,而@Pointcut、@Before、@After、@Around等注解,功能與在xml文件中配置是一樣的;@Pointcut注解下面的方法內容無意義,只是要求一個相應方法提供注解依附。

* 注解只能在使用能獲得源碼的場景,如果不能獲取源碼,則只能通過xml配置的形式,將指定的對象配置成攔截器,對指定的目標進行攔截;因此,通過xml文件配置,而不是注解,是更加通用的方式。

* 除基礎的springframework框架的jar包外,還需要依賴cglib、aspectj的jar包,maven配置: 

 1         <dependency>
 2             <groupId>cglib</groupId>
 3             <artifactId>cglib</artifactId>
 4             <version>2.2</version>
 5         </dependency>
 6         <dependency>
 7             <groupId>org.aspectj</groupId>
 8             <artifactId>aspectjweaver</artifactId>
 9             <version>1.6.11</version>
10         </dependency>

 

二、框架實現原理

Spring框架中的AOP攔截技術,是POJO的方法層面的攔截。其底層實現原理,是動態代理技術。對於面向接口的方法攔截,依賴於jdk的動態代理技術,即java.lang.reflect.Proxy#newProxyInstance,將對被代理的目標對象的調用,委托到代理對象,觸發攔截通知;而當被攔截的方法, 不是在接口中定義時,使用的是cglib,對字節碼進行動態增強,生成被代理類的子對象,以實現代理。下面是兩種代理技術實現原理描述的demo:

 1 package demo.spring;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 import java.lang.reflect.Proxy;
 6 
 7 import org.springframework.cglib.proxy.Enhancer;
 8 
 9 /**
10  * jdk動態代理實現aop攔截
11  * @author jacksonshi
12  * @version $Id: DynamicProxy.java, v 0.1 16/9/11, 11:02 jacksonshi Exp $
13  */
14 public class AopProxy {
15 
16     public static Object createProxyByJdkDynamicProxy(final Object target) {
17         return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass()
18             .getInterfaces(), new InvocationHandler() {
19             @Override
20             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
21                 before(proxy, method, args);
22                 Object ret = method.invoke(target, args);
23                 after(proxy, method, args);
24                 return ret;
25             }
26         });
27     }
28 
29     public static <T> T createProxyByCglib(final T target) {
30         Enhancer enhancer = new Enhancer();
31         enhancer.setClassLoader(AopProxy.class.getClassLoader());
32         enhancer.setSuperclass(target.getClass());
33         enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() {
34             @Override
35             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
36                 before(proxy, method, args);
37                 Object ret = method.invoke(target, args);
38                 after(proxy, method, args);
39                 return ret;
40             }
41         });
42         return (T)enhancer.create();
43     }
44 
45     private static void before(Object proxy, Method method, Object[] args) {
46         System.out.println("do something before: " + method.getName());
47     }
48 
49     private static void after(Object proxy, Method method, Object[] args) {
50         System.out.println("do something after: " + method.getName());
51     }
52 
53     public static void main(String[] args) {
54         ITarget t = new TargetImpl();
55         ITarget proxy = (ITarget) createProxyByJdkDynamicProxy(t);
56         proxy.sayHello();
57 
58         TargetImpl cglibProxy = (TargetImpl) createProxyByCglib(t);
59         cglibProxy.sayHello();
60         cglibProxy.sayHello2();
61     }
62 
63 }
64 
65 interface ITarget {
66     void sayHello();
67 }
68 
69 class TargetImpl implements ITarget {
70     @Override
71     public void sayHello() {
72         System.out.println("hello");
73     }
74 
75     public void sayHello2() {
76         System.out.println("hello 2!");
77     }
78 }
createProxyByJdkDynamicProxy()方法,利用jdk的動態代理技術,對TargetImpl#sayHello()進行代理,生成的代理對象是ITarget接口的一個實例,其只有sayHello()接口是可見的,因此也只能攔截sayHello();
createProxyByCglib()方法,利用cglib庫,對TargetImpl的兩個方法均可進行代理,無論是否是接口中定義的方法;

 在spring框架中,動態代理的策略是如果被攔截的方法,是接口中定義的方法,以jdk動態代理生成代理對象,實現通知;否則,使用cglib進行生成子類代理實例,實現通知;事實上,無論是否在接口中定義的方法,均可使用cglib生成動態代理對象,完成攔截和通知,是更通用的方式,但由於jdk動態代理的性能更佳,因此spring框架中優先選擇jdk動態代理技術。

總結:

* spring實現aop,動態代理技術的兩種實現是jdk動態代理、cglib代理,根據被通知的方法是否為接口方法,來選擇使用哪種代理生成策略

* jdk動態代理,原理是實現接口的實例,攔截定義於接口中的目標方法,性能更優,是spring生成代理的優先選擇

* cglib代理,原理是使用cglib庫中的字節碼動態生成技術,生成被代理類的子類實例,可以攔截代理類中的任一public方法的調用,無論目標方法是否定義於接口中,更通用,但性能相對jdk代理差一些;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM