Spring笔记03_AOP
1. AOP
1.1 AOP介绍
1.1.1 什么是AOP
- 在软件业,AOP为Aspect Oriented Programming的缩写,意为:
面向切面编程
,通过预编译方式
和运行期动态代理
实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程
的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离
,从而使得业务逻辑各部分之间的耦合度降低
,提高程序的可重用性
,同时提高了开发的效率。 - AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码。如下图所示:
- 经典应用:事务管理、性能监视、安全检查、缓存 、日志等。
- Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过
代理方式
向目标类织入增强代码。 - AspectJ是一个基于Java语言的AOP框架,从Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入。
1.1.2 AOP实现原理
- aop底层将采用代理机制进行实现
- 接口+实现类时:Spring采用JDK的动态代理Proxy
- 只有实现类时:Spring采用cglib字节码增强。这种底层属于继承增强。
1.1.3 AOP术语【掌握】
- Target :目标类,需要被代理的类。本例中如:UserDao
- Joinpoint(连接点) :所谓连接点是指那些可能被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。本例中如:UserDao的所有的方法
- PointCut 切入点 :所谓切入点是指我们要对哪些Joinpoint进行拦截,即已经被增强的连接点。例如:save()
- Advice :通知/增强,增强的代码。例如:checkPri()
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知、后置通知、异常通知、最终通知、环绕通知(即切面要完成的功能)。 - Weaving(织入) :是指把通知/增强advice应用到目标对象target来创建新的代理对象proxy的过程。
spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入。 - Proxy :代理类,一个类被AOP织入增强后,就产生一个结果代理类。
- Aspect(切面) : 是切入点Pointcut和通知Advice(引介)的结合。
- Introduction(引介) :引介是一种
特殊的通知
,在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或Field。
- 小结:
一个线是一个特殊的面。
一个切入点和一个通知,组成成一个特殊的面。
详解如图01:详解如图02:
1.2 AOP的底层实现(了解)
动态代理
- JDK动态代理:只能对实现了接口的类产生代理
- Cglib动态代理(第三方代理技术):对没有实现接口的类产生代理对象,生成子类对象。
- 代理知识点参考:https://www.cnblogs.com/itzho...
1.2.1 JDK动态代理
- 准备工作:新建web项目,不需要导包
代理对象UserDao
package com.itzhouq.spring.demo1; public interface UserDao { public void save(); public void update(); public void find(); public void delete(); }
实现类
package com.itzhouq.spring.demo1; 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("删除用户"); } }
- 使用JDK产生UserDao的代理类
package com.itzhouq.spring.demo1; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /* * 使用JDK动态代理对UserDao产生代理 */ public class JDKProxy implements InvocationHandler { // 将被增强的对象传递到代理总 private UserDao userDao; public JDKProxy(UserDao userDao) { this.userDao = userDao; } /* * 产生UserDao代理的方法 */ public UserDao createProxy() { 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 { // 判断方法名是不是save if("save".equals(method.getName())) { // 增强 System.out.println("权限校验=============="); return method.invoke(userDao, args); } return method.invoke(userDao, args); } }
测试类
package com.itzhouq.spring.demo1; import org.junit.Test; public class SpringDemo1 { @Test //JDK动态代理 public void test1() { UserDao userDao = new UserDaoImpl(); // 创建代理 UserDao proxy = new JDKProxy(userDao).createProxy(); proxy.save(); proxy.find(); proxy.delete(); proxy.update(); //权限校验============== //保存用户 //查找用户 //删除用户 //更新用户 } }
1.2.2 Cglib动态代理
- Cglib是第三方开源代码生成类库,动态添加类的属性和方法
- cglib的运行原理:在运行时创建目标类的子类从而对目标类进行增强。
- 因为Spring核心包中包含了cglib的包,所以引入Spring的4+2必备包就可以使用Cglib了
目标类:没有实现接口
package com.itzhouq.spring.demo2; 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("删除客户"); } }
- Cglib代理类
package com.itzhouq.spring.demo2; import java.lang.reflect.Method; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; /* * Cglib动态代理 */ public class CglibProxy implements MethodInterceptor { private CustomerDao customerDao; public CglibProxy(CustomerDao customerDao) { this.customerDao = customerDao; } // 使用Cglib产生代理的方法 public CustomerDao createProxy() { // 1. 创建cglib的核心类对象 Enhancer enhancer = new Enhancer(); // 2. 设置父类 enhancer.setSuperclass(customerDao.getClass()); // 3. 设置回调(类似于InvocationHandler对象) enhancer.setCallback(this); // 4. 创建代理对象 CustomerDao proxy = (CustomerDao) enhancer.create(); return proxy; } @Override public Object intercept(Object proxy, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable { // 判断方法是否为save if("save".equals(method.getName())) { // 增强 System.out.println("权限校验========"); return methodProxy.invokeSuper(proxy, arg); } return methodProxy.invokeSuper(proxy, arg); } }
- 测试
package com.itzhouq.spring.demo2; import org.junit.Test; public class SpringDemo2 { /* * cglib的测试 */ @Test public void test1() { CustomerDao customerDao = new CustomerDao(); CustomerDao proxy = new CglibProxy(customerDao).createProxy(); proxy.save(); proxy.find(); proxy.update(); proxy.delete(); //权限校验======== //保存客户 //查找客户 //更新客户 //删除客户 } }
1.2.3 总结
- Spring在运行期,生成动态代理对象,不需要特殊的编译器。
Spring AOP的底层是通过JDK动态代理或者Cglib动态代理技术为目标bean执行横向织入的。
- Spring会优先使用Spring使用JDK代理方式进行代理
- 若目标对象没有实现任何接口,Spring容器会使用Cglib动态代理
- 标记为final的方法不能被代理,因为无法进行覆盖
- Cglib动态代理,是针对的目标类产生子类,所以目标类不能被final修饰。
- Spring只支持方法连接点,不提供属性连接。
2. Spring的AOP的开发(AspectJ的XML方式)
2.1 Spring的AOP简介
- AOP思想最早是由AOP联盟组织提出。Spring是使用这种思想最好的框架。
- Spring的AOP有自己的实现方式(非常繁琐)。AspectJ是一个AOP框架,Spring引入AspectJ作为自身AOP的开发
Spring两种开发方式
- Spring传统方式(弃用)。
- Spring基于AspectJ的AOP开发方式(使用)。
2.2 AOP入门开发
2.2.1 准备工程和jar包
除去基本的6个包,在web项目中添加aop开发的相关jar包
- AOP联盟规范包:..spring相关依赖包spring-framework-3.0.2.RELEASE-dependenciesorg.aopalliancecom.springsource.org.aopalliance1.0.0com.springsource.org.aopalliance-1.0.0.jar
- AOP包:..spring-framework-4.2.4.RELEASE-distspring-framework-4.2.4.RELEASElibsspring-aop-4.2.4.RELEASE.jar
- AspectJ包:..spring相关依赖包spring-framework-3.0.2.RELEASE-dependenciesorg.aspectjcom.springsource.org.aspectj.weaver1.6.8.RELEASEcom.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
- Spring和ASpectJ整合包:..spring-framework-4.2.4.RELEASE-distspring-framework-4.2.4.RELEASElibsspring-aspects-4.2.4.RELEASE.jar
- Spring整合JUnit单元测试包:..spring-framework-4.2.4.RELEASE-distspring-framework-4.2.4.RELEASElibsspring-test-4.2.4.RELEASE.jar
2.2.2 引入Spring的配置文件
- 引入aop的约束
- 约束的位置:../spring-framework-4.2.4.RELEASE-dist/spring-framework-4.2.4.RELEASE/docs/spring-framework-reference/html/xsd-configuration.html 40.2.7 the aop schema
<?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"> </beans>
2.2.3 编写目标类并完成配置
目标接口
package com.itzhouq.spring.demo3; public interface ProductDao { public void save(); public void update(); public void find(); public void delete(); }
目标类
package com.itzhouq.spring.demo3; public class ProductDaoImpl implements ProductDao { @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("删除商品"); } }
配置目标对象
<!-- 配置目标对象:被增强的对象 --> <bean id="productDao" class="com.itzhouq.spring.demo3.ProductDaoImpl"></bean>
2.2.4 测试类
package com.itzhouq.spring.demo3; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /* * AOP入门 */ @RunWith(SpringJUnit4ClassRunner.class) //这两个注释就是Spring整合了JUnit单元测试 @ContextConfiguration("classpath:applicationContext.xml") public class SpringDemo3 { //注入productDao @Resource(name="productDao") private ProductDao productDao; @Test public void test1() { productDao.save(); productDao.update(); productDao.find(); productDao.delete(); // 保存商品 // 更新商品 // 查找商品 // 删除商品 } }
- 下面需要对save()方法增强
2.2.5 编写一个切面类
- 切面:多个通知和多个切入点的组合。
package com.itzhouq.spring.demo3; /* * 切面类 */ public class MyAspectXML { public void checkPri() { System.out.println("权限校验。。。"); } }
2.2.6 将切面类交给Spring
<!-- 将切面类交给Spring管理 --> <bean id="myAspect" class="com.itzhouq.spring.demo3.MyAspectXML"></bean>
2.2.7 通过AOP的配置实现动态代理
<!-- 通过AOP的配置完成对目标类产生代理 --> <aop:config> <!-- 表达式配置哪些类的哪些方法需要进行增强 --> <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.save(..))" id="pointcut1"/> <!-- 配置切面 --> <aop:aspect ref="myAspect"> <aop:before method="checkPri" pointcut-ref="pointcust1"/> </aop:aspect> </aop:config>
2.2.8 测试类中运行结果,save实现了增强
@RunWith(SpringJUnit4ClassRunner.class) //这两个注释就是Spring整合了JUnit单元测试 @ContextConfiguration("classpath:applicationContext.xml") public class SpringDemo3 { //注入productDao @Resource(name="productDao") private ProductDao productDao; @Test public void test1() { productDao.save(); productDao.update(); productDao.find(); productDao.delete(); // 权限校验。。。 // 保存商品 // 更新商品 // 查找商品 // 删除商品 } }
2.3 Spring中的通知类型
2.3.1 前置通知
配置
<!-- 配置切面 --> <aop:aspect ref="myAspect"> <aop:before method="checkPri" pointcut-ref="pointcut1"/> </aop:aspect>
- 在目标方法执行之前进行操作
- 可以获得切入点的信息
比如在切面类中加入参数JoinPoint
public class MyAspectXML { public void checkPri(JoinPoint joinPoint) { System.out.println("权限校验。。。"+joinPoint); } }
测试打印的效果
// 权限校验。。。execution(void com.itzhouq.spring.demo3.ProductDao.save()) // 保存商品 // 更新商品 // 查找商品 // 删除商品
2.3.2 后置通知:
- 在目标方法之后操作,可以获得返回值
- 修改接口ProductDao和实现类ProductDaoImpl类的delete方法的返回值为String
配置后置通知
<!-- 通过AOP的配置完成对目标类产生代理 --> <aop:config> <!-- 表达式配置哪些类的哪些方法需要进行增强 --> <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.save(..))" id="pointcut1"/> <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.delete(..))" id="pointcut2"/> <!-- 配置切面 --> <aop:aspect ref="myAspect"> <!-- 前置通知============= --> <aop:before method="checkPri" pointcut-ref="pointcut1"/> <!-- 后置通知============= --> <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result"/> </aop:aspect> </aop:config>
- 在后置切面类中添加记录日志的方法
/* * 后置通知演示 */ public void writeLog(Object result) { System.out.println("日志记录======="+result); }
测试
权限校验。。。execution(void com.itzhouq.spring.demo3.ProductDao.save()) // 保存商品 // 更新商品 // 查找商品 // 删除商品 // 日志记录=======kkk
2.3.3 环绕通知:
- 在目标方法执行之前 和之后进行操作
- 环绕通知可以组织目标方法的执行
- 举例:需求---在修改方法update前后添加性能监控
- 在切面类中添加一个方法用来测试环绕通知
/** * 性能监控 * @throws Throwable */ public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕前通知==========="); Object obj = joinPoint.proceed();//这一步相当于执行目标程序 System.out.println("环绕后通知========="); return obj; }
配置环绕通知
<aop:config> <!-- 表达式配置哪些类的哪些方法需要进行增强 --> <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.save(..))" id="pointcut1"/> <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.delete(..))" id="pointcut2"/> <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.update(..))" id="pointcut3"/> <!-- 配置切面 --> <aop:aspect ref="myAspect"> <!-- 前置通知============= --> <aop:before method="checkPri" pointcut-ref="pointcut1"/> <!-- 后置通知============= --> <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result"/> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="pointcut3"/> </aop:aspect> </aop:config>
测试
// 权限校验。。。execution(void com.itzhouq.spring.demo3.ProductDao.save()) // 保存商品 // 环绕前通知=========== // 更新商品 // 环绕后通知========= // 查找商品 // 删除商品 // 日志记录=======kkk
2.3.4 异常抛出通知:
- 在程序抛出异常时候进行操作,可以得到异常信息
在find方法上模拟一个异常
@Override public void find() { System.out.println("查找商品"); int i = 1 / 0; }
- 切面类中添加一个方法用于测试异常抛出通知
/* * 异常抛出通知 */ public void afterThrowing(Throwable ex) { System.out.println("异常抛出通知=======" + ex); }
配置异常通知
<aop:config> <!-- 表达式配置哪些类的哪些方法需要进行增强 --> <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.save(..))" id="pointcut1"/> <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.delete(..))" id="pointcut2"/> <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.update(..))" id="pointcut3"/> <aop:pointcut expression="execution(* com.itzhouq.spring.demo3.ProductDaoImpl.find(..))" id="pointcut4"/> <!-- 配置切面 --> <aop:aspect ref="myAspect"> <!-- 前置通知============= --> <aop:before method="checkPri" 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> </aop:config>
测试
// 权限校验。。。execution(void com.itzhouq.spring.demo3.ProductDao.save()) // 保存商品 // 环绕前通知=========== // 更新商品 // 环绕后通知========= // 查找商品 // 异常抛出通知=======java.lang.ArithmeticException: / by zero
2.3.5 最终通知:
- 在切面类中添加方法测试最终通知
/* * 最终通知:相当于finally代码块中的内容 */ public void after() { System.out.println("最终通知========="); }
配置最终通知
<!-- 配置最终通知 --> <aop:after method="after" pointcut-ref="pointcut4"/>
- 测试
- 无论是否有异常,最终通知都会执行。
2.3.6 引介通知(不用会)
2.4 切入点表达式【掌握】
1.execution() 用于描述方法【掌握】 语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常) 修饰符,一般省略 public 公共方法 * 任意 返回值,不能省略 void 返回没有值 String 返回值字符串 * 任意 包,[可以省略] com.itheima.crm 固定的包 com.itheima.crm.*.service crm包下面的任意子包,固定目录service(例如:com.itheima.crm.staff.service) com.itheima.crm.. crm包下面的所有子包(含自己) com.itheima.crm.*.service.. crm包下面的任意子包,固定目录service,service目录任意包(含自己) 类,[可以省略] UserServiceImpl 指定的类 *Impl 以Impl结尾的类 User* 以User开头的类 * 任意的类 方法名,不能省略 addUser 固定的方法名 add* 以add开头的方法名 *Do 以Do结尾的方法名 * 任意的方法名 (参数) () 无参 (int) 一个整型 (int, int) 两个整型 (..) 参数任意 throws,[可以省略],一般省略。 综合案例1: execution(* com.itheima.crm.*.service..*.*(..)) 综合案例2: <aop:pointcut expression="execution(* com.itheima.*WithCommit.*(..)) || execution(* com.itheima.*Service.*(..))" id="myPointCut"/> 2.within:匹配包或子包中的方法(了解) within(com.itheima.aop..*) 3.this:匹配实现了接口的代理对象中的方法(了解) this(com.itheima.aop.user.UserDAO) 4.target:匹配实现了接口的目标对象中的方法(了解) target(com.itheima.aop.user.UserDAO) 5.args:匹配参数格式符合标准的方法(了解) args(int, int) 6.bean(id):对指定的bean所有的方法(了解) bean('userServiceId')
相关推荐
zmysna 2020-06-25
itjavashuai 2020-05-27
neweastsun 2020-05-05
MicroBoy 2020-05-04
方志朋 2020-05-01
横云断岭 2020-04-17
方志朋 2020-03-01
smalllove 2020-02-14
csuzxm000 2020-01-09
neweastsun 2019-12-29
82296830 2014-06-07
whbing 2019-12-08
Julywhj 2019-12-04
JudeJoo 2019-11-19
也许不会看见 2020-07-08
HappyHeng 2020-06-25
justlike 2020-09-02
牧场SZShepherd 2020-07-19
也许不会看见 2020-06-28