MiniSpring框架学习笔记-动态代理:如何在运行时插入逻辑?
- [17. 动态代理:如何在运行时插入逻辑?](#17. 动态代理:如何在运行时插入逻辑?)
-
- 一、先理解代理是什么
- [二、JDK 动态代理怎么用](#二、JDK 动态代理怎么用)
- [三、用 FactoryBean 把代理交给容器](#三、用 FactoryBean 把代理交给容器)
- [四、FactoryBean 接口](#四、FactoryBean 接口)
- [五、BeanFactory 里怎么识别 FactoryBean](#五、BeanFactory 里怎么识别 FactoryBean)
- 六、ProxyFactoryBean:生产代理对象
- [七、抽象 AopProxy](#七、抽象 AopProxy)
- [八、JdkDynamicAopProxy:JDK 代理实现](#八、JdkDynamicAopProxy:JDK 代理实现)
- 九、最终使用方式
- 十、三层实现思路总结
- 十一、学完后要能回答的问题
教程: https://github.com/YaleGuo/minis
极客时间: 手把手带你写一个 MiniSpring
17. 动态代理:如何在运行时插入逻辑?
前面几章里,MiniSpring 已经有了 IoC、MVC、JDBC 模板和 SQL 配置化。接下来如果想继续理解 Spring 里的 AOP,就需要先补一块很重要的前置知识:代理,尤其是 JDK 动态代理。
AOP 是面向切面编程,全称是 Aspect Oriented Programming。它想解决的问题很直观:日志、权限检查、事务控制、性能监控这类代码,通常会散落在很多业务方法前后。如果每个业务方法都手写一遍,代码会越来越乱。
AOP 的核心想法是:
text
业务方法只写业务逻辑
↓
日志、权限、事务等通用逻辑抽出来
↓
程序运行时,把这些通用逻辑插到业务方法前后
这一节先不做完整 AOP,只讲它最关键的一种实现手段:动态代理。
本节主线是:
text
先理解代理模式
↓
再看 JDK 动态代理怎么用
↓
最后把动态代理接入 MiniSpring 容器
一、先理解代理是什么
代理的意思是:调用方不直接访问真实对象,而是先访问一个代理对象,再由代理对象去调用真实对象。
用生活里的例子看:
text
你
↓
房屋中介
↓
房东
你真正想找的是房东,但中介可以在中间做一些额外的事情,比如预约看房、准备合同、沟通价格。放到代码里也是一样,业务对象负责真正的业务,代理对象负责在调用前后加一些通用逻辑。
假设我们有一个业务接口:
java
package com.test.service;
public interface IAction {
void doAction();
}
真实业务类只关心业务本身:
java
package com.test.service;
public class Action1 implements IAction {
@Override
public void doAction() {
System.out.println("do real action");
}
}
如果用静态代理,可以手写一个代理类:
java
package com.test.service;
/**
* 静态代理:代理类提前写好,并且和真实类实现同一个接口。
*/
public class ActionProxy implements IAction {
private final IAction target;
public ActionProxy(IAction target) {
this.target = target;
}
@Override
public void doAction() {
System.out.println("before call real object");
this.target.doAction();
}
}
它们的关系可以画成这样:
text
IAction
^
|
+---------+---------+
| |
Action1 ActionProxy
真实业务对象 静态代理对象
|
| target
v
Action1
调用方只面向 IAction:
java
IAction action = new ActionProxy(new Action1());
action.doAction();
静态代理很好理解,但缺点也明显:如果很多类都要加日志、事务、权限检查,我们就要为很多真实类手写对应的代理类。通用逻辑还没真正通用起来。
所以我们需要动态代理:代理类不用提前手写,而是在程序运行期间由 JVM 动态生成。
二、JDK 动态代理怎么用
JDK 提供的动态代理入口是:
java
Proxy.newProxyInstance(
类加载器,
要代理的接口数组,
方法调用处理器
)
三个参数分别表示:
| 参数 | 作用 |
|---|---|
ClassLoader |
用哪个类加载器生成代理类 |
Class<?>[] interfaces |
代理对象要实现哪些接口 |
InvocationHandler |
代理对象的方法被调用时,统一交给谁处理 |
重点是第三个参数 InvocationHandler。只要调用代理对象的方法,JDK 都会转进它的 invoke(...) 方法:
java
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
可以把调用链理解成:
text
调用 proxyAction.doAction()
↓
进入 InvocationHandler.invoke(...)
↓
执行增强逻辑
↓
通过反射调用 target.doAction()
先写一个最直接的动态代理类:
java
package com.test.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 教学版动态代理。
*
* 传入一个真实对象,返回一个实现相同接口的代理对象。
*/
public class DynamicProxy {
private final Object target;
public DynamicProxy(Object target) {
if (target == null) {
throw new IllegalArgumentException("Target must not be null");
}
this.target = target;
}
public Object getProxy() {
Class<?>[] interfaces = this.target.getClass().getInterfaces();
if (interfaces.length == 0) {
throw new IllegalStateException(
"JDK dynamic proxy requires at least one interface");
}
return Proxy.newProxyInstance(
this.target.getClass().getClassLoader(),
interfaces,
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ("doAction".equals(method.getName())) {
System.out.println("before call real object");
}
return invokeTarget(method, args);
}
});
}
private Object invokeTarget(Method method, Object[] args) throws Throwable {
try {
return method.invoke(this.target, args);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
这里有两个点要注意。
第一,JDK 动态代理只能代理接口。也就是说,Action1 必须实现 IAction,最终拿到的代理对象也应该转成 IAction 使用。
第二,invoke(...) 里不能只处理 doAction,其他方法就直接返回 null。代理对象上可能还会调用 toString()、equals(...),或者接口里以后新增的方法。更稳妥的写法是:需要增强的方法加增强,不需要增强的方法也继续转发给真实对象。
在 Controller 里可以这样手动使用:
java
package com.test.controller;
import com.minis.beans.factory.Autowired;
import com.minis.web.RequestMapping;
import com.test.service.DynamicProxy;
import com.test.service.IAction;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloWorldBean {
@Autowired
private IAction action;
@RequestMapping("/testaop")
public void doTestAop(HttpServletRequest request,
HttpServletResponse response) {
IAction proxyAction = (IAction) new DynamicProxy(this.action).getProxy();
proxyAction.doAction();
try {
response.getWriter().write("test aop, hello world!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
这段代码能跑通动态代理,但它有一个问题:业务代码侵入太强。
调用方本来只想写:
java
action.doAction();
结果现在必须手动写:
java
DynamicProxy proxy = new DynamicProxy(action);
IAction proxyAction = (IAction) proxy.getProxy();
proxyAction.doAction();
这就不像框架了。我们更希望业务代码看起来还是直接调用接口,至于拿到的是普通对象还是代理对象,由 MiniSpring 容器帮我们决定。
三、用 FactoryBean 把代理交给容器
为了让业务代码直接注入代理对象,我们可以引入 FactoryBean。
普通 Bean 的逻辑是:
text
getBean("action")
↓
返回 action 这个 Bean 本身
FactoryBean 的逻辑多了一层:
text
getBean("action")
↓
发现 action 是一个 FactoryBean
↓
调用 factoryBean.getObject()
↓
返回 getObject() 生产出来的对象
在这一节里,action 这个 Bean 本身是 ProxyFactoryBean,但调用方真正拿到的是它创建出来的动态代理对象。
XML 可以这样配置:
xml
<bean id="realaction" class="com.test.service.Action1"/>
<bean id="action" class="com.minis.aop.ProxyFactoryBean">
<property type="java.lang.Object" name="target" ref="realaction"/>
</bean>
这里有两个 Bean:
| Bean id | 实际类型 | 作用 |
|---|---|---|
realaction |
Action1 |
真实业务对象 |
action |
ProxyFactoryBean |
生产 realaction 的代理对象 |
因为当前 MiniSpring 的 @Autowired 是按字段名找 Bean,所以 Controller 里字段名写成 action,就会拿到 action 这个 Bean 对应的对象。由于 action 是 FactoryBean,最终注入进去的是代理对象。
四、FactoryBean 接口
FactoryBean 的接口很小:
java
package com.minis.beans.factory;
public interface FactoryBean<T> {
/**
* 返回这个工厂真正生产出来的对象。
*/
T getObject() throws Exception;
/**
* 返回生产对象的类型,当前教学版可以先用于说明和调试。
*/
Class<?> getObjectType();
/**
* 默认按单例处理,也就是同一个 FactoryBean 只创建一个产品对象。
*/
default boolean isSingleton() {
return true;
}
}
它和普通工厂类的区别在于:容器认识 FactoryBean,并且会在 getBean(...) 时对它做特殊处理。
五、BeanFactory 里怎么识别 FactoryBean
AbstractBeanFactory.getBean(...) 原来的职责是创建普通 Bean。现在它需要在返回前多判断一步:如果当前 Bean 是 FactoryBean,就返回它生产出来的对象。
只看关键逻辑,可以整理成这样:
java
package com.minis.beans.factory.support;
import com.minis.beans.BeansException;
import com.minis.beans.factory.FactoryBean;
import com.minis.beans.factory.config.BeanDefinition;
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport
implements ConfigurableBeanFactory, BeanDefinitionRegistry {
@Override
public Object getBean(String beanName) throws BeansException {
Object singleton = this.getSingleton(beanName);
if (singleton == null) {
BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
if (beanDefinition == null) {
return null;
}
singleton = createBean(beanDefinition);
this.registerBean(beanName, singleton);
applyBeanPostProcessorsBeforeInitialization(singleton, beanName);
invokeInitMethodIfNecessary(beanDefinition, singleton);
applyBeanPostProcessorsAfterInitialization(singleton, beanName);
}
return getObjectForBeanInstance(singleton, beanName);
}
protected Object getObjectForBeanInstance(Object beanInstance,
String beanName) {
if (!(beanInstance instanceof FactoryBean)) {
return beanInstance;
}
FactoryBean<?> factoryBean = (FactoryBean<?>) beanInstance;
return getObjectFromFactoryBean(factoryBean, beanName);
}
private void invokeInitMethodIfNecessary(BeanDefinition beanDefinition,
Object bean) {
String initMethodName = beanDefinition.getInitMethodName();
if (initMethodName != null && !initMethodName.isEmpty()) {
invokeInitMethod(beanDefinition, bean);
}
}
}
如果 FactoryBean 生产的是单例对象,还可以在 FactoryBeanRegistrySupport 里做一层缓存:
java
package com.minis.beans.factory.support;
import com.minis.beans.factory.FactoryBean;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public abstract class FactoryBeanRegistrySupport
extends DefaultSingletonBeanRegistry {
private final Map<String, Object> factoryBeanObjectCache =
new ConcurrentHashMap<>();
protected Object getObjectFromFactoryBean(FactoryBean<?> factory,
String beanName) {
if (factory.isSingleton()) {
return this.factoryBeanObjectCache.computeIfAbsent(
beanName, key -> doGetObjectFromFactoryBean(factory, key));
}
return doGetObjectFromFactoryBean(factory, beanName);
}
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory,
String beanName) {
try {
return factory.getObject();
} catch (Exception ex) {
throw new IllegalStateException(
"FactoryBean getObject failed: " + beanName, ex);
}
}
}
这样 getBean("action") 的结果就不是 ProxyFactoryBean 本身,而是它生产出来的代理对象。
六、ProxyFactoryBean:生产代理对象
ProxyFactoryBean 是这一节最核心的类。它自己是一个 Bean,但它返回给业务层的是代理对象。
java
package com.minis.aop;
import com.minis.beans.factory.FactoryBean;
/**
* 用 FactoryBean 把代理对象交给 IoC 容器管理。
*/
public class ProxyFactoryBean implements FactoryBean<Object> {
private AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory();
/**
* 真实业务对象,例如 Action1。
*/
private Object target;
/**
* 缓存生成好的代理对象。
*/
private Object singletonInstance;
public void setAopProxyFactory(AopProxyFactory aopProxyFactory) {
if (aopProxyFactory == null) {
throw new IllegalArgumentException("AopProxyFactory must not be null");
}
this.aopProxyFactory = aopProxyFactory;
}
public Object getTarget() {
return this.target;
}
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object getObject() {
return getSingletonInstance();
}
@Override
public Class<?> getObjectType() {
if (this.target == null) {
return null;
}
Class<?>[] interfaces = this.target.getClass().getInterfaces();
return interfaces.length > 0 ? interfaces[0] : this.target.getClass();
}
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.singletonInstance = createAopProxy().getProxy();
}
return this.singletonInstance;
}
private AopProxy createAopProxy() {
if (this.target == null) {
throw new IllegalStateException("Target must be configured");
}
return this.aopProxyFactory.createAopProxy(this.target);
}
}
它的流程是:
text
容器创建 ProxyFactoryBean
↓
给 target 注入 realaction
↓
业务代码 getBean("action")
↓
容器发现它是 FactoryBean
↓
调用 ProxyFactoryBean.getObject()
↓
创建并返回 JDK 动态代理对象
七、抽象 AopProxy
上面已经能通过 JDK 实现动态代理了,但 JDK 动态代理只支持接口。如果以后要支持没有接口的类,就需要换一种代理方式,比如 CGLIB 这类基于子类的代理。
真实 Spring AOP 会根据情况选择不同代理方式。简化到 MiniSpring 里,我们先抽象出两个接口:
java
package com.minis.aop;
public interface AopProxy {
Object getProxy();
}
java
package com.minis.aop;
public interface AopProxyFactory {
AopProxy createAopProxy(Object target);
}
这样 ProxyFactoryBean 不直接依赖 JDK 的 Proxy.newProxyInstance(...),而是依赖 AopProxyFactory:
text
ProxyFactoryBean
↓
AopProxyFactory
↓
AopProxy
↓
JdkDynamicAopProxy
这一层抽象的意义是:当前只实现 JDK 动态代理,以后如果要加其他代理方式,只需要新增一个 AopProxy 实现。
真实 Spring AOP 的思路也类似,只是完整很多:
text
业务 Bean
↓
判断是否匹配切点
↓
ProxyFactory 创建代理
↓
选择 JDK 动态代理或 CGLIB 代理
↓
返回代理对象给容器
本节的 MiniSpring 先把重点放在"代理对象怎么生成、怎么交给容器"。所以增强逻辑暂时写死在 doAction 上。真实 Spring 不会这样硬编码方法名,而是用切点规则判断哪些方法需要增强,再把日志、事务等逻辑组织成拦截器链。
八、JdkDynamicAopProxy:JDK 代理实现
下面是 JDK 动态代理的封装。注意 InvocationHandler 是 JDK 提供的接口,核心方法是:
java
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
因为 JdkDynamicAopProxy 实现了 InvocationHandler,所以 Proxy.newProxyInstance(..., this) 里的 this,指的就是当前这个方法调用处理器。以后代理对象上的方法被调用,都会进入这里的 invoke(...)。
java
package com.minis.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 基于 JDK Proxy 的 AopProxy 实现。
*/
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
private final Object target;
public JdkDynamicAopProxy(Object target) {
if (target == null) {
throw new IllegalArgumentException("Target must not be null");
}
this.target = target;
}
@Override
public Object getProxy() {
Class<?>[] interfaces = this.target.getClass().getInterfaces();
if (interfaces.length == 0) {
throw new IllegalStateException(
"JDK dynamic proxy requires at least one interface");
}
return Proxy.newProxyInstance(
this.target.getClass().getClassLoader(),
interfaces,
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ("doAction".equals(method.getName())) {
System.out.println(
"before call real object, dynamic proxy");
}
return invokeTarget(method, args);
}
private Object invokeTarget(Method method, Object[] args) throws Throwable {
try {
return method.invoke(this.target, args);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
默认工厂先直接返回 JDK 代理:
java
package com.minis.aop;
public class DefaultAopProxyFactory implements AopProxyFactory {
@Override
public AopProxy createAopProxy(Object target) {
return new JdkDynamicAopProxy(target);
}
}
到这里,MiniSpring 的代理对象创建就被拆成了几个清楚的角色:
| 类 | 职责 |
|---|---|
ProxyFactoryBean |
作为容器里的 Bean,负责对外生产代理对象 |
AopProxyFactory |
决定使用哪种代理实现 |
AopProxy |
屏蔽具体代理技术 |
JdkDynamicAopProxy |
使用 JDK Proxy 创建代理对象,并处理方法调用 |
九、最终使用方式
配置仍然是:
xml
<bean id="realaction" class="com.test.service.Action1"/>
<bean id="action" class="com.minis.aop.ProxyFactoryBean">
<property type="java.lang.Object" name="target" ref="realaction"/>
</bean>
Controller 里不再手动 new DynamicProxy:
java
package com.test.controller;
import com.minis.beans.factory.Autowired;
import com.minis.web.RequestMapping;
import com.test.service.IAction;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloWorldBean {
/**
* 字段名 action 对应 XML 中 id="action" 的 Bean。
* 这个 Bean 是 ProxyFactoryBean,所以最终注入的是代理对象。
*/
@Autowired
private IAction action;
@RequestMapping("/testaop")
public void doTestAop(HttpServletRequest request,
HttpServletResponse response) {
this.action.doAction();
try {
response.getWriter().write("test aop, hello world!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
最终调用链是:
text
浏览器请求 /testaop
↓
HelloWorldBean.doTestAop(...)
↓
action.doAction()
↓
action 实际是 JDK 动态代理对象
↓
JdkDynamicAopProxy.invoke(...)
↓
执行 before 增强逻辑
↓
method.invoke(target, args)
↓
Action1.doAction()
业务代码看到的还是普通接口:
java
this.action.doAction();
但运行时真正被调用的是代理对象,所以通用逻辑就可以插入到业务方法前面。
十、三层实现思路总结
这一节其实是按三层递进来讲动态代理的。
第一层,最简单的动态代理实现,是新建一个 DynamicProxy 类。
text
真实对象 target
↓ 注入
DynamicProxy
↓ getProxy()
JDK Proxy 生成的代理对象
↓ 调用方法
InvocationHandler.invoke(...)
↓
method.invoke(target, args)
这一层只解决一个问题:怎么用 JDK 动态代理在运行时生成代理对象。核心代码就是 Proxy.newProxyInstance(...),核心入口就是 getProxy()。
第二层,为了让业务代码不被代理细节侵入,引入 FactoryBean。
text
XML 已经配置好:
realaction -> 真实对象
action -> ProxyFactoryBean
业务代码:
@Autowired
private IAction action;
容器 getBean("action")
↓
发现 action 是 FactoryBean
↓
调用 ProxyFactoryBean.getObject()
↓
返回代理对象,而不是返回 ProxyFactoryBean 本身
这一步的关键不是"怎样生成代理",而是"怎样让业务代码无感拿到代理"。是否需要代理,早就在 XML 里通过 action -> ProxyFactoryBean 配好了;getBean(...) 只是在返回对象前判断一下当前 Bean 是否是 FactoryBean。如果是,就调用 getObject() 拿产品对象;如果不是,就返回普通 Bean。
第三层,是向 Spring AOP 靠近的抽象。
text
ProxyFactoryBean
|
| createAopProxy(target)
v
AopProxyFactory
|
| 决定使用哪种代理技术
v
AopProxy
|
| getProxy()
v
JdkDynamicAopProxy
|
| Proxy.newProxyInstance(..., this)
v
JDK 代理对象
|
| 方法调用
v
InvocationHandler.invoke(...)
AopProxy 用来屏蔽具体代理技术,它只暴露 getProxy(),表示"给我一个代理对象"。AopProxyFactory 用来决定创建哪一种 AopProxy,当前默认创建的是 JdkDynamicAopProxy。
invoke(...) 不是 AopProxy 接口的方法,而是 JDK 的 InvocationHandler 方法。JdkDynamicAopProxy 同时实现了 AopProxy 和 InvocationHandler,所以它既能通过 getProxy() 创建代理对象,也能在代理对象的方法被调用时进入 invoke(...) 执行增强逻辑。
简单收一下:
| 层次 | 解决的问题 | 核心类/方法 |
|---|---|---|
| 直接使用 JDK 动态代理 | 运行时生成代理对象 | DynamicProxy#getProxy()、Proxy.newProxyInstance(...) |
| 接入容器 | 业务代码无感拿到代理对象 | FactoryBean#getObject()、ProxyFactoryBean、getBean(...) 判断 FactoryBean |
| AOP 抽象 | 屏蔽具体代理技术,方便以后扩展 | AopProxy#getProxy()、AopProxyFactory、JdkDynamicAopProxy#invoke(...) |
十一、学完后要能回答的问题
第一,什么是代理?
代理就是不直接访问真实对象,而是通过代理对象间接访问真实对象。代理对象和真实对象通常实现同一个接口,因此调用方可以面向接口编程。代理对象可以在调用真实对象前后增加日志、事务、权限检查等通用逻辑。
第二,什么是静态代理?
静态代理是提前手写代理类。它清晰、好理解,但每个需要增强的业务类都可能要配一个代理类,维护成本很高。
第三,什么是动态代理?
动态代理是不提前写代理类,而是在程序运行期间生成代理对象。JDK 动态代理需要目标类实现接口,并通过 InvocationHandler.invoke(...) 统一拦截方法调用。
第四,MiniSpring 为什么要引入 FactoryBean?
因为我们不希望业务代码手动 new 代理对象。FactoryBean 可以让 ProxyFactoryBean 留在容器内部,而业务代码通过 getBean("action") 或 @Autowired 拿到它生产出来的代理对象。
第五,AopProxy 抽象解决了什么问题?
它把"生产代理对象"这件事抽象出来。当前实现是 JdkDynamicAopProxy,以后如果要支持其他代理技术,可以新增实现,而不需要改业务代码和 ProxyFactoryBean 的主流程。
这一节可以总结成一句话:
动态代理让我们可以在运行时生成代理对象,再用
FactoryBean把这个代理对象交给容器管理,业务代码保持action.doAction()不变,通用逻辑却已经被插入到了调用链中。