AOP 简介
AOP (Aspect Oriented Programing) 称为: 面向切面编程, 它是一种编程思想. AOP 是 OOP(面向对象编程 Object Oriented Programming)的思想延续
AOP 采取横向抽取机制, 取代了传统纵向继承体系重复性代码的编写方式(例如性能监视, 事务管理, 安全检查, 缓存, 日志记录等)
AOP 核心思想
基于代理思想, 对原来目标对象, 创建代理对象, 在不修改原对象代码情况下, 通过代理对象, 调用增强功能的代码, 从而对原有业务方法进行增强
切面: 需要代理一些方法和增强代码
AOP 的应用场景
场景一: 记录日志
场景二: 监控方法运行时间 (监控性能)
场景三: 权限控制
场景四: 缓存优化 (第一次调用查询数据库, 将查询结果放入内存对象, 第二次调用, 直接从内存对象返回, 不需要查询数据库 )
场景五: 事务管理 (调用方法前开启事务, 调用方法后提交或者回滚, 关闭事务 )
Spring AOP 编程两种方式
方式一: Spring AOP 使用纯 Java 实现, 不需要专门的编译过程和类加载器, 在运行期通过代理方式向目标类植入增强代码(编程复杂, 不推荐)
方式二: Spring 2.0 之后支持第三方 AOP 框架(AspectJ ), 实现另一种 AOP 编程 (推荐)
AOP 编程相关术语
1.Aspect(切面): 是通知和切入点的结合, 通知和切入点共同定义了关于切面的全部内容 --- 它的功能, 在何时和何地完成其功能
2.joinpoint(连接点): 所谓连接点是指那些被拦截到的点. 在 spring 中, 这些点指的是方法, 因为 spring 只支持方法类型的连接点.
3.Pointcut(切入点): 所谓切入点是指我们要对哪些 joinpoint 进行拦截的定义. 通知定义了切面的 "什么" 和 "何时", 切入点就定义了 "何地".
4.Advice(通知, 增强): 所谓通知是指拦截到 joinpoint 之后所要做的事情就是通知. 通知分为前置通知, 后置通知, 异常通知, 最终通知, 环绕通知(切面要完成的功能)
5.Target(目标对象): 代理的目标对象
6.Weaving(织入): 是指把切面应用到目标对象来创建新的代理对象的过程. 切面在指定的连接点织入到目标对象
7.Introduction(引入)(不要求掌握): 在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field.
AOP 编程底层实现机制
AOP 就是要对目标进行代理对象的创建, Spring AOP 是基于动态代理的, 分别基于两种动态代理机制: JDK 动态代理和 CGLIB 动态代理
方式一: JDK 动态代理
JDK 动态代理, 针对目标对象的接口进行代理 , 动态生成接口的实现类 (必须有接口)
过程要点
1. 必须对接口生成代理
2. 采用 Proxy 对象, 通过 newProxyInstance 方法为目标创建代理对象.
该方法接收三个参数 :
(1)目标对象类加载器
(2)目标对象实现的接口
(3)代理后的处理程序 InvocationHandler
3. 实现 InvocationHandler 接口中 invoke 方法, 在目标对象每个方法调用时, 都会执行 invoke
service 层
- // 接口(表示代理的目标接口)
- public interface ICustomerService {
- // 保存
- void save();
- // 查询
- int find();
- }
- // 实现层
- public class CustomerServiceImpl implements ICustomerService{
- @Override
- public void save() {
- System.out.println("客户保存了.....");
- }
- @Override
- public int find() {
- System.out.println("客户查询数量了.....");
- return 100;
- }
- }
JDK 动态代理工厂
- // 专门用来生成 jdk 的动态代理对象的 - 通用
- public class JdkProxyFactory{
- //target 目标对象
- private Object target;
- // 注入 target 目标对象
- public JdkProxyFactory(Object target) {
- this.target = target;
- }
- public Object getProxyObject(){
- /**
- * 参数 1: 目标对象的类加载器
- * 参数 2: 目标对象实现的接口
- * 参数 3: 回调方法对象
- */
- return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
- new InvocationHandler(){
- public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
- // 如果是保存的方法, 执行记录日志操作
- if(method.getName().equals("save")){
- System.out.println("增强代码: 写日志了...");
- }
- // 目标对象原来的方法执行
- Object object = method.invoke(target, args);// 调用目标对象的某个方法, 并且返回目标对象
- return object;
- }
- });
- }
- }
测试方法
- // 目标: 使用动态代理, 对原来的方法进行功能增强, 而无需更改原来的代码.
- //JDK 动态代理: 基于接口的(对象的类型, 必须实现接口!)
- @Test
- public void testJdkProxy(){
- //target(目标对象)
- ICustomerService target = new CustomerServiceImpl();
- // 实例化注入目标对象
- JdkProxyFactory jdkProxyFactory = new JdkProxyFactory(target);
- // 获取 Object 代理对象: 基于目标对象类型的接口的类型的子类型的对象
- // 必需使用接口对象去强转
- ICustomerService proxy = (ICustomerService)jdkProxyFactory.getProxyObject();
- // 调用目标对象的方法
- proxy.save();
- System.out.println("--------------------");
- proxy.find();
- }
注意
JDK 动态代理产生的对象不再是原对象
错误:
CustomerServiceImpl proxy = (CustomerServiceImpl)jdkProxyFactory.getProxyObject();
正确
ICustomerService proxy = (ICustomerService)jdkProxyFactory.getProxyObject();
方式二: Cglib 动态代理
Cglib 的引入为了解决类的直接代理问题(生成代理子类), 不需要接口也可以代理
该代理方式需要相应的 jar 包, 但不需要导入. 因为 Spring core 包已经包含 cglib , 而且同时包含了 cglib 依赖的 asm 的包(动态字节码的操作类库)
- // 没有接口的类
- public class ProductService {
- public void save() {
- System.out.println("商品保存了.....");
- }
- public int find() {
- System.out.println("商品查询数量了.....");
- return 99;
- }
- }
使用 cglib 代理
- //cglib 动态代理工厂: 用来生成 cglib 代理对象
- public class CglibProxyFactory implements MethodInterceptor{
- private Object target;
- // 注入代理对象
- public CglibProxyFactory(Object target) {
- this.target = target;
- }
- // 获取代理对象
- public Object getProxyObject(){
- //1. 代理对象生成器(工厂思想)
- Enhancer enhancer = new Enhancer();
- // 类加载器
- enhancer.setClassLoader(target.getClass().getClassLoader());
- //2. 在增强器上设置两个属性
- // 设置要生成代理对象的目标对象: 生成的目标对象类型的子类型
- enhancer.setSuperclass(target.getClass());
- // 设置回调方法
- enhancer.setCallback(this);
- //3. 创建获取对象
- return enhancer.create();
- }
- // 回调方法(代理对象的方法)
- /**
- * 参数 1: 代理对象
- * 参数 2: 目标对象的方法对象
- * 参数 3: 目标对象的方法的参数的值
- * 参数 4: 代理对象的方法对象
- */
- public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
- // 如果是保存的方法, 执行记录日志操作
- if(method.getName().equals("save")){
- System.out.println("增强代码: 写日志了...");
- }
- // 目标对象原来的方法执行
- // 调用目标对象的某个方法, 并且返回目标对象
- Object object = method.invoke(target, args);
- return object;
- }
- }
测试方法
- //cglib 动态代理: 可以基于类 (无需实现接口) 生成代理对象
- @Test
- public void testCglibProxy(){
- //target 目标:
- ProductService target = new ProductService();
- // 代理工厂对象, 注入目标
- CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(target);
- // 获取 proxy
- // 代理对象, 其实是目标对象类型的子类型
- ProductService proxy = (ProductService)cglibProxyFactory.getProxyObject();
- // 调用代理对象的方法
- proxy.save();
- System.out.println("---------------------");
- proxy.find();
- }
总结
spring 在运行期, 生成动态代理对象, 不需要特殊的编译器
Spring AOP 优先对接口进行代理 (使用 Jdk 动态代理)如果目标对象没有实现任何接口, 才会对类进行代理 (使用 cglib 动态代理)
需要注意的
1. 对接口创建代理优于对类创建代理, 因为会产生更加松耦合的系统, 所以 spring 默认是使用 JDK 代理. 对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知, 这种方式应该是备用方案
2. 标记为 final 的方法不能够被通知. spring 是为目标类产生子类. 任何需要被通知的方法都被复写, 将通知织入. final 方法是不允许重写的
3.spring 只支持方法连接点: 不提供属性接入点, spring 的观点是属性拦截破坏了封装. 面向对象的概念是对象自己处理工作, 其他对象只能通过方法调用的得到的结果
来源: https://www.cnblogs.com/hublogs/p/12035415.html