什么是代理模式?
代理模式提供了对目标对象的另一个访问方式,即通过代理对象进行目标对象的访问。这样做的目的是在实现目标对象的功能之外,还能做一些额外的增强扩展。
为什么需要代理模式?
1、中介隔离对象:当客户类不愿意或者不能直接使用目标对象时,代理对象就作为中介者,将客户类的请求委托给目标对象。
2、开闭原则:当需要在实现目标对象的功能之外,做一些额外的增强扩展时。比如在方法执行前后打日志,在数据库插入方法前后加上事务等,可以在不修改目标对象代码的前提上做扩展。
静态代理
如图是静态代理的类图
这里可以看到一个RealSubject,在代理模式的设计中,会设计一个接口与目标对象一致的代理对象,在代理对象中持有对目标对象的引用,当客户端访问这个接口时,就会被代理对象拦截,在访问目标对象前后实现增强。
下面来写一个静态代理的案例代码:
创建服务类接口:
Subject:
public interface Subject {
void request();
}
实现服务类接口:
RealObject:
public class RealObject implements Subject{
public void request() {
System.out.println("数据库操作");
}
}
代理类:
Proxy:
public class Proxy implements Subject{
private Subject real ;
public void request() {
System.out.println("打开事务");
real.request();
System.out.println("提交事务");
}
public Proxy(Subject real) {
super();
this.real = real;
}
}
客户端:
Client:
public static void main(String[] args) {
Subject subject = new Proxy(new RealObject());
subject.request();
}
输出:
打开事务
数据库操作
提交事务
静态代理的优缺点
优点:可以实现在不修改目标对象代码的前提下,进行扩展增强
缺点:当需要不同的增强时,需要编写不同的代理类,同时当服务接口修改时,对应的代理类和目标对象都需要修改,不方便维护。
为了解决静态代理的缺点,所以出现了动态代理。
动态代理
特点:在内存中根据要求动态创建代理对象。不需要实现接口
JDK动态代理
API:
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
loader:目标对象的类加载器
interfaces:目标对象实现的接口
h:执行目标对象的方法时,会触发执行,将目标对象的Method对象作为参数传入进来
动态代理类:
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
super();
this.target = target;
}
public Object getProxy() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务");
Object returnValue = method.invoke(target, args);
System.out.println("提交事务");
return returnValue;
}
});
}
}
客户端:
public static void main(String[] args) {
Subject target = new RealObject();
Subject proxy = (Subject) new ProxyFactory(target).getProxy();
proxy.request();
}
输出:
打开事务
数据库操作
提交事务
注意:JDK动态代理,默认会对所有的方法都进行相同的增强,如果想指定方法,或者不同方法实现不同的增强,可以在InvocationHandler编写代码
JDK代理的缺点
目标对象必须实现一个或者多个接口,无法对没有实现任何接口的对象进行代理。
因此出现了 CGLIB动态代理。
CGLIB动态代理
本质是生成目标对象的子类,通过方法的重写来实现增强。
API:
Enhancer工具类用来创建目标对象的子类
MethodInterceptor 设置目标对象的回调函数,即增强扩展的地方。
代理类:
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
super();
this.target = target;
}
public Object getProxy() {
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(new MethodInterceptor() {
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
System.out.println("打开事务");
//据说用cglib的MethodProxy执行方法性能更佳
//Object returnValue = arg3.invoke(target, arg2);
Object returnValue = arg1.invoke(target, arg2);
System.out.println("提交事务");
return returnValue;
}
});
//4.创建子类(代理对象)
return en.create();
}
}
客户端:
public static void main(String[] args) {
Subject target = new RealObject();
Subject proxy = (Subject) new ProxyFactory(target).getProxy();
proxy.request();
}
输出:
打开事务
数据库操作
提交事务
注意:
1、CGLIB出现的目的是为了给没有实现接口的类增强,但是他其实也能给接口增强,如上的代码其实就是给接口增强,不管有没有实现接口,对CGLIB是一样的都是通过生成目标对象子类的方式来实现。
2、因为要通过子类的方式实现增强,因此CGLIB不能给final类增强,会报错
3、增强的过程是通过方法的重写做到,因此CGLIB也不能给final方法增强,final方法不报错,只是没有效果罢了。