1.什么是AOP

OOP(Object-Oriented Programming)面向对象编程,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。
AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切 面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高 了系统的可维护性。可用于权限认证、日志、事务处理等。

2.Spring AOP and AspectJ AOP 有什么区别?AOP 有哪些实现方式?

AOP实现的关键就是代理模式,AOP代理主要分为静态代理和动态代理。

  • AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
  • Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

3.静态代理优缺点

优点:

  • 可以使真实角色更加纯粹,不在去关注一些公共的事情
  • 公共的业务由代理来完成,完成业务的分工
  • 公共业务发生扩展时更加集中方便

缺点:

  • 如果接口改了,代理的也要跟着改
  • 因为代理对象,需要与目标对象实现一样的接口,所以会有很多代理类,增加了代码量
  • 在编译期就将代理类和目标对象确立下来,不利于程序的扩展。 ```java //抽象接口:租房 public interface Rent { public void rent(); } //真实角色:房东—>租房 public class Host implements Rent{ public void rent() {
    1. System.out.println("房屋出租");
    } }

//代理类:中介 public class Proxy implements Rent{ // 接收保存目标对象【真正做事的还是真实角色Host】,因此需要维护Host的引用 Host host; public Proxy(Host host){this.host = host;} public void rent() { seeHouse();//看房 host.rent(); 执行目标对象的方法 fare();//收费 }

  1. public void seeHouse(){
  2. System.out.println("带房客看房");
  3. }
  4. public void fare(){
  5. System.out.println("收中介费");
  6. }

}

//客户类:通过代理类执行一系列业务 public class Client { public static void main(String[] args) { // 目标对象 Host host = new Host(); // 代理 Proxy proxy = new Proxy(host); proxy.rent(); // 执行的是,代理的方法 } }

  1. <a name="q7lMV"></a>
  2. # 4.动态代理
  3. <a name="uyDln"></a>
  4. ## 4.1.知识树
  5. 动态代理:代理类编写时不指定被代理的类是谁,代理类是动态生成的,通过java反射机制,在运行时获取某个目标类的接口,并创建代理类<br />动态代理分为两类:**基于接口的动态代理;基于类的动态代理**
  6. - 基于接口的动态代理—JDK API动态代理
  7. - 基于类的动态代理–cglib动态代理
  8. <a name="xIjWw"></a>
  9. ### 4.1.1.基于JDK API动态代理
  10. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22054115/1652360324750-31b4c731-ff21-4e5e-a5df-f7711271fb3c.png#clientId=u431f8f3c-33df-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=421&id=ue9a2e259&margin=%5Bobject%20Object%5D&name=image.png&originHeight=463&originWidth=917&originalType=binary&ratio=1&rotation=0&showTitle=false&size=45903&status=done&style=none&taskId=u43301e69-1029-482f-a79d-28e5b44390d&title=&width=833.5)<br />被代理类和代理类都实现同一套接口,代理类先看代理的是谁,运行时把代理类加载进来,被代理类实现的接口代理类也实现了,从而可对目标对象的方法进行调用和扩展<br />**两个重要的类:**
  11. - **Proxy类:**他是所有动态代理类的超类,调用它的静态方法newProxyInstance()可以生成目标对象的代理对象
  12. ```java
  13. public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
  14. 参数一:生成代理对象使用哪个类装载器【一般我们使用的是代理类的装载器】
  15. 参数二:要代理的对象,通过接口指定【指定要代理类的接口】
  16. 参数三:要将方法调用分派到的调用处理程序【可自行实现Invocationhandler接口传入】
  • InvocationHandler:【调用处理程序】每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用被编码并分发给其调用处理程序的invoke方法来执行并返回结果。

InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法

  1. invoke()是InvocationHandler接口唯一的一个方法
  2. public Object invoke(Object proxy, Method method, Object[] args)
  3. 参数一proxy:正在被调用的方法的代理实例
  4. 参数二method:method对应于调用代理实例上的接口方法的实例
  5. 参数三args:当前被调用的代理实例方法中的参数

注意:

  • 代理对象拥有目标对象相同的方法【因为参数二指定了对象的接口】
  • 用户调用代理对象的什么方法,调用的都是处理器的invoke方法。
  • 使用JDK动态代理必须要有接口【参数二需要接口】 ```java //抽象接口:租房 public interface Rent { public void rent(); } //真实角色:房东—>租房 public class Host implements Rent{ public void rent() {
    1. System.out.println("房屋出租");
    } }

//代理类 public class JdkProxy implements InvocationHandler { // 接收保存目标对象 private Object target; public void setRent(Rent rent){this.target = rent;}

  1. //生成代理类
  2. public Object getJdkProxt(){
  3. return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
  4. }
  5. @Override
  6. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  7. seeHouse();
  8. //核心:本质利用反射实现
  9. Object result = method.invoke(target, args);
  10. fare();
  11. return result;
  12. }
  13. public void seeHouse(){
  14. System.out.println("带房客看房");
  15. }
  16. public void fare(){
  17. System.out.println("收中介费");
  18. }

}

//客户端 public class Client { public static void main(String[] args) { Host host = new Host(); JdkProxy jdkProxy = new JdkProxy(); jdkProxy.setRent(host); Rent proxy = (Rent)jdkProxy.getProxt(); proxy.rent(); } }

  1. <a name="ymbaq"></a>
  2. ### 4.1.2.cglib动态代理
  3. **JDK动态代理是必须实现接口才能进行代理,如果一个类没实现接口,就不能使用Proxy动态代理**。此时可以使用CGLIB(Code Generation Library)动态代理,来实现增强。<br />cglib【code generator bibrary】,代码生成器,可以动态的生成字节码对象,即生成目标类的子类,通过调用父类(目标类)的方法实现,在调用父类方法时再代理中进行增强<br />**cglib实现动态代理的步骤:**
  4. - 1.生成一个字节码对象(空的)
  5. - 2.设置字节码对象的父类是目标对象
  6. - 3.查找目标类上的所有非final的public类型的方法定义;
  7. - 4.将符合条件的方法定义转换成字节码;
  8. - 5.通过生成字节码对象进行回调方法,在回调的过程中,追加功能代码(方法功能的扩展)
  9. - 6.将组成的字节码转换成相应的代理的class对象;
  10. - 7.实现MethodInterceptor接口,用来处理对代理类上所有方法的请求
  11. **Enhance类:**生成动态子类以启用方法拦截。动态生成的子类将重写父类的非final方法,并回调到用户定义的拦截器实现的钩子<br />**MethodInterceptor:**方法拦截器
  12. ```java
  13. Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
  14. 参数一:由CGLib生成的代理类实例
  15. 参数二:在代理类上调用的被代理方法的引用
  16. 参数三:被调用的方法的参数
  17. 参数四:要被触发的父类的方法的引用

使用CGLIB的前提:

  • 如果类是抽象类,只能调用已实现的方法,如果调用抽象方法的时候会报错
  • 类不能是final类,否则会报错 ```java //目标类 public class Host{ public void rent() {
    1. System.out.println("房屋出租");
    } }

public class CglibProxy implements MethodInterceptor { private Object target;//需要代理的目标对象 public Object getCglibProxy(Object objectTarget){ //为目标对象target赋值 this.target = objectTarget; Enhancer enhancer = new Enhancer(); //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类 enhancer.setSuperclass(objectTarget.getClass()); // 设置回调 enhancer.setCallback(this); //创建并返回代理对象 return enhancer.create(); }

  1. public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  2. seeHouse();
  3. Object result = methodProxy.invoke(target, args);
  4. fare();
  5. return result;
  6. }
  7. public void seeHouse(){
  8. System.out.println("带房客看房");
  9. }
  10. public void fare(){
  11. System.out.println("收中介费");
  12. }

} ```

4.2.JDK动态代理与CGLIB对比

  • 1.来源不同
    • JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才生成代理对象,在调用具体方法前调用InvokeHandler的invoke()处理。
    • CGLIB动态代理:利用asm开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
  • 2.使用方式不同
    • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
    • 如果目标对象没有实现接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
  • 3.作用不同
    • JDK动态代理只能对实现了接口的类生成代理,而不能针对类
    • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final
  • 4.影响不同

    • JDK Proxy的优势:最小化依赖关系、代码实现简单、简化开发和维护、JDK原生支持,比CGLIB更加可靠,随JDK版本平滑升级。而字节码类库通常需要进行更新以保证在新版Java上能够使用。
    • CGLIB的优势:无需实现接口,达到代理类无侵入,只操作关心的类,而不必为其他相关类增加工作量。高性能。

      4.3.Spring AOP使用的代理方式

  • 默认使用 JDK 动态代理,这样便可以代理所有的接口类型

  • 如果目标对象没有实现任何接口,则默认是CGLIB的代理方式
  • 可强制使用CGLIB,指定proxy-target-class = “true” 或者 基于注解@EnableAspectJAutoProxy(proxyTargetClass = true)

org.springframework.aop.framework.ProxyProcessorSupport
image.png
org.springframework.aop.framework.DefaultAopProxyFactory
image.png
spring会根据对当前类的所有接口进行判断,如果没有满足的接口,则设置proxyTargetClass为true,如果有则将接口记录下来方便之后使用JDK代理方式时创建代理类使用。
在createAopProxy()中,config.isProxyTargetClass() 为true,则会进入第一个if分支里面。在这个分支里面,如果bean它本身是一个interface或者是本身已经是一个代理对象,那么就会创建JDK代理,否则会使用CGLIB的方式创建代理对象。

5.解释一下Spring AOP里面的几个名词

  1. 切面(Aspect):被抽取的公共模块,可能会横切多个对象。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。

首先要理解‘切’字,需要把对象想象成一个立方体,传统的面向对象变成思维,类定义完成之后(封装)。每次实例化一个对象,对类定义中的成员变量赋值,就相当于对这个立方体进行了一个定义,定义完成之后,就等着被使用,等着被回收。
面向切面编程则是指,对于一个我们已经封装好的类,我们可以在编译期间或在运行期间,对其进行切割,把立方体切开,在原有的方法里面添加(织入)一些新的代码,对原有的方法代码进行一次增强处理。而那些增强部分的代码,就被称之为切面,如下面代码实例中的通用日志处理代码,常见的还有事务处理、权限认证等等。

  1. 切入点(Pointcut):切入点是指我们要对哪些Join point进行拦截的定义。通过切入点表达式,指定拦截的方法,比如指定拦截add、search。

要对哪些类中的哪些方法进行增强,进行切割,指的是被增强的方法。即要切哪些东西。

  1. 连接点(Join point):指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。

知道了要切哪些方法后,剩下的就是什么时候切,在原方法的哪一个执行阶段加入增加代码,这个就是连接点。如方法调用前,方法调用后,发生异常时等等。

  1. 通知(Advice):在切面的某个特定的连接点(Join point)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。

通知被织入方法,改如何被增强。定义切面的具体实现。那么这里面就涉及到一个问题,空间(切哪里)和时间(什么时候切,在何时加入增加代码),空间我们已经知道了就是切入点中定义的方法,而什么时候切,则是连接点的概念。

  1. 目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
  2. AOP代理对象(AOP Proxy Object)

AOP代理是AOP框架所生成的对象,该对象是目标对象的代理对象。代理对象能够在目标对象的基础上,在相应的连接点上调用通知。

  1. 织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成织入。

将切面切入到目标方法之中,使目标方法得到增强的过程被称之为织入。
切入点(pointcut)和连接点(join point)匹配的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。 切入点使得定位通知(advice)可独立于OO层次。 例如,一个提供声明式事务管理的around通知可以被应用到一组横跨多个对象中的方法上(例如服务层的所有业务操作)。

6.Spring通知有哪些类型?

在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过 SpringAOP框架触发的代码段。 Spring切面可以应用5种类型的通知:

  1. 前置通知(Before):在目标方法被调用之前调用通知功能;
  2. 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法 的输出是什么;
  3. 返回通知(After-returning ):在目标方法成功执行之后调用通知;
  4. 异常通知(After-throwing):在目标方法抛出异常后调用通知;
  5. 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。