
一、AOP简介
AOP的英文全称是Aspect Oriented Programming,意为:面向切面编程。
AOP采取横向抽取的机制,取代了传统纵向继承体系的代码复用。AOP常用于:事务管理,性能监视,安全检查,缓存,日志等。
Spring AOP使用纯Java实现,在程序运行期间通过代理的方式向目标类织入增强代码。
AspectJ是一个基于Java语言的AOP框架,Spring在2.0版本引入了对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译期间提供横向代码的织入。
二、spring-aop的实现原理
spring-aop的底层采用代理机制进行实现。
如果目标类实现了接口,spring默认采用jdk的动态代理;
如果目标类没有实现接口,spring默认采用cglib字节码增强。不过,我们可以声明强制使用cglib的代理方式。
三、AOP的术语【重要】
1. target(目标类),需要被代理的类。我们将会用:UserService + UserServiceImpl作为目标类。
2. joinPoint(连接点),连接点是指那些可能被拦截到的方法。例如:目标类中的所有方法
3. pointCut(切入点),已经被增强的连接点。例如:目标类中的addUser()方法。切入点是连接点的一个子集。
4. advice(通知 / 增强),增强代码。例如:切面类中的before、after方法。
5. weaving(织入),指把通知(advice)应用到目标对象(target)来创建新的代理对象(proxy)的过程。可知,织入是一个过程。
6. proxy(代理类)
7. aspect(切面),切面是指切入点(pointcut)和通知(advice)的结合。
四、用JDK的动态代理实现AOP
JDK的动态代理是对装饰者设计模式的简化。使用的前提是目标类必须实现接口。
用JDK动态代理有三个主要模块需要实现:
1. 目标类:接口+实现类(UserService + UserServiceImpl)
2. 切面类:切面类用于存放通知(MyAspect)
3. 工厂类:工厂类用来生成代理对象
4. 测试类
下面给出一个JDK动态代理实现AOP的例子
1. 目标类(接口+实现类)
package cn.african.service; public interface UserService { public void addUser(); public void updateUser(); public void deleteUser(); } package cn.african.service; public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println("addUser..."); } @Override public void updateUser() { System.out.println("updateUser..."); } @Override public void deleteUser() { System.out.println("deleteUser..."); } }
2. 切面类
package cn.african.aspect; public class MyAspect { public void before() { System.out.println("before"); } public void after() { System.out.println("after"); } }
3. 工厂类
package cn.african.factory; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import cn.african.aspect.MyAspect; import cn.african.service.UserService; import cn.african.service.UserServiceImpl; public class MyFactoryBean { public static UserService createService() { // 1. 目标对象 UserService userService = new UserServiceImpl(); // 2. 切面对象 MyAspect myAspect = new MyAspect(); // 3. 代理对象 UserService proxyService = (UserService) Proxy.newProxyInstance( MyFactoryBean.class.getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //前置通知 myAspect.before(); //执行目标类的方法 Object object = method.invoke(userService, args); //后置通知 myAspect.after(); return object; } }); //4. 返回代理对象 return proxyService; } }
我们重点分析工厂类中代理对象的生成:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数1:loader,类加载器。因为动态代理是在运行时创建代理对象,而任何类都需要类加载器将其加载到内存,所以我们要给将来运行期间产生的这个代理类提供一个类加载器。 一般情况下,使用 当前类.class.getClassLoader(),或者 目标实例.getClass().getClassLoader();其实他们俩是一个ClassLoader。
参数2:interfaces,代理类需要实现的所有接口。有两个方式来提供这些接口:
方式1:目标实例.getClass().getInterfaces();但是这种方式只能获得自己的接口,不能获得父类的接口。
方式2:new Class[ ] { UserService.class };这种方式可以获得父类的接口
参数3:InvocationHandler,处理类,这是JDK反射包中提供的接口,里面只有一个invoke方法,代理类的每一个方法执行时,都将调用一次invoke方法。InvocationHandler需要实现,一般采用匿名内部类来实现。
我们在分析这个invoke方法,方法的格式为:public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
参数3.1:proxy,代理对象
参数3.2:method:代理对象当前要执行的方法
参数3.3:args:要执行的方法的入参
4. 测试类
package cn.african.test; import org.junit.Test; import cn.african.factory.MyFactoryBean; import cn.african.service.UserService; public class TestJdkProxy { @Test public void testJdkProxy() { UserService userService = MyFactoryBean.createService(); userService.addUser(); System.out.println("--------------"); userService.updateUser(); System.out.println("--------------"); userService.deleteUser(); } }
打印结果:

五、用CGLIB实现AOP
如果目标类没有实现接口,就无法使用JDK的动态代理来生成代理类,这时候我们采用纵向继承机制来实现目标类的代码增强,这就是cglib要完成的工作。
cglib代理是在程序运行期间,创建目标类的子类,在子类中对目标类的方法进行增强。
cglib有两个jar包依赖:cglib.jar和asm.jar,但这两个jar都已经被整合在spring-core.jar中了。
下面我们手动实现一个cglib代理:
1. 目标类
package cn.african.service; public class UserServiceImpl { public void addUser() { System.out.println("addUser..."); } public void updateUser() { System.out.println("updateUser..."); } public void deleteUser() { System.out.println("deleteUser..."); } }
2. 切面类
package cn.african.aspect; public class MyAspect { public void before() { System.out.println("before"); } public void after() { System.out.println("after"); } }
3. 工厂类
采用cglib,底层将创建目标类的子类
package cn.african.factory; import java.lang.reflect.Method; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import cn.african.aspect.MyAspect; import cn.african.service.UserService; import cn.african.service.UserServiceImpl; public class CglibBeanFactory { public static UserService createService() { // 目标类 UserServiceImpl userService = new UserServiceImpl(); // 切面类 MyAspect myAspect = new MyAspect(); // 1. 核心类 Enhancer enhancer = new Enhancer(); // 2. 设置父类 enhancer.setSuperclass(UserServiceImpl.class); // 3. 设置回调函数 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { myAspect.before(); Object object = method.invoke(userService, args); myAspect.after(); return object; } }); // 4. 创建代理 UserServiceImpl proxyService = (UserServiceImpl) enhancer.create(); return proxyService; } }
我们解释一下这个回调函数:
MethodInterceptor接口类似于JDK中的InvocationHandler接口,而intercept方法类似于InvocationHandler接口中的invoke方法。
前三个参数和invoke方法中的三个参数是一样的。
第四个参数methodProxy是方法代理,可以通过它执行代理类的父类,也就是执行目标类。
Object object = method.invoke(userService, args) 可以替换成 methodProxy.invokeSuper(proxy, args),两个效果是一样的。
4. 测试类
package cn.african.test; import org.junit.Test; import cn.african.factory.CglibBeanFactory; import cn.african.service.UserService; public class TestCglibProxy { @Test public void demo01() { UserService userService = CglibBeanFactory.createService(); userService.addUser(); System.out.println("--------------"); userService.updateUser(); System.out.println("--------------"); userService.deleteUser(); } }
打印结果:

六、半自动代理实现AOP
我们之所以称之为半自动代理,是因为Spring自动创建了代理对象,但是却需要我们手动去容器中获取这个代理对象。
在实现这个半自动的demo之前,我们首先介绍一下aop联盟(aopalliance)定义的一套通知(advice)接口规范。
音乐博士Rod Johnson童鞋在aop联盟的规范里定义了一个空的通知接口Advice:
package org.aopalliance.aop; /** * Tag interface for Advice. Implementations can be any type * of advice, such as Interceptors. * @author Rod Johnson * @version $Id: Advice.java,v 1.1 2004/03/19 17:02:16 johnsonr Exp $ */ public interface Advice {}
Spring按照通知在目标类方法上连接点的位置,把通知又分为以下五大接口规范:
1. 前置通知:org.springframework.aop.MethodBeforeAdvice
2. 后置通知:org.springframework.aop.AfterReturningAdvice
3. 环绕通知:org.aopalliance.intercept.MethodInterceptor
注意环绕通知并不是定义在spring中,而是在aop联盟的规范中。同时也要注意在cglib代理中设置回调函数的时候new了一个org.springframework.cglib.proxy.MethodInterceptor的匿名实现,这是两个不同的MethodInterceptor,注意区分。环绕通知是我们的重点。
4. 异常抛出通知:org.springframework.aop.ThrowsAdvice
5. 引介通知:org.springframework.aop.IntroductionInterceptor
引介通知用来在目标类中添加一些新的属性和方法,但是很少用。
我们先模拟一下环绕通知的流程。注意,环绕通知必须手动执行目标方法。
try{ //1. 前置通知 //2. 执行目标方法 //3. 后置通知 }catch{ //4. 抛出异常通知 }
1,2,3如果有任何一处抛出异常,都会停止后面代码的执行,进入catch块。
所以,如果在前置通知中抛出异常,可以阻止目标方法的执行。
后置通知是在目标方法执行后执行,所以它可以获得目标方法的返回值。
下面我们来实现一个半自动代理的demo:
1. 目标类
package cn.african.service; public interface UserService { void addUser(); void updateUser(); void deleteUser(); } package cn.african.service; public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println("addUser..."); } @Override public void updateUser() { System.out.println("updateUser..."); } @Override public void deleteUser() { System.out.println("deleteUser..."); } }
2. 切面类
切面类中装有通知,我们环绕通知为例,所以这个类需要实现环绕通知的接口org.aopalliance.intercept.MethodInterceptor。同时要注意到,环绕通知必须手动执行目标方法。
package cn.african.aspect; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class MyAspect implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { // 前置通知 System.out.println("before"); // 手动执行目标方法,这是一个放行方法 Object object = invocation.proceed(); // 后置通知 System.out.println("after"); return object; } }
3. bean-aop.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"> <!-- 1. 目标类 --> <bean id="userService" class="cn.african.service.UserServiceImpl"></bean> <!-- 2. 切面类 --> <bean id="myAspect" class="cn.african.aspect.MyAspect"></bean> <!-- 3. 代理类 --> <bean id="proxyService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="interfaces" value="cn.african.service.UserService"></property> <property name="target" ref="userService"></property> <property name="interceptorNames" value="myAspect"></property> <!-- <property name="optimize" value="true"></property> --> <!--如果声明optimize=true,无论是否有接口,都采用cglib代理--> </bean> </beans>
我们分下一下代理类的配置:
org.springframework.aop.framework.ProxyFactoryBean是FactoryBean的一个实现,它是一个代理类的工厂Bean,专门用来生成代理对象,底层会调用ProxyFactoryBean中的getObject()方法来返回这个代理对象。
interfaces:确定接口
target:确定目标类
interceptorNames:切面类实例的Bean ID,但是这里使用value,却没有使用ref,这是因为setInterceptorNames接收的参数是个String的可可变参数,本质上是一个String[ ]。
optimize:当值为true时,强制使用cglib代理;默认值为false。
4. 测试类
package cn.african.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.african.service.UserService; public class TestAop { @Test public void demo01() throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext("beans-aop.xml"); UserService userService = context.getBean("proxyService", UserService.class); userService.addUser(); System.out.println("----------------"); userService.updateUser(); System.out.println("----------------"); userService.deleteUser(); context.getClass().getMethod("close").invoke(context); } }
打印结果:
七、Spring AOP编程
从Spring容器中获取目标对象,如果配置了AOP,Spring将自动生成代理对象并返回。
想要确定目标类,得使用AspectJ的切入点表达式。关于AspectJ我们将在另一篇文章中详细介绍。
下面是一个AOP编程的demo:
1. 目标类
package cn.african.service; public interface UserService { public void addUser(); public void updateUser(); public void deleteUser(); } package cn.african.service; public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println("addUser..."); } @Override public void updateUser() { System.out.println("updateUser..."); } @Override public void deleteUser() { System.out.println("deleteUser..."); } }
2. 切面类
我们使用环绕通知,环绕通知要手动执行目标方法。
package cn.african.aspect; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class MyAspect implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { // 前置通知 System.out.println("before"); // 手动执行目标方法,这是一个放行方法 Object object = invocation.proceed(); // 后置通知 System.out.println("after"); return object; } }
3. beans-aop.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 1. 目标类 --> <bean id="userService" class="cn.african.service.UserServiceImpl"></bean> <!-- 2. 切面类 --> <bean id="myAspect" class="cn.african.aspect.MyAspect"></bean> <!-- 3. AOP配置 --> <aop:config> <aop:pointcut expression="execution(* cn.african.service.UserServiceImpl.*(..))" id="myPointcut"/> <aop:advisor advice-ref="myAspect" pointcut-ref="myPointcut"/> </aop:config> </beans>
下面解释一下这段AOP的配置:
1. 使用<aop:config>必须要导入aop的命名空间。
2. aop编程就是在<aop:config>之间进行配置:
<aop:pointcut>:切入点,从目标对象中获取具体的方法
<aop:advisor>:这个标签描述一个特殊的切面,这个特殊的切面是一个通知和一个切入点的结合。
advice-ref:通知的引用
pointcut-ref:切入点的引用
注意advisor和advice的区别!!!
3. 切入点表达式
execution(* cn.african.service.UserServiceImpl.*(..))
execution:按照官方的解释是: for matching method execution join points, this is the primary pointcut designator(指示符) you will use when working with Spring AOP。
execution( * cn.african.service. UserServiceImpl. * (..) )
返回类型任意 包名 类名 方法任意 参数任意
4. 测试类
package cn.african.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.african.service.UserService; public class TestAop { @Test public void demo01() throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext("beans-aop.xml"); UserService userService = context.getBean("userService", UserService.class); userService.addUser(); System.out.println("----------------"); userService.updateUser(); System.out.println("----------------"); userService.deleteUser(); context.getClass().getMethod("close").invoke(context); } }
打印结果:

