一、AOP简介
1.什么是AOP?
(1)AOP的全称是Aspect-Oriented Programming,即面向切面编程,是OOP思想的一种补充,目前已经是一种比较成熟的编程方式。传统的OOP可以通过组合或者继承的方式来达到代码的重用,但是如果要实现某个功能,同样的代码会分散到各个方法中去,这样想关闭某个功能,或者想对其进行修改,就必须修改所有的相关的方法。
(2)AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或者运行的时候,再将这些提取出来的代码应用到需要执行的地方。
目前最流行的AOP框架有两个,分别是Spring AOP和AspectJ。Spring AOP使用纯java实现,不需要专门的编译过程和类加载器,在运行期间通过代理的方式向目标织入增强代码,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译的时候提供了横向代码的织入。
2.AOP术语
(1)Aspect(切面):在实际应用过程中,切面是指封装的用于横向插入下同功能(如事务、日志等)的类,如上图中的Aspect,该类要被Spring容器识别为切面,需要在配置文件中通过配置文件进行指定。
(2)Jointpount(连接点):
在程序的执行过程中的某个点,实际上是对象的一个操作,例如方法的调用或者异常的抛出,而在Spring AOP中,连接点就是指方法的调用。
(3)PointCut(切入点):即切面与程序流程的交叉点,即那些需要处理的连接点,如下图所示,切入点是指类或者方法名,如某个通知需要用到以add开头的方法,那么所有满足这一规则的都是切入点。
(4)Advice(通知/增强处理):AOP在特定的切入点执行增强处理,即定义好的切入点处所要执行的代码,可以将其理解为切面类中的方法,它是切面的具体实现。
(5)Target Object(目标对象):指所有被通知的对象,也叫做被增强的对象。如果AOP框架采用的是动态AOP实现,那么该对象就是一个被代理的对象。
(6)Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
(7)Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。
二、动态代理
0.AOP中的代理是由AOP框架动态生成的一个对象,该对象可以作为目标对象使用,Spring中AOP代理可以是JDK动态代理,也可以是CGLIB代理。
1.JDK动态代理。
(1)JDK动态代理是通过java.lang.reflect.Proxy类来实现的,可以通过Proxy类的neProxyInstance()方法创建代理对象,对于使用业务接口的类,Spring会默认使用JDK动态代理实现AOP。
(2)动态代理的实现代码:
//UserDao类 public interface UserDao { /** * */ public void addUser(); public void deleteUser(); } //UserDao实现类UserDaoImpl
//
/** * 目标类,本案例中会将类UserDaoImpl做为一个目标类,对其方法进行增强处理。 */ public class UserDaoImpl implements UserDao { public void addUser() { System.out.println("添加用户"); } public void deleteUser() { System.out.println("删除用户"); } }
/** * 切面类,可以存在多个通知Advice(即增强的方法)
在该类中模拟一个权限检查方法和模拟一个日志记录的方法,这两个方法就表示切面中的通知 */ public class MyAscpect { //下面这两个方法表示切面中的通知 public void check_Permissions(){ System.out.println("模拟检查权限。。。。。"); } public void log(){ System.out.println("模拟记录日志"); } }import com.cisco.aspect.MyAscpect;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 代理类 * 该类需要实现InvocationHandler接口,并编写代理方法,并实现了接口中的invoke()方法,所有动态代理所调用的方法都交由该方法处理, * 在创建代理方法createProxy()中,使用Proxy类的newProxyInstance()方法来创建代理对象。
newProxyInstance()方法包含3个参数,其中第一个参数表示当前类的加载器,第二个参数表示被代理对象实现的所有接口,
第三个参数this表示代理类jdkProxy本身,在invoke()方法中,目标方法执行的前后,
会分别执行切面类的check_Permission()方法和log()方法
Proxy.newProxyInstance(classLoader,clazz,this);
*/ public class JdkProxy implements InvocationHandler { //1.声明目标类接口 private UserDao userDao; //2.创建代理方法 public Object createProxy(UserDao userDao){ this.userDao=userDao; //3.类加载器 ClassLoader classLoader=JdkProxy.class.getClassLoader(); //4.被代理对象实现的所有接口 Class[] clazz = userDao.getClass().getInterfaces(); //5.使用代理类进行增强,返回的是代理后的对象 return Proxy.newProxyInstance(classLoader,clazz,this); } /** * 所有动态代理类的方法的调用,都会交由Invoke()方法进行处理, * @param proxy 被代理后的对象 * @param method 将要被执行的方法信息(反射) * @param args 执行方法时候所需要的参数 * @return * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //6声明切面 MyAscpect myAscpect = new MyAscpect(); //7.前增强 myAscpect.check_Permissions(); //8.在目标类上调用方法,并传入参数 Object obj=method.invoke(userDao,args); //9.后增强 myAscpect.log(); return obj; } }
//最后是测试类
package com.cisco.jdk; public class JdkTest { public static void main(String[] args){ //1.创建代理对象 JdkProxy jdkProxy = new JdkProxy(); //2.创建目标对象 UserDao userDao = new UserDaoImpl(); //3.从代理对象中获取增强后的目标对象 UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao); //4.执行方法 userDao1.addUser(); userDao1.deleteUser(); } }
三、基于代理类的AOP实现
1.Spring的通知类型,Spring中的通知按照在目标中类方法的连接点位置,可以分为以下5种类型
(1)环绕通知(org.aopalliance.intercept.MethodInterceptor):在目标方法执行前后实施增强,可以用于日志和事务管理等功能。
(2)前置通知(org.springframework.aop.MethodBeforeAdvice):在目标方法执行前实施增强,可以用于权限管理等功能。
(3)后置通知(org.springframework.aop.AfterAdvice):在目标方法执行之后实施增强,可用于关闭流、上传文件、删除临时文件等功能。
(4)异常通知(org.springframework.aop.ThrosAdvice):在方法抛出异常后实施增强,可以用以处理异常激烈日志能等功能。
(5)引介通知(org.springframework.aop.IntroductionInterceptor):在目标类中添加一些新的方法和属性,可用于修改老版本程序。(增强类)
2.ProxyFactoryBean
(1)ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactoryBean负责为其他Bean创建代理实例。在Spring中,使用ProxyFactoryBean时创建AOP代理的基本方式。
(2)ProxyFactoryBean的常用可配置属性:
target:代理的目标对象;ProxyInterface:代理实现的接口,如果是多个接口,可使用下面的各式<list><value></value></list>
(3)ProxyFactoryBean典型的环绕通知案例:
首先创建切面类MyAspect类,由于实现环绕通知需要实现MethodInterceptor接口,所以MyAspect类需要试下该接口,并试下接口中的invoke方法
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 环绕通知需要实现MethodInterceptor接口 * 这里为了演示效果,在目标方法前后分别执行了检查权限和记录日志的方法,这两个方法也就是增强的方法,也就是通知。 */ public class MyAspect implements MethodInterceptor { public Object invoke(MethodInvocation mi) throws Throwable { check_Permissons(); //执行目标方法 Object obj = mi.proceed(); log(); return obj; } public void check_Permissons(){ System.out.println("模拟检查权限"); } public void log(){ System.out.println("模拟日志记录权限"); } }
其次为了演示效果,在目标方法前后分别执行了检查权限和记录日子和方法,这两个方法也就是增强的方法,也就是通知。创建配置文件applicationContext.xml,并指定代理对象。首先通过<bean>元素定义了目标类和切面,然后使用ProxyFactoryBean定义了代理的目标对象,在定义的代理对象中,分别通过<property>子元素指定了代理的实现接口,代理的目标对象、需要织入的目标类的通知以及代理的方式。
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <!--1.目标类--> <bean id="userDao" class="com.cisco.jdk.UserDaoImpl"></bean> <!--2.切面类--> <bean id="myAspect" class="com.cisco.factoryBean.MyAspect"></bean> <!--3.在Spring代理工厂定义一个名称为userDaoProxy的代理对象--> <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--3.1指定代理实现的接口--> <property name="proxyInterfaces" value="com.cisco.jdk.UserDao"/> <!--3.2指定目标对象--> <property name="target" ref="userDao"/> <!--3.3指定切面,植入环绕通知--> <property name="interceptorNames" value="myAspect"/> <!--3.4指定代理方式,true:使用cglib,false:默认使用jdk动态代理--> <property name="proxyTargetClass" value="true"/> </bean> </beans>
四、AspectJ开发
1.AspectJ是一个基于Java语言的AOP框架,Spring AOP引入了对ASpectJ的支持,并允许使用ASpectJ进行编程。AspectJ试下AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的AspectJ。
2.基于XML的声明式AspectJ。
就XML的声明式AspectJ是指通过XML文件来定义切面、切入点以及通知。所有的切面、切入点和通知都必须定义在<aop:config>元素内,<aop:config>元素以及子元素如下图所示:
对于Spring配置文件中的<beans>元素下可以包含多个<aop:config>元素,一个<aop:config>元素又可以包含属性和子元素,其子元素包括<aop:pointcut> <aop:advisor>和<aop:aspect>。在配置的时候,这3个元素必须按照这个顺序来进行定义,在<aop:aspect>元素下,同样包含了属性和多个子元素,通过使用<aop:aspect>元素及其子元素就可以在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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--定义切面Bean--> <bean id="myAspect" class="com.cisco.aspect.MyAscpect" /> <aop:config> <!--配置切面--> <aop:aspect id="aspect" ref="myAspect"> <!--配置切入点--> <aop:pointcut id="myPointCut" expression="execution(* com.cisco.jdk.*.*(..))"/> <!--配置通知--> <!--前置通知--> <aop:before method="myBefore" pointcut-ref="myPointCut"/> <!--后置通知--> <aop:after-returning method="myAfterRunning" pointcut-ref="myPointCut" returning="returnVal"/> <!--环绕通知--> <aop:around method="myAround" pointcut-ref="myPointCut"/> <!--异常通知--> <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/> <!--最终通知--> <aop:after method="myAfter" pointcut-ref="myPointCut"/> </aop:aspect> </aop:config> </beans>
上述配置的讲解:在Spring的配置文件中,配置切面使用的是<aop:aspect>,这个元素会将已经定义好的Spring Bean转换为切面Bean,所以在配置文件中要先顶一个普通的Spring Bean,如上述代码中的myAspect,定义完成后,通过<aop:aspect>元素的ref属性即可引用该Bean。在配置<aop:aspect>元素的时候,通常会指定id和ref属性;配置切入点:切入点是同构<aop:pointcut>元素来定义的,当
======================================================
未完待续~