• 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代理差一些;

  • 相关阅读:
    代码规范 for node.js with 'npm-coding-style'
    转:HL7 Tools suite
    转 HL7 2.x
    创建公共配置表,并以全国区县代码维护为例
    转 MySQL 数据备份与还原
    [转]''\=DevExpress 中 汉化包 汉化方法
    MSI Error 1603 installing AppFabric 1.1 / Win7 x64
    [转]如何修改远程桌面默认端口号
    【转】windows server 2012清除并重建SID
    oneM2M标准发展神速 实现万物联网的愿景
  • 原文地址:https://www.cnblogs.com/jacksonshi/p/5863313.html
Copyright © 2020-2023  润新知