不论是在开源框架中还是日常开发中,都少不了 代理的出现。那么代理到底是什么呢?本文将详细介绍代理相关知识,彻底搞懂代理。
1. 什么是代理,它有什么特点?
大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家做了一次对客户群体的“过滤”。把微商代理和厂家进一步抽象,前者可抽象为代理类,后者可抽象为委托类(被代理类)。
通过使用代理,通常有两个优点,并且能够分别与我们提到的微商代理的两个特点对应起来:
优点一:可以隐藏委托类的实现;
优点二:可以实现客户与委托类间的解耦,提高扩展性。
2. 代理实现方式
本章节将详细介绍
代理底层的原理,并结合设计原理,总结在使用过程中应当注意的事项。
我们一般常见的代理如下图所示:
下面围绕 计算器 这个场景为基础来讲解各种代理的使用及原理。
/**
* 计算器接口及基础实现类
* @author jackcheng (jackcheng1117@163.com)
*/
public class TestProxy {
public interface Calculate {
/**
* 计算合
*/
void add(int i, int j);
}
public class CalculateImpl implements Calculate{
@Override
public void add(int i, int j) {
System.out.println("result = " + (i + j));
}
}
}
2.1 JDK 静态代理
假设现在有一个需求:需要在Calculate接口中所有方法执行前后打印一条日志
如何可以在不修改原有代码的逻辑下解决呢?
我们试着使用静态代理解决:
/**
* 使用静态代理解决日志打印需求
* @author jackcheng (jackcheng1117@163.com)
*/
public class CalculateByStaticProxy implements Calculate {
private Calculate calculate;
public CalculateByStaticProxy(Calculate calculate) {
this.calculate = calculate;
}
@Override
public void add(int i, int j) {
System.out.println("====== Before() ======");
calculate.add(i, j);
System.out.println("====== After () ======");
}
/**
* 测试结果
*/
static class Test {
public static void main(String[] args) {
CalculateByStaticProxy proxy = new CalculateByStaticProxy(new TestProxy().new CalculateImpl());
proxy.add(10, 20);
}
}
}
通过静态代理在不修改原有逻辑的前提下,好像可以很好的完成添加日志的需求。
静态代理原理:
通过上面的案例,我们并没有添加新的东西,只是使用了组合的方式对原有逻辑进行了包装。也就是说:
代理对象 = 增强代码 + 目标对象(原对象)
需要注意的是:在程序运行之前代理类就已经存在了,所以这种代理方式就成为 静态代理。
代理模式看似很好的解决了需求,但是要注意的是,现在我们只有1个接口,如果有数百上千个接口就需要添加相同数量的代理类,我们努力的方向是:如何不写类或者少些代理类完成需求。
2.2 JDK 动态代理
使用静态代理完成增加日志需求,若接口很多的话,则要很大的工作量,那么我们试着使用动态代理解决一下:
/**
* 使用动态代理解决日志打印需求
* @author jackcheng (jackcheng1117@163.com)
*/
public class CalculateByDynamicProxy implements InvocationHandler{
private Object target;
public CalculateByDynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("====== Before() ======");
method.invoke(target, args);
System.out.println("====== After () ======");
return null;
}
public static class Test {
public static void main(String[] args) {
Calculate proxy = (Calculate) Proxy.newProxyInstance(Calculate.class.getClassLoader(), new Class[]{Calculate.class}, new CalculateByDynamicProxy(new TestProxy().new CalculateImpl()));
proxy.add(10, 20);
}
}
}
观察上方动态代理实现案例,本质上动态代理也是:
代理对象 = 增强代码 + 目标对象(原对象)
与静态代理不同的是,动态代理的实现更加通用,不需要为每一个类都单独创建一个代理类,
只需要传入被代理类的元信息即可创建代理类。排除反射性能消耗,编写一个动态代理类即可为多个接口创建代理对象。
实现动态代理关键步骤:
通过Proxy.newInstance静态方法创建一个代理对象
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
loader参数 : 被代理类(接口)的classLoader
interfaces参数: 被代理类(接口)的类数组
h参数: 自己创建一个实现InvocationHandler接口的类,该类实际上是一个 “中介类” ,用来写 增强代码
使用动态代理的好处就是不需要单独为每个接口都创建一个代理类,只需要创建一个实现InvocationHandler接口的实现类即可。
然后通过Java反射机制使用JVM中已知Class生成新的Class对象(代理对象),并对其方法包装执行。
关于JDK动态代理需要注意:
jdk中的动态代理只能对实现接口的类进行动态代理,并不能针对一个具体的实现类进行动态代理。
若现在有一个接口,并且接口中有A,B两个方法,我们创建一个类实现该接口,并且实现A,B方法,但是如果再在接口实现中创建一个C方法,但是接口中没有声明C方法,那么C方法时不能被代理的
2.3 Cglib (Code Generation Library) 动态代理
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类
生成一个子类
,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰
的类进行代理。
cglib实现动态代理的方法和JDK动态代理类似
//创建一个普通类
public class SayHello {
public void say(String name) {
System.out.println("您好," + name);
}
}
//CGLIB动态代理类
public class CglibProxy implements MethodInterceptor {
/**
* 生成CGLIB代理对象
* @param cls -Class类 需要被代理的真实对象
* @return
*/
public Object getProxy(Class cls) {
//1.CGLIB enhancer增强类对象
Enhancer en = new Enhancer();
//2.设置增强类型
en.setSuperclass(cls);
//3.定义代理逻辑对象为当前对象,要求当前对象实现 MethodInterceptor 接口
en.setCallback(this);
//生成代理对象并返回
Object proxy = en.create();
return proxy;
}
/**
* 代理逻辑方法
* 1.proxy 代理对象
* 2.method 方法
* 3.args 方法参数
* 4.methodProxy 方法代理
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("调用代理对象之前的逻辑~");
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("调用代理对象之后的逻辑~");
return result;
}
}
//测试代码
public class TestCglibProxy {
public static void main(String[] args) {
CglibProxy cglib = new CglibProxy();
SayHello proxy = (SayHello) cglib.getProxy(SayHello.class);
proxy.say("James");
}
}
3. 代理总结
优点:
1.代理模式可以将代理对象与真实被调用的目标对象分离
2.在一定程度上降低了系统的耦合,提高扩展性
3.保护目标对象
缺点:
1.代理模式会造成系统设计中类的数目增加
2.在客户端和目标对象通过反射增加一个代理对象,会造成请求处理速度变慢
性能测试:(万份数据测试)
反射 < Cglib < jdk8动态代理
以上关于代理的论述,仅代表个人观点,作者水平有限,如有错误,欢迎批评指正。
@author : jackcheng1117@163.com