目录
(3)自定义MethodInterceptor(方法拦截器)
上篇文章学习了 Spring AOP 的应用,接下来我们来学习 Spring AOP 的原理,也就是 Spring 是如何实现 AOP 的。
Spring AOP 是基于动态代理来实现 AOP 的,咱门学习内容主要分以下两部分:
1、代理模式
2、Spring AOP 源码 剖析
一、代理模式
代理模式,也叫 委托模式。
定义:为其他对象提供一种代理以控制这个对象的访问。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。
某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。
使用代理前:
使用代理后:
生活中的代理:
艺人经纪人:广告商找艺人拍广告,需要经过经纪人,由经纪人来和艺人沟通。
房屋中介:房屋进行租赁时,卖方会把房屋授权给中介,由中介来代理看房,房屋咨询等服务。
经销商:厂商不直接对外销售产品,由经销商负责代理销售。
秘书/助理:合作伙伴找老板谈合作,需要先经过秘书/助理预约。
代理模式的主要角色:
1、Subject:业务接口类。可以是抽象类或者接口(不一定有)。
2、RealSubject:业务实现类。具体的业务执行,也就是被代理对象。
3、Proxy:代理类。RealSubject的代理。
比如 房屋出租:
Subject:就是提前定义了房东做的事情,交给中介代理,也是中介要做的事情。
RealSubject:房东。
Proxy:中介。
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) {
HouseSubject subject = new RealHouseSubject();
//创建代理类
HouseProxy houseProxy = new HouseProxy(subject);
//通过代理类访问⽬标⽅法
houseProxy.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.rentHouse();;
//代理后
System.out.println("我是中介, 结束代理");
}
}
4、使用:
public class Main {
public static void main(String[] args) {
HouseSubject subject = new RealHouseSubject();
//创建代理类
HouseProxy houseProxy = new HouseProxy(subject);
//通过代理类访问⽬标⽅法
houseProxy.rentHouse();
System.out.println("=========");
houseProxy.saleHouse();
}
}
运行结果:
从上述代码可以看出,我们修改接口(Subject)和业务实现类(RealSubject),还需要修改代理类(Proxy)。
同样的,如果新增接口(Subject)和业务实现类(RealSubject),也需要对每一个业务实现类新增代理类(Proxy)。
既然代理的流程是一样的,有没有一种办法,让他们通过一个代理类来实现呢?这就需要用到动态代理技术了。
三、动态代理
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标对象都单独创建一个代理对象,而是把这个创建代理对象的工作推迟到程序运行时由JVM来实现。也就是说动态代理在程序运行时,根据需要动态创建生成。
比如房屋中介,我不需要提前预测都有哪些业务,而是业务来了我再根据情况创建。
先看代码再来理解。Java也对动态代理进行了实现,并给我们提供一些API,常见的实现方式有两种:
1、JDK动态代理
2、CGLIB动态代理
动态代理在我们日常开发中使用的相对较少,但是在框架中几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。
1、JDK动态代理
(1)JDK动态代理实现步骤
1、定义一个接口及其实现类(静态代理中的 HouseSubject 和 RealHouseSubject)。
2、自定义 InvocationHandler 并重写 invoke 方法,在 invoke 方法中我们会调用目标方法(被代理类的方法),并自定义一些处理逻辑。
3、通过 Proxy.newProxyInstance(ClassLoader, Class<?>[ ] Interfaces, InvocationHandler h)方法创建代理对象。
(2)定义JDK动态代理类
创建 JDKInvocationHandler类 实现 InvocationHandler 接口:
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;
}
}
创建一个代理对象并使用:
public class Main {
public static void main(String[] args) {
/**
* JDK动态代理
*/
//创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
RealHouseSubject target = new RealHouseSubject();//目标对象
/**
* newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
* loader:加载我们的被代理类的ClassLoad
* interfaces:要实现的接口
* h:代理要做的事情,需要实现 InvocationHandler 这个接口
*/
HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{HouseSubject.class},
new JDKInvocationHandler(target)
);
proxy.rentHouse();
System.out.println("==============");
proxy.saleHouse();
}
}
运行程序,结果如下:
假设代理的是类,而不是对象,代码如下:
public class Main {
public static void main(String[] args) {
RealHouseSubject target = new RealHouseSubject();
RealHouseSubject proxy = (RealHouseSubject) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{RealHouseSubject.class},
new JDKInvocationHandler(target)
);
proxy.rentHouse();
System.out.println("==============");
proxy.saleHouse();
}
}
运行程序,结果如下:(报错了)
报错原因:RealHouseSubject is not an interface(RealHouseSubject 类不是接口),说明JDK 动态代理只能代理接口,不能代理类,不然会报错。
(3)代码简单讲解
主要是学习API的使用,我们按照 Java API 的规范来使用即可。
1、InvocationHandler:
InvocationHandler 接口是 Java 动态代理的关键接口之一,它定义了一个单一方法 invoke(),用于处理被代理对象的方法调用。
public interface InvocationHandler {
/**
* 参数说明
* proxy:被代理对象
* method:被代理对象需要实现的⽅法,即其中需要重写的⽅法
* args:method所对应⽅法的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
通过实现 InvocationHandler 接口,可以对被代理对象的方法进行功能增强。
2、Proxy:
Proxy 类中使用频率最高的方法:newProxyInstance(),这个方法主要用来生成一个代理对象。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//...代码省略
}
这个方法一共有 3 个参数:
loader:类加载器,用于加载被代理对象。
interface:被代理类实现的一些接口(这个参数的定义,也决定了JDK动态代理只能代理实现了接口的一些类)。
h:代理要做的事情,实现 InvocationHandler 接口的对象。
2、CGLIB动态代理
JDK动态代理有一个最致命的问题,是只能代理实现了接口的类。
有些场景下,我们的业务码是直接实现的,并没有接口定义。为了解决这个问题,我们可以用 CGLIB 动态代理机制来解决。
CGLIB(Code Generation Library)是一个基于 ASM 的字节码生产库,它允许我们在运行时对字节码进行修改和动态生成。
CGLIB 通过继承方式实现代理,很多知名的开源框架都使用到了 CGLIB。例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。(其中 Spring 是基于动态代理实现的,动态代理是基于反射实现的)
(1)CGLIB 动态代理类实现步骤
1、定义一个类(被代理类)。
2、自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于增强目标方法,和 JDK 动态代理中的 invoke 方法类似。
3、通过 Enhancer 类的 create() 创建代理类。
接下来看实现:
(2)添加依赖
和 JDK 动态代理不同,CGLIB(Code Generation Library)实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
(3)自定义MethodInterceptor(方法拦截器)
实现 MethodInterceptor 接口:
import org.springframework.cglib.proxy.MethodInterceptor;
import java.lang.reflect.Method;
public class CGLibInterceptor implements MethodInterceptor {
private Object target;
public CGLibInterceptor(Object target) {
this.target = target;
}
/**
* 调用代理对象的方法
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, org.springframework.cglib.proxy.MethodProxy proxy) throws Throwable {
//代理增强内容
System.out.println("我是中介,开始代理");
Object result = method.invoke(target, args);
//代理增强内容
System.out.println("我是中介,结束代理");
return result;
}
}
(4)创建代理类,并使用
代理接口:
public class Main {
public static void main(String[] args) {
//目标对象
HouseSubject target = new RealHouseSubject();
HouseSubject proxy = (HouseSubject) Enhancer.create(target.getClass(), new CGLibInterceptor(target));
proxy.rentHouse();
System.out.println("=============");
proxy.saleHouse();
}
}
运行程序,执行结果如下:
代理类:
public class Main {
public static void main(String[] args) {
//目标对象
HouseSubject target = new RealHouseSubject();
RealHouseSubject proxy = (RealHouseSubject) Enhancer.create(target.getClass(), new CGLibInterceptor(target));
proxy.rentHouse();
System.out.println("=============");
proxy.saleHouse();
}
}
运行程序,执行结果如下:
(5)代码简单讲解
1、MethodInterceptor:
MethodInterceptor 和 JDK动态代理中的 InvocationHandler 类似,它只定义了一个方法 intercept(),用于增强目标方法。
public interface MethodInterceptor extends Callback {
/**
* 参数说明:
* o: 被代理的对象
* method: ⽬标⽅法(被拦截的⽅法, 也就是需要增强的⽅法)
* objects: ⽅法⼊参
* methodProxy: ⽤于调⽤原始⽅法
*/
Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}
2、Enhancer.create():
public static Object create(Class type, Callback callback) {
//...代码省略
}
type:被代理类的类型(类或接口)
callback:自定义方法拦截器 MethodInterceptor
四、Spring AOP 源码剖析(了解)
Spring AOP 主要基于两种方式实现的:JDK 及 CGLIB 的方式。
Spring 源码过于复杂,我们只摘出一些主要内容,以了解为主
Spring 对于 AOP 的实现,基本都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成 生成代理对象的逻辑在父类 AbstractAutoProxyCreator 中。
protected Object createProxy(Class<?> beanClass,@Nullable String beanName,
@Nullable Object[]specificInterceptors,TargetSource targetSource){
if(this.beanFactory instanceof ConfigurableListableBeanFactory){
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)
this.beanFactory,beanName,beanClass);
}
//创建代理⼯⼚
ProxyFactory proxyFactory=new ProxyFactory();
proxyFactory.copyFrom(this);
/**
* 检查proxyTargetClass属性值,spring默认为false
* proxyTargetClass 检查接⼝是否对类代理, ⽽不是对接⼝代理
* 如果代理对象为类, 设置为true, 使⽤cglib代理
*/
if(!proxyFactory.isProxyTargetClass()){
//是否有设置cglib代理
if(shouldProxyTargetClass(beanClass,beanName)){
//设置proxyTargetClass为true,使⽤cglib代理
proxyFactory.setProxyTargetClass(true);
}else{
/**
* 如果beanClass实现了接⼝,且接⼝⾄少有⼀个⾃定义⽅法,则使⽤JDK代理
* 否则CGLIB代理(设置ProxyTargetClass为true )
* 即使我们配置了proxyTargetClass=false, 经过这⾥的⼀些判断还是可能会将其
设为true
*/
evaluateProxyInterfaces(beanClass,proxyFactory);
}
}
Advisor[]advisors=buildAdvisors(beanName,specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if(advisorsPreFiltered()){
proxyFactory.setPreFiltered(true);
}
// Use original ClassLoader if bean class not locally loaded in overriding class loader
ClassLoader classLoader =getProxyClassLoader();
if(classLoader instanceof SmartClassLoader&&classLoader != beanClass.getClassLoader()){
classLoader=((SmartClassLoader)classLoader).getOriginalClassLoader();
}
//从代理⼯⼚中获取代理
return proxyFactory.getProxy(classLoader);
}
代理工厂有一个重要的属性:proxyTargetClass,默认值为false。也可以通过程序设置
proxyTargetClass | ⽬标对象 | 代理⽅式 |
false | 实现了接口 | jdk代理 |
false | 未实现接口(只有实现类) | cglib代理 |
true | 实现了接口 | cglib代理 |
true | 未实现接口(只有实现类) | cglib代理 |
可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 来设置。
注意:
Spring 默认 proxyTargetClass:false,会分为两种情况:
实现了接口:使用 JDK 代理。
普通类: 使用 CGLIB 代理。
Spring Boot 2.X 开始,默认使用 proxyTargetClass:true
默认使用 CGLIB 代理
SpringBoot设置 @EnableAspectJAutoProxy 无效,因为 Spring Boot 默认使用 AopAutoConfiguration 进行装配。
可以通过配置项 spring.aop.proxy-target-class=false 来进行修改,设置为 jdk 代理。
使用 context.getBean() 需要添加注解,使 HouseProxy,RealHouseSubject 被 Spring 管理。测试 AOP 代理,需要把这些类交给 AOP 管理(自定义注解或使用 @Aspect)
我们现在从源码中点进去看看代理工厂的代码:
public class ProxyFactory extends ProxyCreatorSupport {
//...代码省略
//获取代理
public Object getProxy(@Nullable ClassLoader classLoader) {
//分两步 先createAopProxy,后getProxy
return createAopProxy().getProxy(classLoader);
}
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}
//...代码省略
}
createAopProxy 的实现在 DefaultAopProxyFactory 中
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
//...代码省略
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws
AopConfigException {
/**
* 根据proxyTargetClass判断
* 如果⽬标类是接⼝, 使⽤JDK动态代理
* 否则使⽤cglib动态代理
*/
if (!NativeDetector.inNativeImage() &&
(config.isOptimize() || config.isProxyTargetClass() ||
hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine
target class:" +
"Either an interface or a target is required for proxy
creation. ");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) ||
ClassUtils.isLambdaClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
} else {
return new JdkDynamicAopProxy(config);
}
}
//...代码省略
}
接下来就是创建代理了
JDK动态代理:
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
//...代码省略
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " +
this.advised.getTargetSource());
}
return Proxy.newProxyInstance(determineClassLoader(classLoader),
this.proxiedInterfaces, this);
}
//...代码省略
}
CGLIB动态代理:
class CglibAopProxy implements AopProxy, Serializable {
//...代码省略
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
//...代码省略
// Configure CGLIB Enhancer...
Enhancer enhancer = createEnhancer();
// Generate the proxy class and create a proxy instance.
return createProxyClassAndInstance(enhancer, callbacks);
}
//...代码省略
}
五、常见面试题
1、什么是 AOP?
AOP是 面向切面编程,也是一种思想,切面指的是某一类特定问题,所以 AOP 也可以理解为 面向切面编程。
2、Spring AOP的实现方式有哪些?
(1)基于注解(@Aspect 或 自定义注解)
(2)基于 xml
(3)基于代理
3、Spring AOP 的实现原理?
基于动态代理实现的,其中的动态代理有两种形式:(1)JDK (2)CGLIB
4、Spring 使用的是哪种代理方式?
Spring 的 proxyTargetClass 默认为:false,其中:实现了接口,使用 JDK 代理;普通类:使用CGLIB代理。
Spring Boot 从 2.X 之后,proxyTargetClass 默认为:true,默认使用 CGLIB 代理
现在我们测试一下:当这个值为 true 时,则使用的是动态代理
@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管理的对象,打断点,观察对象名称,如图:
可以看到,是 CGLIB代理。
当值设为 false 时;就要看代理对象是不是接口了,是接口用的就是JDK代理,代理对象是类就是CGLIB代理。
因为要代理接口,所以现在重新创建一个接口,内容如下:
public interface IFace { void test(); }
再创建一个成 Controller 类,实现上面这个类
@RequestMapping("/test2") @RestController public class TestController2 implements IFace{ @MyAspect @RequestMapping("t1") @Override public void test() { System.out.println("测试测试"); } }
main方法如下:
@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); //代理接口 IFace iFace = (IFace) context.getBean("testController2"); System.out.println(iFace); } }
现在看看Spring对象,任然要使用断点才能看到,如图:
看看bean对象,如图:
5、JDK 和 CGLIB 的区别?
使用JDK 动态代理只能代理接口。
使用 CGLIB 动态代理 既可以代理接口,也可以代理类。
六、总结
1、AOP 是一种思想,是对某一类事情的集中处理。Spring 框架实现了AOP,称之为 Spring AOP。
2、Spring AOP 场景的实现方式有两种:(1)基于注解@Aspect来实现。(2)基于自定义注解来实现,还有一些更原始的方式,比如基于代理、基于 xml 配置的方式,但目标比较少见。
3、Spring AOP 是基于动态代理实现的,有两种方式:(1)基于 JDK 动态代理实现。(2)基于 CGLIB 动态代理实现。运行时使用哪种方式与项目配置的代理对象有关。