Spring4入门之第三章AOP的XML方式
AOP的概述
- 引用百度的一段详解:
-
不修改程序源代码的情况下对程序进行增强。
比如可以进行权限校验、日志记录、性能监控和事务控制等等。
-
AOP最早由AOP联盟的组织提出的,并制定了一套规范,Spring将AOP思想引入到框架当中,必须遵守AOP联盟的规范。
-
AOP面向切面编程。AOP是OOP的扩展和延伸,解决OOP开发中遇到的问题。
AOP的底层实现
JDK动态代理
-
JDK动态代理是代理模式的一种实现方式,其只能代理接口。
-
为接口中的save方法在执行之前,进行权限校验。
-
使用步骤:
1、 新建一个接口
2、 为接口创建一个实现类
3、 创建代理类实现java.lang.reflect.InvocationHandler接口
4、 测试
-
首先新建一个接口UserDao.java
public interface UserDao { public void save(); public void update(); public void find(); public void delete(); }
-
然后为接口UserDao.java新建一个实现类UserDaoImpl.java
public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("保存用户。。。"); } @Override public void update() { System.out.println("修改用户。。。"); } @Override public void find() { System.out.println("查询用户。。。"); } @Override public void delete() { System.out.println("删除用户。。。"); } }
-
创建一个代理类JdkProxy实现java.lang.reflect.InvocationHandler接口,重写invoke方法
/** * 使用JDK动态代理对UserDao产生代理 * @author SYJ */ public class JdkProxy implements InvocationHandler { private UserDao userDao; public JdkProxy(UserDao userDao) { this.userDao = userDao; } /** * @Title: creatUserDaoProxy * @Description: TODO(产生UserDao的方法) * @param @param dao * @param @return * @return UserDao */ public UserDao creatUserDaoProxy() { UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this); return userDaoProxy; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("save".equals(method.getName())) { System.out.println("权限校验========="); } return method.invoke(userDao, args); } }
Cglib动态代理
-
CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库。
-
它可以在运行期扩展Java类与实现Java接口。Hibernate用它来实现PO(Persistent Object 持久化对象)字节码的动态生成。
-
CGLIB是一个强大的高性能的代码生成包。它广泛的被许多AOP的框架使用,例如Spring AOP为他们提供方法的interception(拦截)。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
-
除了CGLIB包,脚本语言例如Groovy和BeanShell,也是使用ASM来生成java的字节码。当然不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
-
实现一个业务类,注意,这个业务类并没有实现任何接口:
CustomerDao
public class CustomerDao { public void save() { System.out.println("客户保存。。。"); } public void update() { System.out.println("客户修改。。。"); } public void find() { System.out.println("客户查找。。。"); } public void delete() { System.out.println("客户删除。。。"); } }
自定义CglibProxy类实现MethodInterceptor接口
/** * Cglib实现动态代理 * @author SYJ */ public class CglibProxy implements MethodInterceptor { private CustomerDao customerDao; public CglibProxy(CustomerDao customerDao) { this.customerDao = customerDao; } /** * @Title: creatCustomerDaoProxy * @Description: TODO(使用cglib产生代理的方法) * @param @return * @return CustomerDao */ public CustomerDao creatCustomerDaoProxy() { // 1.创建cglib的核心类对象 Enhancer enhancer = new Enhancer(); // 2.设置父类 enhancer.setSuperclass(customerDao.getClass()); // 3.设置回调 enhancer.setCallback(this); // 4.产生代理对象 CustomerDao customerDaoProxy = (CustomerDao) enhancer.create(); return customerDaoProxy; } @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { if ("save".equals(method.getName())) { System.out.println("Cglib的权限校验========="); } return methodProxy.invokeSuper(proxy, args); } }
-
这个接口只有一个intercept()方法,这个方法有4个参数:
1)proxy表示增强的对象,即实现这个接口类的一个对象;
2)method表示要被拦截的方法;
3)args表示要被拦截方法的参数;
4)methodProxy表示要触发父类的方法对象;
AOP的相关术语
-
相关术语
- Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
- Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
- Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
- Advice(增强、通知):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
- Target(目标对象):织入 Advice 的目标对象.。
- Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
- introduce(引入):为对象引入附加的方法或属性,从而达到修改对象结构的目的。有的AOP工具又将其称为mixin。
-
图解AOP
-
AOP的Advice类型
-
前置增强(Before advice):(可以获取切入点信息)
在某连接点之前执行的增强,但这个增强不能阻止连接点前的执行(除非它抛出一个异常)
-
后置返回增强(After returning advice):
在某连接点正常完成后执行的增强:例如,一个方法没有抛出任何异常,正常返回。
-
环绕增强(Around Advice):
包围一个连接点的增强,如方法调用。这是最强大的一种增强类型。 环绕增强可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
-
后置异常增强(After throwing advice):
在方法抛出异常退出时执行的增强。
-
后置最终增强(After (finally) advice):
当某连接点退出的时候执行的增强(不论是正常返回还是异常退出)。
-
-
AOP的基本运行流程(来源于网络)
Spring的AOP的入门(AspectJ的Xml方式)
-
传统的AOP开发(Aop联盟)和基于AspectJ的AOP开发(AspcetJ和Spring的整合)
-
为接口CustomerDao中的save方法在执行之前,进行权限的校验
-
CustomerDao.java
public interface ProductDao { public void save(); public void update(); public void find(); public String delete(); }
-
ProductDaoImpl.java
public class ProductDaoImpl implements ProductDao { @Override /* * 增强save方法 * */ public void save() { System.out.println("保存商品"); } @Override public void update() { System.out.println("修改商品"); } @Override public void find() { System.out.println("查询商品"); } @Override public String delete() { System.out.println("删除商品"); return "delete method"; } }
-
编写切面类:
public class MyAspect { /** * @Title: chekPri * @Description: TODO(前置通知) * @param joinPoint * @return void */ public void chekPri() { System.out.println("权限校验========="); } }
-
编写Spring的配置文件,将CustomerDao的实现类ProductDaoImpl和自己编写的切面类都交给Spring管理
<?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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here --> <!-- 配置目标对象,被增强的对象 --> <bean id="productDao" class="com.syj.Spring.aop.ProductDaoImpl" /> <!-- 将切面交给Spring管理 --> <bean id="myAspect" class="com.syj.Spring.aop.MyAspect"/> <!-- 通过AOP的配置完成对目标类产生代理 --> <aop:config> <!-- 定义一个切入点,通过表达式配置哪些类的哪些方法需要增强 --> <aop:pointcut expression="execution(* com.syj.Spring.aop.ProductDaoImpl.save(..))" id="pointcut1"/> <!-- 配置切面,要给什么类的什么方法在什么时候执行什么方法 --> <aop:aspect ref="myAspect" > <!-- 前置通知 --> <aop:before method="chekPri" pointcut-ref="pointcut1" /> </aop:aspect> </aop:config> </beans>
-
运行结果,在执行save方法之前首先进行权限的校验
-
-
AOP开发的通知类型
-
前置通知(获取切入点信息)
在save方法执行前进行权限的校验,就是上面的入门代码。
定义一个切入点,通过表达式配置哪些类的哪些方法需要增强
<aop:pointcut expression="execution(* com.syj.Spring.aop.ProductDaoImpl.save(..))" id="pointcut1"/>
配置切面
<aop:aspect ref="myAspect" > <!-- 前置通知 --> <aop:before method="chekPri" pointcut-ref="pointcut1" /> </aop:aspect>
切面类的增强方法:
/** * * @Title: chekPri * @Description: TODO(前置通知) * @param joinPoint * @return void */ public void chekPri(JoinPoint joinPoint) { System.out.println("权限校验=========" + joinPoint);//joinPoint获取切入点信息 }
-
后置通知(可以获取返回值)
在delete执行之后执行日志的打印
定义一个切入点,通过表达式配置哪些类的哪些方法需要增强
<aop:pointcut expression="execution(* com.syj.Spring.aop.ProductDaoImpl.delete(..))" id="pointcut2"/>
配置切面
<aop:aspect ref="myAspect" > <!-- 前置通知 --> <aop:before method="chekPri" pointcut-ref="pointcut1" /> <!-- 后置打印日志 --> <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result" /> </aop:aspect>
切面类的增强方法
/** * @Title: writeLog * @Description: TODO(后置通知) * @param * @return void */ public void writeLog(Object result) { //会得到ProductDaoImpl中delete的返回值 System.out.println("日志记录=========" + result);//配置切面的returning属性值一致 }
-
环绕通知()
测试update方法的性能
定义一个切入点,通过表达式配置哪些类的哪些方法需要增强
<aop:pointcut expression="execution(* com.syj.Spring.aop.ProductDaoImpl.update(..))" id="pointcut3"/>
配置切面
<aop:aspect ref="myAspect" > <!-- 前置通知 --> <aop:before method="chekPri" pointcut-ref="pointcut1" /> <!-- 后置打印日志 --> <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result" /> <!-- 对数据的修改进行性能的监控 --> <aop:around method="around" pointcut-ref="pointcut3" /> </aop:aspect>
切面类的增强方法
/** * 性能的监控 * * @throws Throwable */ public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕前通知==========="); Object obj = joinPoint.proceed(); System.out.println("环绕后通知==========="); return obj; }
-
异常抛出通知(可以获取异常信息)
在find方法执行如果抛出异常时的解决办法
定义一个切入点,通过表达式配置哪些类的哪些方法需要增强
<aop:pointcut expression="execution(* com.syj.Spring.aop.ProductDaoImpl.find(..))" id="pointcut4"/>
配置切面
<aop:aspect ref="myAspect" > <!-- 前置通知 --> <aop:before method="chekPri" pointcut-ref="pointcut1" /> <!-- 后置打印日志 --> <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result" /> <!-- 对数据的修改进行性能的监控 --> <aop:around method="around" pointcut-ref="pointcut3" /> <!-- 异常抛出的通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex" /> </aop:aspect>
切面类的增强方法
/** * * @Title: afterThrowing * @Description: TODO(异常抛出通知) * @param * @return void */ public void afterThrowing(Throwable ex) { System.out.println("异常抛出通知===========" + ex);//对应aop:after-throwing中throwing属性值 }
-
最终通知(异常中的finally)
定义一个切入点,通过表达式配置哪些类的哪些方法需要增强
切入点依然选择find方法,在异常中测试最终通知
<aop:pointcut expression="execution(* com.syj.Spring.aop.ProductDaoImpl.find(..))" id="pointcut4"/>
配置切面
<aop:aspect ref="myAspect" > <!-- 前置通知 --> <aop:before method="chekPri" pointcut-ref="pointcut1" /> <!-- 后置打印日志 --> <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result" /> <!-- 对数据的修改进行性能的监控 --> <aop:around method="around" pointcut-ref="pointcut3" /> <!-- 异常抛出的通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex" /> <!-- 最终通知 --> <aop:after method="after" pointcut-ref="pointcut4" /> </aop:aspect>
切面类的增强方法
/** * * @Title: after * @Description: TODO(最终通知,最后一定会执行) * @param * @return void */ public void after() { System.out.println("最终一定会执行=========="); }
-
Spring的Pointcut表达式之execution
-
基于execution的函数完成的
-
语法
- [访问修饰符] 方法返回值 包名.类型.方法名(参数)
- public void com.syj.spring.CustomerDao.save(…)
- * *.*. *. *Dao.save(…) 包的的所有Dao结尾的save方法执行
- * com.syj.spring.CustomerDao+.save(…) +代表当前的类和其子类
- * com.syj.spring…*.*(…) 这个包以及子包下面的所有类的所有方法