Java教程

AOP与代理

本文主要是介绍AOP与代理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Spring Aop源码阅读&循环依赖问题


什么是AOP

  • 再软件行业,Aop为Aspect Oriented Programming的缩写,意为:面向切面编程
  • AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型
  • 作用:再不修改【目标类代码】的前提下,可以通过AOP技术去【增强目标类的功能】。通过【预编译方式】或者【运行期动态代理】实现程序功能的统一维护的一种技术
    • 对目标类进行无感知的功能增强。
  • AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
  • AOP最早由AOP联盟的组织提出的,制定了一套规范。Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范
  • 利用AOP可以对业务代码中【业务逻辑】和【系统逻辑】进行隔离,从而使得【业务逻辑】和【系统逻辑】直接的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
微服务架构(SpringCloudAlibaba)-->针对业务层面的拆分--->增加了系统处理的复杂性(服务治理)--->服务网格(istio,再服务忘了架构基础之上,将业务实现和服务治理进行了拆分。)
如果应用架构又改变了--> 微服务架构技术就不吃香了--->不变的东西(架构思想,强大的继承【数据结构与算法,JVM,并发编程,Netty,MySql】)

为什么使用AOP

业务层存在的问题:

public class UserServiceImpl{
 public void saveUser(){
 
 // C 程序员:⽇志监控
 // B 程序员:开启事务--系统逻辑
 // A 程序员:编写业务逻辑代码--程序员更应该关注业务的实现,⽽不需要关⼼系统逻辑
 // B 程序员:关闭事务--系统逻辑
 }
}
public class OrderServiceImpl{
 public void saveOrder(){
 
 // 开启事务
 // 编写业务逻辑代码
 // 关闭事务
 }
}

需要改善的地方:

  1. 抽象共性代码(系统功能)
  2. 将代码职责分离(系统功能,业务功能)
  3. 保证代码的扩展性
  • 作用:
    AOP采取横向抽取机制,补充了传统纵向继承体系(OOP)无法解决的重复性代码优化(性能监视,事务管理,安全检测,缓存),将业务逻辑和系统处理的代码(关闭连接,事务管理,操作日志记录)解耦。
  • 优势:
    重复性代码被抽取处理之后,维护更加方便
    不想修改原有代码前提下,可以动态横向添加共性代码。

AOP相关术语介绍

  • Joinpoint(连接点)

    所谓连接到是指哪些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
    
  • Pointcut(切入点)

    所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
    
  • Advice(通知/增强)

    所谓通知是指拦截到Joinpoint之后所要作的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切莫要完成的功能)
    
  • Introduction(引介)

    引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期间为类动态地添加一些方法或Field
    
  • Target(目标对象)

    代理的目标对象
    
  • Weaving(织入)

    是指把增强应用到目标对象来创建新的代理对象的过程
    
  • Proxy(代理)

    一个类被AOP织入增强后,就产生一个结果代理类
    
  • Aspect(切面)

    是切入点和通知的结合。以后可以编写和配置的
    
  • Advisor(通知面,顾问)

    和Aspect很相似
    

AOP实现只AspectJ(了解)

  • AspectJ是一个Java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译器进行),让java代码局有AspectJ的AOP功能(当然需要特殊的编译器)
  • 可以这样说AspectJ是目前实现的AOP框架中最成熟,功能最丰富的语言。更幸运的是,AspectJ与Java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易
  • 了解AspectJ应用到java代码的过程(这个过程称为织入),对于织入这个概念,可以简单理解为aspect(切面)应用到目标函数(类)的过程
  • 对于织入这个过程,一般分为静态织入和动态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring Aop采用的就是基于运行时增强代理技术
  • ApectJ采用 的就是静态织入的方式,AspectJ主要采用的是编译器织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。

AOP实现之Spring Aop(了解)

实现原理分析
  • Spring AOP是通过动态代理技术实现的
  • 而动态代理是基于反射设计的。
  • 动态嗲了技术的实现方式有俩种:基于接口的JDK动态代理和基于继承的CGLIB动态代理
JDK动态代理

目标对象必须实现接口

// JDK代理对象⼯⼚&代理对象⽅法调⽤处理器
public class JDKProxyFactory implements InvocationHandler {
 // ⽬标对象的引⽤
 private Object target;
 // 通过构造⽅法将⽬标对象注⼊到代理对象中
 public JDKProxyFactory(Object target) {
 super();
 this.target = target;
 }
 /**
 * @return
 */
 public Object getProxy() {
 // 如何⽣成⼀个代理类呢?
 // 1、编写源⽂件
 // 2、编译源⽂件为class⽂件
 // 3、将class⽂件加载到JVM中(ClassLoader)
 // 4、将class⽂件对应的对象进⾏实例化(反射)
 // Proxy是JDK中的API类
 // 第⼀个参数:⽬标对象的类加载器
 // 第⼆个参数:⽬标对象的接⼝
 // 第⼆个参数:代理对象的执⾏处理器
 Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
 this);
 return object;
 }
 /**
 * 代理对象会执⾏的⽅法
 */
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
 Method method2 = target.getClass().getMethod("saveUser", null);
 Method method3 =
Class.forName("com.sun.proxy.$Proxy4").getMethod("saveUser", null);
 System.out.println("⽬标对象的⽅法:" + method2.toString());
 System.out.println("⽬标接⼝的⽅法:" + method.toString());
 System.out.println("代理对象的⽅法:" + method3.toString());
 System.out.println("这是jdk的代理⽅法");
 // 下⾯的代码,是反射中的API⽤法
 // 该⾏代码,实际调⽤的是[⽬标对象]的⽅法
 // 利⽤反射,调⽤[⽬标对象]的⽅法
 Object returnValue = method.invoke(target, args);
 return returnValue;
 }
}

Cglib动态代理
  • 目标对象不需要实现接口
  • 底层是通过继承目标对象产生代理子对象(代理子对象中继承了目标对象的方法,并可以对该方法进行增强)
public class CgLibProxyFactory implements MethodInterceptor {
 /**
 * @param clazz
 * @return
 */
 public Object getProxyByCgLib(Class clazz) {
 // 创建增强器
 Enhancer enhancer = new Enhancer();
 // 设置需要增强的类的类对象
 enhancer.setSuperclass(clazz);
 // 设置回调函数
 enhancer.setCallback(this);
 // 获取增强之后的代理对象
 return enhancer.create();
 }
 /***
 * Object proxy:这是代理对象,也就是[⽬标对象]的⼦类
 * Method method:[⽬标对象]的⽅法
 * Object[] arg:参数
 * MethodProxy methodProxy:代理对象的⽅法
 */
 @Override
 public Object intercept(Object proxy, Method method, Object[] arg,
MethodProxy methodProxy) throws Throwable {
 // 因为代理对象是⽬标对象的⼦类
 // 该⾏代码,实际调⽤的是⽗类⽬标对象的⽅法
 System.out.println("这是cglib的代理⽅法");
 // 通过调⽤⼦类[代理类]的invokeSuper⽅法,去实际调⽤[⽬标对象]的⽅法
 Object returnValue = methodProxy.invokeSuper(proxy, arg);
 // 代理对象调⽤代理对象的invokeSuper⽅法,⽽invokeSuper⽅法会去调⽤⽬标类的invoke⽅
法完成⽬标对象的调⽤
 
 return returnValue;
 }
}

ASM API使用

ClassWriter classWriter = new ClassWriter(0);
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className, null,
 "java/lang/Object", null);
MethodVisitor initVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "
<init>",
 "()V", null, null);
initVisitor.visitCode();//访问开始
initVisitor.visitVarInsn(Opcodes.ALOAD, 0);//this指针⼊栈
initVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "
<init>",
 "()V");//调⽤构造函数
initVisitor.visitInsn(Opcodes.RETURN);
initVisitor.visitMaxs(1, 1);//设置栈⻓、本地变量数
使用
  • 其实现ProxyFactoryBean创建
  • 使用aop:advisor定义通知器的方式实现AOP则需要通知类实现Advice接口
  • 增强(通知)的类型有:
- 前置通知:org.springframework.aop.MethodBeforeAdvice
 - 后置通知:org.springframework.aop.AfterReturningAdvice
 - 环绕通知:org.aopalliance.intercept.MethodInterceptor
 - 异常通知:org.springframework.aop.ThrowsAdvice

基于AspectJ的AOP使用

其实就是指的Spring+AspectJ整合,不过Spring以及将AspectJ收录到自身的框架中了,并且底层织入依然是采取的动态织入方式。

切入点表达式

切入点表达式的格式:

execution([修饰符]返回值类型 包名.类名.方法名(参数))

表达式格式说明:

  • execution:必须要

  • 修饰符:可省略

  • 返回值类型:必须要,但是可以使用*通配符

  • 包名:

    ** 多级包之间使⽤.分割
    ** 包名可以使⽤*代替,多级包名可以使⽤多个*代替
    ** 如果想省略中间的包名可以使⽤ ..
    
  • 类名

    ** 可以使⽤*代替
    ** 也可以写成*DaoImpl
    
  • 方法名:

    ** 也可以使⽤*好代替
    ** 也可以写成add*
    
    
  • 参数

    ** 参数使⽤*代替
    ** 如果有多个参数,可以使⽤ ..代替
    

通知类型

通知类型(五种):前置通知,后置通知,最终通知,环绕通知,异常抛出通知。

前置通知:

* 执⾏时机:⽬标对象⽅法之前执⾏通知
 * 配置⽂件:<aop:before method="before" pointcut-ref="myPointcut"/>
 * 应⽤场景:⽅法开始时可以进⾏校验

后置通知:

* 执⾏时机:⽬标对象⽅法之后执⾏通知,有异常则不执⾏了
 * 配置⽂件:<aop:after-returning method="afterReturning" pointcutref="myPointcut"/>
 * 应⽤场景:可以修改⽅法的返回值

最终通知:

 * 执⾏时机:⽬标对象⽅法之后执⾏通知,有没有异常都会执⾏
 * 配置⽂件:<aop:after method="after" pointcut-ref="myPointcut"/>
 * 应⽤场景:例如像释放资源

环绕通知:

* 执⾏时机:⽬标对象⽅法之前和之后都会执⾏。
 * 配置⽂件:<aop:around method="around" pointcut-ref="myPointcut"/>
 * 应⽤场景:事务、统计代码执⾏时机

异常抛出通知:

* 执⾏时机:在抛出异常后通知
 * 配置⽂件:<aop:after-throwing method=" afterThrowing " pointcutref="myPointcut"/>
 * 应⽤场景:包装异常

使用注解实现

  1. 编写切面类(注意不是通知类,因为该类中可以指的切入点)

  2. 环绕通知注解
    @Around

     作⽤:
     把当前⽅法看成是环绕通知。属性:
     value:
     ⽤于指定切⼊点表达式,还可以指定切⼊点表达式的引⽤。
    
    
  3. 定义通用切入点
    使用@PointCut注解再切面类中定义一个通用的切入点,其他通知可以引用该切入点

纯注解方式
@Configuration
@ComponentScan(basePackages="com.kkb")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

代理模式

其实每个模式名称就表明了该模式的作用,戴爱玲模式就是多一个代理类出来替原对象进行一些操作。代理又分为动态代理和静态代理。

静态代理

比如租房子找中介

静态代理的重点:

  • 需要为源类手动编写一个代理类
  • 代理类和源类实现同一接口。
  • 代理对象持有源对象的引用。

静态代理的缺点:

  • 会产生大量的代理类。
代理模式的应用场景:

如果已有的方法再使用的时候需要对原有的方法进行改进,此时有俩种办法:

  1. 修改原有的方法来适应,这样违反了“对扩展开发,对修改关闭”的原则
  2. 就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。

使用代理模式,可以将功能划分的更加清晰,有助于后期维护!

动态代理

动态代理再编译期间,不需要为源类去手动编写一个代理类

只会再运行期间,去为源对象产生一个代理对象。

JDK动代理和Cglib动态代理的区别

1.JDK动态代理是Java⾃带的,cglib动态代理是第三⽅jar包提供的。
2.JDK动态代理是针对【拥有接⼝的⽬标类】进⾏动态代理的,⽽Cglib是【⾮final类】都可以进⾏动态
代理。但是Spring【优先使⽤JDK动态代理】。
3.JDK动态代理实现的逻辑是【⽬标类】和【代理类】都【实现同⼀个接⼝】,⽬标类和代理类【是平级
的】。⽽Cglib动态代理实现的逻辑是给【⽬标类】⽣个孩⼦(⼦类,也就是【代理类】),【⽬标类】和
【代理类】是⽗⼦继承关系】。
4.JDK动态代理在早期的JDK1.6左右性能⽐cglib差,但是在JDK1.8以后cglib和jdk的动态代理性能基
本上差不多。反⽽jdk动态代理性能更加的优越
这篇关于AOP与代理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!