一、概念
1、定义:为一个对象提供替身或占位符以控制对这个对象的访问
2、类型;结构型
3、适用环境:
- 远程代理:为不同地址空间的地址对象提供本地的代理对象
- 虚拟代理:创建资源消耗较大的对象时,先创建一个消耗较小的对象来代替真实对象,而真实的对象只有被用到才创建
- Copy-On-Write代理:属于虚拟代理的一种,把克隆操作推迟到客户端真正需要时才执行
4、优缺点
优点
- 协调调用者和被调用者,一定程度上降低耦合
- 远程代理使本地客户端能访问远程机器上的对象
- 虚拟代理减少了系统的资源消耗
- 保护代理控制了对真实对象的使用权限
缺点
- 由于增加代理对象,可能造成请求处理速度变慢
- 一些代理模式的实现较为复杂
5、代理模式和装饰者模式的异同点?
代理模式和装饰者模式很像,对于装饰者模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个接口,能够动态的增加行为;对于代理模式,代理类(proxy class)和真实处理的类(real class)都实现同一个接口,可以增强方法,不论我们使用哪种模式,都能自定义一些方法,这是相同的地方。
不同之处在于:装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注在控制对对象的访问,代理类可以对它的客户隐藏具体信息,使用代理模式的时候,要创建对象的实例,而使用装饰器模式的时候,我们是将原始对象作为参数传递给装饰者构造器的。
再简洁一点:
- 代理模式强调控制,装饰者模式强调增强
- 代理模式强调透明访问,装饰者模式强调自由构建
二、静态代理
实现一:继承,重写方法
缺点:一定会产生类,产生类爆炸,不符合OCP
class Target{
public void print() {
System.out.println("target");
}
}
class Proxy extends Target{
@Override
public void print() {
System.out.println("proxy");
super.print();
}
}
public class test {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.print();
}
}
实现二:组合,利用接口(本质是装饰者模式),也会产生类爆炸,但比继承少一点
- 真实对象和代理对象实现同一个接口。
- 代理对象要包含真实对象
interface Dao {
void print();
}
class Target implements Dao {
@Override
public void print() {
System.out.println("target");
}
}
class Proxy implements Dao {
private Target target;
Proxy(Target target) {
this.target = target;
}
@Override
public void print() {
System.out.println("proxy");
target.print();
}
}
public class test {
public static void main(String[] args) {
Dao proxy = new Proxy(new Target());
proxy.print();
}
}
三、动态代理
静态代理与动态代理的区别
- 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则。
- 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
- 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
模拟动态代理
不需要手动创建类文件(因为一旦手动创建类文件,就会产生类爆炸),通过接口反射生成一个类文件,然后调用第三方的编译技术,动态编译这个产生的类文件成class文件,继而利用UrlclassLoader
(因为这个动态产生的class不在工程当中所以需要使用UrlclassLoader
)把这个动态编译的类加载到jvm当中,最后通过反射把这个类实例化。
JDK动态代理
//1.接口
public interface Dao {
void add();
}
//2.被代理的对象
public class DaoImpl implements Dao {
@Override
public void add() {
System.out.println("add");
}
}
//3.自定义Hander类
public class MyInvocation implements InvocationHandler {
Dao target;
public MyInvocation(Dao target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before-----");
return method.invoke(target, args);
}
}
//4.测试类
public class Test {
public static void main(String[] args) {
Dao target = new DaoImpl();
Dao proxy = (Dao) Proxy.newProxyInstance(Test.class.getClassLoader(), target.getClass().getInterfaces(), new MyInvocation(target));
proxy.add();
}
}
原理:
通过接口反射得到字节码,然后把字节码转成class ,底层是一个native方法,是openJDK ,c++写的
JDK Proxy 生成对象的步骤如下:
1、拿到被代理类的引用,并且获取它的所有的接口(反射获取)。
2、JDK Proxy类重新生成一个新的类,实现了被代理类所有接口的方法。
3、动态生成Java代码,把增强逻辑加入到新生成代码中。
4、编译生成新的Java代码的class文件。
5、加载并重新运行新的class,得到类就是全新类。
以上这个过程就叫字节码重组。JDK 中有一个规范,在 ClassPath 下只要是$开头的 class文件一般都是自动生成的。
CGLib动态代理
//1.被代理的对象
class Target {
void find() {
System.out.println("target");
}
}
//2.处理类
public class CGlibMeipo implements MethodInterceptor {
public Object getInstance(Class<?> clazz) throws Exception {
//相当于Proxy,代理的工具类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
Object obj = methodProxy.invokeSuper(o, objects);
System.out.println("after");;
return obj;
}
}
//3.测试类
public class Test {
public static void main(String[] args) {
try {
Target obj = (Target) new CGlibMeipo().getInstance(Target.class);
obj.find();
} catch (Exception e) {
e.printStackTrace();
}
}
}
CGLib和JDK动态代理对比
1.JDK是采用读取接口的信息,CGLib覆盖父类方法。
2.目的:都是生成一个新的类,去实现增强代码逻辑的功能。
3.JDK Proxy 对于用户而言,必须要有一个接口实现,目标类相对来说复杂,CGLib 可以代理任意一个普通的类,没有任何要求。
4.CGLib 生成代理逻辑更复杂,效率,调用效率更高,生成一个包含了所有的逻辑的FastClass
,不再需要反射调用。
JDK Proxy生成代理的逻辑简单,执行效率相对要低,每次都要反射动态调用。
5.CGLib 有个坑,CGLib不能代理final的方法。
优点
1、代理模式能将代理对象与真实被调用的目标对象分离。
2、一定程度上降低了系统的耦合度,扩展性好。
3、可以起到保护目标对象的作用。
4、可以对目标对象的功能增强。
缺点
1、代理模式会造成系统设计中类的数量增加。
2、在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
3、增加了系统的复杂度。
Spring 中的代理选择原则
Spring 利用动态代理实现 AOP 有两个非常重要的类,一个是 JdkDynamicAopProxy
类和 CglibAopProxy
类
1、当 Bean 有实现接口时,Spring 就会用 JDK 的动态代理
2、当 Bean 没有实现接口时,Spring 选择 CGLib。
3、Spring 可以通过配置强制使用 CGLib,只需在 Spring 的配置文件中加入如下代码:
<aop:aspectj-autoproxy proxy-target-class="true"/>