【JavaEE进阶】Spring AOP 原理

发布于:2025-04-04 ⋅ 阅读:(33) ⋅ 点赞:(0)

欢迎关注个人主页:逸狼


创造不易,可以点点赞吗

如有错误,欢迎指出~


SpringAOP是基于动态代理来实现AOP的

代理模式

代理模式,也叫委托模式. 定义:为其他对象提供⼀种代理以控制对这个对象的访问.它的作⽤就是通过提供⼀个代理类,让我们 在调⽤⽬标⽅法的时候,不再是直接对⽬标⽅法进⾏调⽤,⽽是通过代理类间接调⽤. 在某些情况下,⼀个对象不适合或者不能直接引⽤另⼀个对象,⽽代理对象可以在客⼾端和⽬标对象之 间起到中介的作⽤.

生活中的代理->房屋中介:房屋进⾏租赁时,卖⽅会把房屋授权给中介,由中介来代理看房,房屋咨询等服务

代理模式的主要⻆⾊

  • 1. Subject:业务接⼝类.可以是抽象类或者接⼝(不⼀定有)
  • 2. RealSubject:业务实现类.具体的业务执⾏,也就是被代理对象.
  • 3. Proxy:代理类.RealSubject的代理.

⽐如房屋租赁:

  1. Subject就是提前定义了房东做的事情,交给中介代理,也是中介要做的事情
  2. RealSubject:房东
  3. Proxy:中介

代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进⾏⼀些功能的附加与增强.

根据代理的创建时期,代理模式分为静态代理和动态代理.

  • 静态代理:由程序员创建代理类或特定⼯具⾃动⽣成源代码再对其编译,在程序运⾏前代理类的 .class⽂件就已经存在了.
  • 动态代理:在程序运⾏时,运⽤反射机制动态创建⽽成

静态代理

静态代理:在程序运⾏前,代理类的.class⽂件就已经存在了.(在出租房⼦之前,中介已经做好了相关的 ⼯作,就等租⼾来租房⼦了)

我们通过代码来加深理解.以房租租赁为例

定义接⼝(定义房东要做的事情,也是中介需要做的事情)

package com.example.demo.proxy;

public interface HouseSubject {
    void rent();
    void sale();
}

实现接⼝(房东出租房⼦) 

package com.example.demo.proxy;


public class RealHouseSubject implements HouseSubject{


    @Override
    public void rent() {
        System.out.println("我是房东,我出租房子");
    }

    @Override
    public void sale() {
        System.out.println("我是房东,我出售房子");

    }
}

 代理(中介,帮房东出租房⼦)

package com.example.demo.proxy;

public class HouseProxy implements HouseSubject{

    private RealHouseSubject subject;

    public HouseProxy(RealHouseSubject subject){
        this.subject = subject;//中介代理的房子实际上是房东的房子
    }

    @Override
    public void rent() {
        System.out.println("我是中介,我帮房东开始代理");
        subject.rent();
        System.out.println("我是中介,我帮房东结束代理");
    }

    @Override
    public void sale() {
        System.out.println("我是中介,我帮房东开始代理");
        subject.sale();
        System.out.println("我是中介,我帮房东结束代理");
    }
}

使用 

package com.example.demo.proxy;

public class Main {
    public static void main(String[] args) {
        HouseProxy proxy = new HouseProxy(new RealHouseSubject());
        proxy.rent();
    }
}

结果

从上述程序可以看出,虽然静态代理也完成了对⽬标对象的代理,但是由于代码都写死了,对⽬标对象的 每个⽅法的增强都是⼿动完成的,⾮常不灵活.所以⽇常开发⼏乎看不到静态代理的场景. 

动态代理

相⽐于静态代理来说,动态代理更加灵活. 我们不需要针对每个⽬标对象都单独创建⼀个代理对象,⽽是把这个创建代理对象的⼯作推迟到程序运 ⾏时由JVM来实现.也就是说动态代理在程序运⾏时,根据需要动态创建⽣成.

⽐如房屋中介,我不需要提前预测都有哪些业务,⽽是业务来了我再根据情况创建. 我们还是先看代码再来理解.

Java也对动态代理进⾏了实现,并给我们提供了⼀些API,常⻅的实现⽅式有两种:

  • 1. JDK动态代理
  • 2. CGLIB动态代理

动态代理在我们⽇常开发中使⽤的相对较少,但是在框架中⼏乎是必⽤的⼀⻔技术.学会了动态代理 之后,对于我们理解和学习各种框架的原理也⾮常有帮助.

JDK动态代理

JDK动态代理类实现步骤

  • 1. 定义⼀个接⼝及其实现类(静态代理中的 HouseSubject 和RealHouseSubject )
  • 2. ⾃定义InvocationHandler 并重写 invoke ⽅法,在invoke ⽅法中我们会调⽤⽬标⽅ 法(被代理类的⽅法)并⾃定义⼀些处理逻辑
  • 3. 通过Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) ⽅法创建代理对象  

定义JDK动态代理类

实现 InvocationHandler 接⼝ 

InvocationHandler接⼝是Java动态代理的关键接⼝之⼀,它定义了⼀个单⼀⽅法invoke() ,⽤于 处理被代理对象的⽅法调⽤.

 通过实现 InvocationHandler 接⼝,可以对被代理对象的⽅法进⾏功能增强. 

package com.example.demo.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JDKInvocation implements InvocationHandler {

    //目标对象/被代理对象
    private Object target;

    public JDKInvocation(HouseSubject target){
        this.target = target;
    }
/**
 * 参数说明 
 * proxy:代理对象 
 * method:代理对象需要实现的⽅法,即其中需要重写的⽅法 
 * args:method所对应⽅法的参数 
 */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //代理前
        System.out.println("开始代理");
        //通过反射执行目标对象的目标方法
        Object result = method.invoke(target, args);
        //代理后
        System.out.println("结束代理");
        return result;
    }
}

创建⼀个代理对象并使⽤

Proxy 类中使⽤频率最⾼的⽅法是: newProxyInstance() ,这个⽅法主要⽤来⽣成⼀个代理 对象 

package com.example.demo.proxy;

import java.lang.reflect.Proxy;

public class Main {
 

    public static void main(String[] args) {
        //JDK动态代理
        HouseSubject target = new RealHouseSubject();

        //运行时,创建代理对象
        //写法1
//        HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),
//                new Class[]{HouseSubject.class},new JDKInvocation(target));
        //写法2
        HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),new JDKInvocation(target));
        proxy.rent();
        proxy.sale();
    }
}

结果 

CGLIB动态代理

JDK动态代理有⼀个最致命的问题是其只能代理实现了接⼝的类. 有些场景下,我们的业务代码是直接实现的,并没有接⼝定义.为了解决这个问题,我们可以⽤CGLIB动 态代理机制来解决.

CGLIB(CodeGenerationLibrary)是⼀个基于ASM的字节码⽣成库,它允许我们在运⾏时对字节码进⾏修改和动态⽣成.CGLIB通过继承⽅式实现代理,很多知名的开源框架都使⽤到了CGLIB.

例如Spring 中的AOP模块中:如果⽬标对象实现了接⼝,则默认采⽤JDK动态代理,否则采⽤CGLIB动态代理.

CGLIB动态代理类实现步骤

  • 1. 定义⼀个类(被代理类)
  • 2. ⾃定义MethodInterceptor 并重写intercept ⽅法, intercept ⽤于增强⽬标⽅ 法,和JDK动态代理中的invoke ⽅法类似
  • 3. 通过Enhancer类的create()创建代理类

接下来看下实现:

添加依赖

和JDK动态代理不同,CGLIB(CodeGenerationLibrary)实际是属于⼀个开源项⽬,如果你要使⽤它 的话,需要⼿动添加相关依赖

<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>3.3.0</version>
</dependency>
⾃定义MethodInterceptor(⽅法拦截器)

实现MethodInterceptor接⼝

MethodInterceptor 和JDK动态代理中的 InvocationHandler 类似,它只定义了⼀个⽅ 法intercept() ,⽤于增强⽬标⽅法

package com.example.demo.proxy;



import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGLibMethodInterceptor implements MethodInterceptor {

    //目标对象/被代理对象
    private Object target;

    public CGLibMethodInterceptor(HouseSubject target){
        this.target = target;
    }
/**
 * 参数说明: 
 * o: 被代理的对象 
 * method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法) 
 * objects: ⽅法⼊参 
 * methodProxy: ⽤于调⽤原始⽅法 
 */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        //代理前
        System.out.println("开始代理");
        //通过反射执行目标对象的目标方法
        Object result = method.invoke(target, objects);
        //代理后
        System.out.println("结束代理");
        return result;
    }
}
创建代理类,并使⽤ 

Enhancer.create()⽤来⽣成⼀个代理对象


    public static void main(String[] args) {
        //CGLib动态代理
        HouseSubject target = new RealHouseSubject();
        //运行时, 动态代理创建对象
        //如果使用CGlib运行的话, 需要添加vm option
        //--add-opens java.base/java.lang=ALL-UNNAMED
        HouseSubject proxy = (HouseSubject) Enhancer.create(target.getClass(),
                new CGLibMethodInterceptor(target));
        proxy.rent();
    }

 

--add-opens java.base/java.lang=ALL-UNNAMED

 两种动态代理的对比

代理⼯⼚有⼀个重要的属性:proxyTargetClass,默认值为false.也可以通过程序设置 

可以通过@EnableAspectJAutoProxy(proxyTargetClass = true) 来设置 注意: SpringBoot2.X开始,默认使⽤CGLIB代理 可以通过配置项 spring.aop.proxy-target-class=false 来进⾏修改,设置默认为jdk代理 SpringBoot设置 @EnableAspectJAutoProxy ⽆效,因为SpringBoot默认使⽤ AopAutoConfiguration进⾏装配

运⾏时使⽤哪种⽅式与项⽬配置和代理的对象有关

@SpringBootApplication
public class SpringAopApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringAopApplication.class, args);

//		//设置spring.aop.proxy-target-class=false
//		//测试代理类的情况, 结论: CGLIb代理
//		TestController bean = context.getBean(TestController.class);
//		System.out.println(bean.getClass().toString());
//		//测试代理接口 结论: JDK代理
//		//修改代码内容, 给RealHouseSubject 添加@Component
		HouseSubject bean1 = (HouseSubject)context.getBean("realHouseSubject");
		System.out.println(bean1.getClass().toString());
//		//把接口改成类, 以下代码会报错
//		RealHouseSubject bean1 = (RealHouseSubject)context.getBean("realHouseSubject");
//		System.out.println(bean1.getClass().toString());


		//设置spring.aop.proxy-target-class=true  (默认)
		//测试代理类的情况, 结论: CGLib
		TestController bean = context.getBean(TestController.class);
		System.out.println(bean.getClass().toString());
		//测试代理接口 结论: CGLib
		//修改代码内容, 给RealHouseSubject 添加@Component
//		HouseSubject bean1 = (HouseSubject)context.getBean("realHouseSubject");
//		System.out.println(bean1.getClass().toString());
		//把上面接口改成类, 代码不会报错
		RealHouseSubject bean1 = (RealHouseSubject)context.getBean("realHouseSubject");
		System.out.println(bean1.getClass().toString());
	}

}