The previous article talked about the agent mode: http://aphysia.cn/archives/dynamicagentdesignpattern
So to recap, how did the agency model come about? Suppose there is a demand:
controller
classes in the system call methods, print the log.
Assuming the original code:
public class Person{
public void method(){
// 表示自己的业务逻辑
process();
}
}
If you add a print method to all classes, it is definitely unrealistic. If I have hundreds of such classes, it crashes when I write, and there is too much code duplication, redundancy, and coupling. If I download I stopped logging this time, did other things, and changed all the hundreds of classes again.
public class Person{
public void method(){
log();
// 表示自己的业务逻辑
process();
log();
}
}
Static proxy
How to write more beautifully? static proxy comes out at this time, first abstract the method into an interface:
public class IProxy(){
public void method();
}
Let specific classes implement IProxy
and write your own business logic, such as:
public class Person implements IProxy(){
public void method(){
// 表示自己的业务逻辑
process();
}
}
Then get a proxy class to enhance the method:
public class PersonProxy implements IProxy{
private IProxy target;
public PersonProxy(IProxy target){
this.target = target;
}
@Override
public void method() {
log();
target.method();
log();
}
}
When calling, put the real object in the constructor of the proxy class, you can get a proxy class and enhance its methods. The advantage is that if I want to change next time, do not log and do other things, I It is enough to change the proxy class. I don't need to change the method of my target class everywhere, but the disadvantages are still obvious. If you want to enhance which class, you must write a proxy class for it, which seems unreasonable.
Dynamic proxy
let him automatically generate proxy objects? dynamic proxy to do is this thing, it can dynamic according to classes we offer, proxy classes generated objects.
The most important thing is that it is dynamically generated at runtime. As long as we pass in the information about the class we want to proxy to enhance, such as the class object itself, the class loader, the interface of the class, etc., it can be generated without knowing in advance that it is a class A , Class B or Class C.
There are three main implementation methods for dynamic proxy. Today we focus on analyzing JDK dynamic proxy:
- JDK proxy: use the official proxy provided by the JDK
- Third-party CGlib proxy: use CGLib's Enhancer class to create proxy objects
- javassit: Javassist is an open source library for analyzing, editing and creating Java bytecode. It was created by Shigeru Chiba (千叶梓) from the Department of Mathematics and Computer Science at Tokyo Institute of Technology.
JDK dynamic proxy
Steps for usage
- Create a new interface
- Create a new class to implement the interface
- Create a proxy class to implement the
java.lang.reflect.InvocationHandler
interface
code show as below:
IPlayDao.java
(playing interface)
public interface IPlayDao {
void play();
}
StudentDao.java
(Student class that implements the interface for shopping and playing)
public class StudentDao implements IPlayDao {
@Override
public void play() {
System.out.println("我是学生,我想出去玩");
}
}
MyProxy.java proxy class:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyProxy {
private Object target;
public MyProxy(Object target){
this.target=target;
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
// 一个接口可能很多方法,要是需要针对某一个方法,那么需要在函数里判断 method
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务 2");
// 执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务 2");
return returnValue;
}
}
);
}
}
Test class (Test.java)
public class Test {
public static void main(String [] args){
// 保存生成代理类的字节码文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
IPlayDao target =new StudentDao();
System.out.println(target.getClass());
IPlayDao proxy = (IPlayDao) new MyProxy(target).getProxyInstance();
System.out.println(proxy.getClass());
// 执行方法 【代理对象】
proxy.play();
}
}
由于加了这句代码,我们可以把生成的代理类的字节码文件保存下来, 其实通过输出也可以看到,两个对象不是同一个类,代理类是动态生成的:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924001916.png)
### 源码分析
跟着源码一步步看,先从调用的地方 `Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)`:
![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924002757.png)
进入方法里面,**省略各种异常处理**,主要剩下了**生成代理类字节码** 以及 **通过构造函数反射构造新对象**:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 判空
Objects.requireNonNull(h);
// 安全检查
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* 查找或者生成代理对象
*/
Class<?> cl = getProxyClass0(loader, intfs);
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 获取构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 反射构造代理对象
return cons.newInstance(new Object[]{h});
}
上面注释里面说查找或者生成代理对象,为什么有查找?因为并不是每一次都生成,生成的代理对象实际上会缓存起来,如果没有,才会生成,看源码 `Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces)`:
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length> 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 调用缓存代理类的 cache 来获取类加载器
return proxyClassCache.get(loader, interfaces);
}
如果由实现给定接口的给定加载器定义的代理类存在,这将简单地返回缓存的副本; 否则,它将通过 ProxyClassFactory 创建代理类,`proxyClassCache` 其实就是个 `weakCache`:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
初始化的时候,proxyClassCache 指定了两个属性,一个是 `KeyFactory`, 另外一个是 `ProxyClassFactory`, 从名字就是猜到 `ProxyClassFactory` 是代理类工厂:
public WeakCache(BiFunction<K, P, ?> subKeyFactory,
BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}
**记住这里的 subKeyFactory,实际上就是传入的 ProxyClassFactory**,那前面 `proxyClassCache.get(loader, interfaces);` 到底是怎么操作的?
![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924010200.png)
上面调用到了 `subKeyFactory.apply(key, parameter)`,这个 `subKeyFactory` 实际上是我们传的 `ProxyClassFactory`, 进入里面去看:
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 生成的代理类前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 下一个生成的代理类的名字的计数器,一般是 $Proxy0,$Proxy1
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* 检验类加载器是否能通过接口名称加载该类
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + "is not visible from class loader");
}
/*
* 判断接口类型
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + "is not an interface");
}
/*
* 判断是否重复
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface:" + interfaceClass.getName());
}
}
// 代理包名字
String proxyPkg = null;
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* 记录非公共代理接口的包,以便在同一个包中定义代理类。确认所有非公共代理接口都在同一个包中。
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// 如果没有非公共代理接口,请使用 com.sun.proxy 包
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* 为要生成的代理类选择一个名称。
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* 生成指定的代理类。(这里是重点!!!)
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* 这里的 ClassFormatError 意味着 (禁止代理类生成代码中的错误) 提供给代理类创建的参数有其他一些无效方面 (比如超出了虚拟机限制)。
*/
throw new IllegalArgumentException(e.toString());
}
}
}
上面调用一个方法生成代理类,我们看看 IDEA 反编译的代码:
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
// 生成文件
final byte[] var4 = var3.generateClassFile();
// 判断是否要写入磁盘!!!
if (saveGeneratedFiles) {
// 开启高权限写入
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1> 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
}
Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file:" + var4x);
}
}
});
}
return var4;
}
生成代理文件实际上和我们想的差不多,就是一些 hashCode(),toString(),equals(), 原方法,代理方法等:
![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924011447.png)
这与我们之前看到的文件一致:
![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924011628.png)
然后之所以我们代码要设置写入磁盘,是因为这个变量, 控制了写磁盘操作:
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
**为什么只支持接口实现,不支持继承普通类?**
因为代理类继承了 Proxy 类,并且实现了接口,Java 不允许多继承,所以不能代理普通类的方式,并且在静态代码块里面,用反射方式获取了所有的代理方法。
JDK 代理看起来像是个黑盒,实际上,每一句代码,都有其缘由。其实本质上也是动态的为我们的原始类,动态生成代理类。
生成的代理类里面其实对原始类进行增强(比如 `play()` 方法)的时候, 调用了 `super.h.invok()` 方法,其实这里的 `h` 是什么呢?
`h` 是父类的 `h`, 生成的代理类的父类是 `Proxy`,Proxy 的 `h`,就是我们传入的 `InvocationHandler`:
public final void play() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
生成的代码里面通过反射调用到的其实是我们自己重写的那部分逻辑,所以就有了增强的功能,不得不说,这种设计确实巧妙:
![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210925020135.png)
## 动态代理有多香
动态代理是Java语言里面的一个很强大的特性,可以用来做一些切面,比如拦截器,登录验证等等,但是它并不是独立存在的,任何一个知识点都不能独立说明语言的强大,重要的是它们的组合。
动态代理要实现强大的功能,一般需要和反射,注解等一起合作,比如对某些请求进行拦截,拦截后做一些登录验证,或者日志功能。最重要的一点,它能够在减少耦合度的前提下实现增强。
**【作者简介】**:
秦怀,公众号【**秦怀杂货店**】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:`Java源码解析`,`JDBC`,`Mybatis`,`Spring`,`redis`,`分布式`,`剑指Offer`,`LeetCode`等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。
[剑指Offer全部题解PDF](http://aphysia.cn/archives/jianzhiofferpdf)
[2020年我写了什么?](http://aphysia.cn/archives/2020)
[开源编程笔记](https://damaer.github.io/Coding/#/)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。