java 代理模式

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被为拖累执行后的后续处理。
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。
更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。
按照代理的创建时期,代理类可以分为两种
静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
动态:在程序运行时运用反射机制动态创建而成

下面是两个例子说明了静态代理和动态代理的使用方法:
静态代理模式
1、建立move接口
package com.feixun.designerpattern.proxy.static_proxy;
public interface Moveable {
void move();
}
2、建立实现move功能的Car类
package com.feixun.designerpattern.proxy.static_proxy;
import java.util.Random;
public class Car implements Moveable{
@Override
public void move() {
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println(“汽车行驶中”);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3、建立日志代理类CarLogProxy
package com.feixun.designerpattern.proxy.static_proxy;
public class CarLogProxy implements Moveable {
private Moveable moveable;//将movable 类型的对象聚合进来,其实movable是一个接口,这里用到了多态的概念。
public CarLogProxy(Moveable moveable) {
super();
this.moveable = moveable;
}
@Override
public void move() {
System.out.println(“日志开始…”);
moveable.move();
System.out.println(“日志结束…”);
}
}
4、接着建立行驶时间CarTimeProxy代理类
package com.feixun.designerpattern.proxy.static_proxy;
//CarTimeProxy 为 属于Moveable 的类型对象的代理类
//基于组合的方式
public class CarTimeProxy implements Moveable{
private Moveable moveable;//同上所示
public CarTimeProxy(Moveable moveable) {
super();
this.moveable = moveable;
}
@Override
public void move() {
long starttime = System.currentTimeMillis();
System.out.println(“汽车开始行驶”);
moveable.move();
long endtime = System.currentTimeMillis();
System.out.println(“汽车行驶结束,行驶了”+(endtime-starttime)+” 毫秒”);
}
}
测试类
package com.feixun.designerpattern.proxy.static_proxy;
public class Client {
public static void main(String[] args) {
Car car1 = new Car();
CarLogProxy carLogProxy = new CarLogProxy(car1);
CarTimeProxy carTimeProxy = new CarTimeProxy(carLogProxy);
carTimeProxy.move();
}
//引入问题:如果现在更多的realsubject 需要事件代理,那么现在需要新建多个代理类吗?类膨胀。
//动态产生代理,实现对不同类,不同方法的代理。
}
运行结果:
汽车开始行驶
日志开始…
汽车行驶中
日志结束…
汽车行驶结束,行驶了529 毫秒

相信有一点基础的同学都可以看出静态代理的缺点
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法(即委托类对外暴露的方法),除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

这样我们必须要引入动态代理:
在上面的示例中,一个代理只能代理一种类型,而且是在编译期就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象
动态代理模式(JDK代理)
涉及的接口:
java.lang.reflect.Proxy
Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
// 获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy);
// 获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces);
// 判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl);
// 用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h);
//调用处理器接口
java.lang.reflect.InvocationHandler
它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
// 该方法负责集中处理动态代理类上的所有方法调用,第一个参数既是代理类实例,第二个参数是被调用的方法对象,第三个方法是调用参数.调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行.
Object invoke(Object proxy, Method method, Object[] args);

java.lang.ClassLoader
这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。 每次生成动态代理类对象时都需要指定一个类装载器对象

动态代理的优点
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强
首先建立定义接口的Moveable
package com.feixun.designerpattern.proxy.dynamic_proxy;
public interface Moveable {
void move();
}
接着定义realsubject(被代理类):泛指所有类型的被代理类。可以是Car,Subway,etc.
package com.feixun.designerpattern.proxy.dynamic_proxy;
import java.util.Random;
public class Car implements Moveable {
@Override
public void move() {
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println(“汽车行驶中”);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
接着建立实现(implements)InvocationHandler接口的类:
InvocationHandler,中定义了Object invoke(Object proxy, Method method, Object[] args) 方法。参数解释如下图代码中注释
package com.feixun.designerpattern.proxy.dynamic_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyHandler implements InvocationHandler {
private Object target;//被代理对象,委托类
public MyHandler(Object target) {
this.target = target;
}
/* (non-Javadoc)
* @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
* proxy–指被代理的对象(感觉java这个参数的名字并不是很好)
* method–被代理对象的方法
* args:被代理对象的方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println(“汽车开始运行了…”);
method.invoke(target, args);//运行被代理对象的方法,采用反射的机制。
System.out.println(“汽车运行结束…”);
return null;
}
}
这一步只是完成了被代理对象方法的扩充,还没有产生真实的代理对象。
再看测试类:
package com.feixun.designerpattern.proxy.dynamic_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class TestDynamicProxy {
public static void main(String[] args) {
Car car = new Car();
InvocationHandler handler = new MyHandler(car);
Class< ? > cs = car.getClass();
/*
* @param : classloader 被代理对象的类加载器
* @param : interfaces 被代理对象实现的接口
* @param : Invocationhandler handler /子类
*/
Moveable moveable=(Moveable)Proxy.newProxyInstance(cs.getClassLoader(),
cs.getInterfaces(), handler);//这一步动态生成代理对象 moveable
moveable.move();
}
}
运行结果:
汽车开始运行了…
汽车行驶中
汽车运行结束…

可以看出:动态代理解决了对不同委托类产生代理对象的问题,假设现在我有Subway类,我要实现被代理,我只需要Subway实现Moveable,然后在测试类中更改几行代码就好,所以解决了聚合静态代理产生的问题,又将问题复杂度减少了一个维度。
但是jdk动态代理也有问题:委托累想要实现自己的代理类,必须实现接口,如果有类没有实现接口,就不能使用JDK代理。

cglib动态代理
上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理, 通俗说cglib可以在运行时动态生成字节码。

Cglib代理,也叫作子类代理,是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理.因为采用的是继承,所以不能对final修饰的类进行代理.

JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
前提条件:
需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-3.2.5.jar
cglib的Maven坐标

cglib
cglib
3.2.5

目标类不能为final, final修饰的类不能被继承
目标对象的方法如果为final/static,因不能被子类重写,那么就不会被拦截,即不会执行目标对象额外的业务方法
补充知识:
static修饰的方法:
1、父类中的静态方法可以被继承、但不能被子类重写。
2、如果在子类中写一个和父类中一样的静态方法,那么该静态方法由该子类特有,两者不构成重写关系。
final修饰:
1、修饰类表示不允许被继承。
2、修饰方法表示不允许被子类重写,但是可以被子类继承,不能修饰构造方法。
3、修饰变量表示不允许被修改
(final修饰的方法表示此方法已经是“最后的、最终的”含义,亦即此方法不能被重写,可以重载多个final修饰的方法,此处需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中final修饰的方法同时访问控制权限为private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final的矛盾,而是在子类中重新定义了新的方法。)
①首先定义业务类,无需实现接口(当然实现接口也是可以的,不影响)

class BookImpl{
public void addBook(){
System.out.println(“新增书籍”);
}
}

②实现MethodInterceptor方法拦截器接口,并创建代理类

class BookCGlib implements MethodInterceptor{
private Object target;//业务类对象,供代理方法中进行真正的业务方法调用

//相当于jdk动态代理中的绑定
public Object getInstance(Object target){
    this.target = target;//给业务方法赋值
    //创建加强器,用来创建动态代理类
    Enhancer enhancer = new Enhancer();
    //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
    enhancer.setSuperclass(this.target.getClass());
    //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而CallBack则需要实现intercept()方法进行拦截
    enhancer.setCallback(this);
    return enhancer.create();
}

//实现回调方法,拦截方法
public Object intercept(Object obj, Method method, Object[] args,
        MethodProxy proxy) throws Throwable {
    System.out.println("-----预处理-------");
    proxy.invokeSuper(obj, args);//调用业务类(父类中的方法)
    System.out.println("-----调用后操作-----");
    return null;
}

}

③创建业务类和代理类对象,然后通过代理类对象.getInstance(业务类对象),返回一个动态代理类对象(它是业务类的子类,可以用业务类引用指向它.最后通过动态代理类对象进行方法调用)

public class TestCGlib {
public static void main(String[] args) {
BookImpl bookImpl = new BookImpl();
BookCGlib cglib = new BookCGlib();
BookImpl bookCGlib = (BookImpl) cglib.getInstance(bookImpl);
bookCGlib.addBook();
}
}

==比较:==

①静态代理是通过在代码中显式定义一个业务实现类一个代理,在代理类中对同名的业务方法进行包装.用户通过代理类调用包装过的业务方法;

②jdk动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法

③CGlib动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理.
cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值