目录
代理模式
定义
代理模式的主要角色
静态代理
动态代理
JDK动态代理
接口介绍
CGLIB动态代理
Spring AOP源码解析
验证
没实现接口
实现了接口
小结
Spring AOP 是基于动态代理来实现AOP的.
代理模式
代理模式, 也叫委托模式.
定义
为其他对象提供⼀种代理以控制对这个对象的访问. 它的作⽤就是通过提供⼀个代理类, 让我们在调⽤⽬标⽅法的时候, 不再是直接对⽬标⽅法进⾏调⽤, ⽽是通过代理类间接调⽤.
在某些情况下, ⼀个对象不适合或者不能直接引⽤另⼀个对象, ⽽代理对象可以在客⼾端和⽬标对象之间起到中介的作⽤.
使⽤代理前:
使用代理后:
代理模式的主要角色
1. Subject: 业务接⼝类. 可以是抽象类或者接⼝(不⼀定有)
2. RealSubject: 业务实现类. 具体的业务执⾏, 也就是被代理对象.
3. Proxy: 代理类. RealSubject的代理.
UML类图如下:
代理模式可以在不修改被代理对象的基础上, 通过扩展代理类, 进⾏⼀些功能的附加与增强.
根据代理的创建时期, 代理模式分为静态代理和动态代理.
• 静态代理: 由程序员创建代理类或特定⼯具⾃动⽣成源代码再对其编译, 在程序运⾏前代理类的.class ⽂件就已经存在了.
• 动态代理: 在程序运⾏时, 运⽤反射机制动态创建⽽成。
静态代理
静态代理: 在程序运⾏前, 代理类的 .class⽂件就已经存在了.
(以房租租赁为例,在出租房⼦之前, 中介已经做好了相关的⼯作, 就等租⼾来租房⼦了)
下面我们通过代码来加深理解:
1. 定义接⼝(定义房东要做的事情, 也是中介需要做的事情)
public interface HouseSubject {
void rentHouse();
}
2. 实现接⼝(房东出租房⼦)
public class RealHouseSubject implements HouseSubject{
@Override
public void rentHouse() {
System.out.println("我是房东, 我出租房子");
}
}
3. 代理(中介, 帮房东出租房⼦)
public class HouseProxy implements HouseSubject{
private HouseSubject target;
public HouseProxy(HouseSubject target) {
this.target = target;
}
@Override
public void rentHouse() {
//出租前
System.out.println("我是中介, 开始代理");
//出租房子
target.rentHouse();
//出租后
System.out.println("我是中介, 结束代理");
}
}
4.使用
public class Main {
public static void main(String[] args) {
//静态代理
HouseProxy proxy = new HouseProxy(new RealHouseSubject());
proxy.rentHouse();
}
}
运行结果:
上⾯这个代理实现⽅式就是静态代理.
从上述程序可以看出, 虽然静态代理也完成了对⽬标对象的代理, 但是由于代码都写死了, 对⽬标对象的每个⽅法的增强都是⼿动完成的,⾮常不灵活. 所以⽇常开发⼏乎看不到静态代理的场景.
接下来新增需求: 中介⼜新增了其他业务: 代理房屋出售,此时我们需要对上述代码进⾏修改:
1. 接⼝定义修改
public interface HouseSubject {
void rentHouse();
void saleHouse();
}
2.接口实现修改
public class RealHouseSubject implements HouseSubject{
@Override
public void rentHouse() {
System.out.println("我是房东, 我出租房子");
}
@Override
public void saleHouse() {
System.out.println("我是房东, 我出售房子");
}
}
3.代理类修改
public class HouseProxy implements HouseSubject{
private HouseSubject target;
public HouseProxy(HouseSubject target) {
this.target = target;
}
@Override
public void rentHouse() {
//出租前
System.out.println("我是中介, 开始代理");
//出租房子
target.rentHouse();
//出租后
System.out.println("我是中介, 结束代理");
}
@Override
public void saleHouse() {
//出租前
System.out.println("我是中介, 开始代理");
//出租房子
target.saleHouse();
//出租后
System.out.println("我是中介, 结束代理");
}
}
从上述代码可以看出, 我们修改接⼝和业务实现类时, 还需要修改代理类.
同样的, 如果有新增接⼝和业务实现类, 也需要对每⼀个业务实现类新增代理类.
既然代理的流程是⼀样的, 有没有⼀种办法, 让他们通过⼀个代理类来实现呢?
这就需要⽤到动态代理技术了.
动态代理
相⽐于静态代理来说,动态代理更加灵活.
我们不需要针对每个⽬标对象都单独创建⼀个代理对象, ⽽是把这个创建代理对象的⼯作推迟到程序运⾏时由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) ⽅法创建代理对象
1.定义一个接口
public interface HouseSubject {
void rentHouse();
void saleHouse();
}
2.定义一个实现类
public class RealHouseSubject implements HouseSubject{
@Override
public void rentHouse() {
System.out.println("我是房东, 我出租房子");
}
@Override
public void saleHouse() {
System.out.println("我是房东, 我出售房子");
}
}
3.实现 InvocationHandler 接口并重写 invoke 方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {
private Object target;//目标对象
public JDKInvocationHandler(Object target) {
this.target = target;
}
@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;
}
}
4.创建一个代理对象并使用
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
//JDK动态代理
//目标对象
RealHouseSubject target = new RealHouseSubject();
//动态生成代理对象 代理接口, 运行成功
HouseSubject proxy= (HouseSubject) Proxy.newProxyInstance(
HouseSubject.class.getClassLoader(),
new Class[]{HouseSubject.class},
new JDKInvocationHandler(target));
proxy.rentHouse();
proxy.saleHouse();
}
}
运行结果:
接口介绍
InvocationHandler
InvocationHandler 接⼝是Java动态代理的关键接⼝之⼀, 它定义了⼀个单⼀⽅法 invoke() , ⽤于
处理被代理对象的⽅法调⽤.
public interface InvocationHandler {
/**
* 参数说明
* proxy:代理对象
* method:代理对象需要实现的⽅法,即其中需要重写的⽅法
* args:method所对应⽅法的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
通过实现 InvocationHandler 接⼝, 可以对被代理对象的⽅法进⾏功能增强.
Proxy
Proxy 类中使⽤频率最⾼的⽅法是: newProxyInstance() , 这个⽅法主要⽤来⽣成⼀个代理
对象.
这个⽅法⼀共有 3 个参数:
Loader: 类加载器, ⽤于加载代理对象.
interfaces : 被代理类实现的⼀些接⼝(这个参数的定义, 也决定了JDK动态代理只能代理实现了接⼝的⼀些类)
h : 实现了 InvocationHandler 接⼝的对象
如果让JDK代理类的话
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
//JDK动态代理
//目标对象
RealHouseSubject target = new RealHouseSubject();
//JDK代理类
RealHouseSubject proxy= (RealHouseSubject)Proxy.newProxyInstance(
RealHouseSubject.class.getClassLoader(),
new Class[]{RealHouseSubject.class},
new JDKInvocationHandler(target));
proxy.rentHouse();
proxy.saleHouse();
}
}
运行结果:
CGLIB动态代理
JDK 动态代理有⼀个最致命的问题是其只能代理实现了接⼝的类.
有些场景下, 我们的业务代码是直接实现的, 并没有接⼝定义. 为了解决这个问题, 我们可以⽤ CGLIB 动态代理机制来解决.
CGLIB(Code Generation Library)是⼀个基于ASM的字节码⽣成库,它允许我们在运⾏时对字节码进⾏修改和动态⽣成. CGLIB 通过继承⽅式实现代理, 很多知名的开源框架都使⽤到了CGLIB. 例如 Spring中的 AOP 模块中: 如果⽬标对象实现了接⼝,则默认采⽤ JDK 动态代理, 否则采⽤ CGLIB 动态代理.
CGLIB动态代理类实现步骤
1. 定义⼀个类(被代理类)
2. ⾃定义 MethodInterceptor 并重写 intercept ⽅法, intercept ⽤于增强⽬标⽅
法,和 JDK 动态代理中的 invoke ⽅法类似
3. 通过 Enhancer 类的 create()创建代理类
代码示例
HouseSubject
public interface HouseSubject {
void rentHouse();
void saleHouse();
}
RealHouseSubject
public class RealHouseSubject implements HouseSubject{
@Override
public void rentHouse() {
System.out.println("我是房东, 我出租房子");
}
@Override
public void saleHouse() {
System.out.println("我是房东, 我出售房子");
}
}
CGLibMethodInterceptor
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(Object target) {
this.target = target;
}
/**
* 调用代理对象的方法
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("我是中介, 开始代理");
Object result = method.invoke(target, args);
System.out.println("我是中介, 结束代理");
return result;
}
}
Main
import org.springframework.cglib.proxy.Enhancer;
public class Main {
public static void main(String[] args) {
// 使用CGLib 完成代理
//目标对象
HouseSubject target = new RealHouseSubject();
//代理接口
HouseSubject houseSubject = (HouseSubject)Enhancer.create(target.getClass(), new CGLibMethodInterceptor(target));
houseSubject.rentHouse();
houseSubject.saleHouse();
}
}
运行结果:
Main
import org.springframework.cglib.proxy.Enhancer;
public class Main {
public static void main(String[] args) {
// 使用CGLib 完成代理
//目标对象
HouseSubject target = new RealHouseSubject();
//代理类
RealHouseSubject houseSubject = (RealHouseSubject) Enhancer.create(target.getClass(), new CGLibMethodInterceptor(target));
houseSubject.rentHouse();
houseSubject.saleHouse();
}
}
运行结果:
接口介绍
MethodInterceptor
参数说明:
o: 被代理的对象
method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法)
objects: ⽅法⼊参
methodProxy: ⽤于调⽤原始⽅法
Enhancer.create()
Enhancer.create() ⽤来⽣成⼀个代理对象。
参数说明:
argumentTypes:被代理类的类型(类或接口)
arguments:自定义方法拦截器 MethosInterceptor
Spring AOP源码解析
Spring AOP 主要基于两种⽅式实现的: JDK 及 CGLIB 的⽅式。
Spring对于AOP的实现,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成
⽣成代理对象的逻辑在⽗类 AbstractAutoProxyCreator 中。
在上面的代码中有⼀个重要的属性: proxyTargetClass, 默认值为false. 也可以通过程序设置。
| proxyTargetClass | 目标对象 | 代理方式 |
| false | 实现了接口 | JDK代理 |
| false | 未实现接口(只有实现类) | CGLIB代理 |
| true | 实现了接口 | CGLIB代理 |
| true | 未实现接口(只有实现类) | CGLIB代理 |
注意
Spring Boot 2.X开始, 默认使⽤CGLIB代理
可以通过配置项 spring.aop.proxy-target-class=false 来进⾏修改,设置默认为jdk代理,原来可以使用 @EnableAspectJAutoProxy(proxyTargetClass = true) 来设置,但是SpringBoot设置 @EnableAspectJAutoProxy ⽆效, 因为Spring Boot 默认使⽤AopAutoConfiguration进⾏装配
验证
没实现接口
TestController
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
@MyAspect
@RequestMapping("/t1")
public String t1(){
log.info("执行t1方法...");
return "t1";
}
}
SpringAopApplication
import ***.wmh.springaop.controller.TestController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class SpringAopApplication {
public static void main(String[] args) {
ApplicationContext context=SpringApplication.run(SpringAopApplication.class, args);
TestController bean = context.getBean(TestController.class);
System.out.println(bean);
}
}
设置配置项 spring.aop.proxy-target-class=false时
Debug模式下查看到的:
设置配置项 spring.aop.proxy-target-class=true时
Debug模式下查看到的:
实现了接口
Iface
public interface Iface {
void test();
}
MyAspect
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}
TestController2
import ***.wmh.springaop.config.MyAspect;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController2 implements Iface{
@MyAspect
@RequestMapping("/test1")
public void test() {
System.out.println("测试");
}
}
SpringAopApplication
import ***.wmh.springaop.controller.Iface;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class SpringAopApplication {
public static void main(String[] args) {
ApplicationContext context=SpringApplication.run(SpringAopApplication.class, args);
Iface iface=(Iface)context.getBean("testController2");
System.out.println(iface);
}
}
设置配置项 spring.aop.proxy-target-class=false时
Debug模式下查看到的:
设置配置项 spring.aop.proxy-target-class=true时
Debug模式下查看到的:
小结
Spring 默认proxyTargetClass 为false,实现了接口,使用JDK代理,未实现接口,使用CGLIB代理
SpringBoot 默认proxyTargetClass 为true,默认使用 CGLIB代理
但是可以通过设置配置项spring.aop.proxy-target-class指定proxyTargetClass。