代理模式
代理模式是一种结构型模式。一个类代表另一个类的功能。需要代理类的原因可能是无法直接访问被代理的类,或者被代理类需要扩展新的功能,但是无法修改源代码。
使用房主+中介+客户来举例。
房主:需要被代理的类。他需要出租房子,但是没有时间带客户来看房子。代码如下
public class Homeowner {
public void rent(){
System.out.println("我是房主,我需要出租房子");
}
}
中介:代理类。可以替房主带客户来看房子。这就拓展了房主类的需求
public class Agent extends Homeowner{
public void rent(){
lookHouse();
super.rent();
}
private void lookHouse(){
System.out.println("中介带客户看房子");
}
}
客户:调用服务的类。客户的需求是租房子,那他就需要找房主(被代理类),但房主没时间(需要额外功能),那他就去找中介(代理类)。
public class Client {
public static void main(String[] args) {
Agent agent = new Agent();
agent.rent();
}
}
静态代理
所谓静态代理可以这样简单理解:在代码运行之前,代理类的字节文件已经存在了,不是动态生成的。上面的例子就是静态代理。
缺点也就很明显,静态代理只可以为一个类服务,如果需要代理很多类,那么需要写同样多的代理类,不仅繁琐,还增加了系统的复杂度。
动态代理
动态代理则是利用到了Java的反射机制在程序运行过程中动态生成代理类字节码。主要和InvocationHandler接口和Proxy类有关。
用保存用户注册数据为例,需要新增的功能是打印一下日志。
被代理类接口:
public interface UserDao {
void save();
}
被代理类:
public class UserDaoImpl implements UserDao{
@Override
public void save() {
System.out.println("保存用户数据");
}
}
代理类:可以当做一个工具类
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target){
this.target = target;
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("打印日志:开始保存数据");
method.invoke(target,args);
System.out.println("打印日志:用户数据保存完毕");
return null;
}
});
}
}
调用者:
public class ProxyTest {
public static void main(String[] args) {
UserDao target = new UserDaoImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
UserDao proxyInstance = (UserDao) proxyFactory.getProxyInstance();
proxyInstance.save();
}
}
动态代理使用起来确实方便,只需要写一个代理类,就可以完成所有函数的日志打印功能。但是因为它依靠了Java的反射机制,也就存在一个弊端,如果被代理类没有接口的话,或者需要代理的函数不是实现于它的接口,那么就无法使用了。
cglib代理
cglib(Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
因为是一个第三方库,所以使用就需要引入jar包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
还是用保存用户注册数据为例
被代理类:注意这个类是没有实现接口的
public class UserDao {
public void save() {
System.out.println("保存数据");
}
}
代理类:和动态代理就是在实现方式上有点不同,其他没有什么区别
public class ProxyFactory implements MethodInterceptor {
private Object target;//维护一个目标对象
public ProxyFactory(Object target) {
this.target = target;
}
//为目标对象生成代理对象
public Object getProxyInstance() {
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数
en.setCallback(this);
//创建子类对象代理
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("打印日志:开始保存数据");
// 执行目标对象的方法
method.invoke(target, args);
System.out.println("打印日志:用户数据保存完毕");
return null;
}
}
调用者:
public class ProxyTest {
public static void main(String[] args) {
UserDao target = new UserDao();
ProxyFactory proxyFactory = new ProxyFactory(target);
UserDao proxyInstance = (UserDao) proxyFactory.getProxyInstance();
proxyInstance.save();
}
}
总结
- 上面的举例都是添加额外功能,这个主要是和spring的AOP接轨的。其实使用代理模式,还可以在不方便直接访问被代理类的情况下使用的
- 静态代理:在编译时产生class字节码文件,可以直接使用,效率高。但不适合存在大量的被代理类的场景
- 动态代理:使用反射还是会比较消耗系统资源的,但使用起来很方便。不过需注意一个使用细节,就是被代理类必须存在接口
- cglib代理:无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。