基础:需要具备面向对象设计思想,多态的思想,反射的思想;
1 什么是代理模式
代理模式:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
抽象角色:通过接口或抽象类声明需要实现的业务方法method1。
真实角色:实现抽象角色,并实现其所有声明的方法method1。
代理角色:实现抽象角色,并实现其所有声明的方法method1,method1则是通过调用真实角色实现的方法method1,除此之外,还可以添加自定义方法method2增强method1
代理又分为静态代理和动态代理,我们先从静态代理开始了解代理模式的工作流程。
2 静态代理
一个场景:客户要去银行取钱,此时,委托柜员代理自己从账户中取钱,柜员不仅代他取出钱给他,还生成了取钱业务的记录。
/**
* 抽象角色
*/
public interface UserService {
void getMoney();
}
/**
* 真实角色---实现抽象角色
*/
public class UserServiceImpl implements UserService{
public void getMoney(){
System.out.println("客户取钱");
}
}
/**
* 代理角色---实现抽象角色,调用真实角色的方法,并增强该行为
*/
public class ProxyActor implements UserService {
public UserServiceImpl userService;
public void getMoney(){
userService=new UserServiceImpl();
userService.getMoney(); //调用真实角色的具体方法
generateDetail(); //自定义的增强方法
}
public void generateDetail(){
System.out.println("生成业务记录");
}
}
由上面代码可以看出,原本由真实角色UserService执行的getMoney()方法,现在由代理角色ProxyActor执行,而且还封装了自定义的generateDetail()方法对其增强。但是,这样的设计模式也带来了缺点。
缺点:当功能叠加的时候,只能臃肿的扩展代理类,为每一个真实角色扩展一个代理角色类,在实际开发中,大量使用静态代理会导致类的急剧增加。
究其原因:是因为静态代理所需要的实现类在编译时期就要加载检查。
如何改善:使用动态代理设计方法,在运行期需要的时候在加载对应的类,减少类的急剧增加。
3 动态代理
3.1 JDK动态代理
3.1.1 设计实现
抽象角色和真实角色仍然不变,动态构建新的代理类:通过实现InvocationHandler接口的反射方法invoke(),这个方法在代理类中动态实现。(InvocationHandler位于java.lang.reflect包)
public class MyInvocationHandler implements InvocationHandler {
private Object target; //角色属性和构造方法
MyInvocationHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
Object result = method.invoke(target, args);
generateDetail();
return result;
}
public void generateDetail(){
System.out.println("生成业务记录");
}
}
使用测试:
@Test
public void test(){
UserService userService=new UserServiceImpl();
MyInvocationHandler invocationHandler=new MyInvocationHandler(userService);
UserService proxyInstance = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
invocationHandler); //需要的时候在动态生成代理类
proxyInstance.getMoney(); //这一步:通过动态代理类执行业务方法时,会通过反射invoke()执行代理方法
}
Proxy代理类有静态方法:Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler invocationHandler):返回代理类的一个实例,返回后的代理类可以被当作真实角色使用,可使用真实角色中申明的方法。
其实动态代理就是在运行时,指定所需接口生成class文件,自然就能指向class已实现的接口,相当于生成了一个代理类实例,但是这个代理类的具体操作封装在InvocationHandler的invoke(Object obj, Method method, Object[] args)中实现,所以在生成代理类的时候还需提供一个handler,由它接管实际操作。
通过这种方式,抽象角色、真实角色都能在运行时动态改变,从而实现灵活的动态代理关系。
3.1.2 总结
所为的DynamicProxy是这样一种class:它是在运行时生成的class,该class需要实现一组interface,使用动态代理类的时候,必须实现InvocationHandler接口。
JDK动态代理的一般步骤
1. 创建被代理的接口(抽象角色)和类(真实角色)
2. 创建一个实现接口InvocationHandler的类(必须实现invoke方法),传入真实角色对象,用于封装代理任务
3. 调用Proxy的静态方法,创建一个代理类newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandlerh)
4.调用代理类的业务方法即可
3.2 CGlib动态代理
3.1.1 代理实现
1.引入cglib-node-2.2.jar包
2.用CglibProxy拦截类实现接口MethodInterceptor:重写intercept拦截方法
/**
* 真实角色,被代理类,不用抽象角色
*/
public class User{
public void getMoney(){
System.out.println("客户取钱");
}
}
/**
* CGlib代理类
*/
public class CGlibProxy implements MethodInterceptor {
private Enhancer enhancer=new Enhancer();
public Object getProxy(Class cl){
//设置创建子类的类
enhancer.setSuperclass(cl);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable{
Object result=methodProxy.invokeSuper(o, objects);
generateDetail();
return result;
}
private void generateDetail(){
System.out.println("生成业务记录");
}
}
CGlib测试:
/**
* CGlib动态代理测试
*/
@Test
public void test3(){
CGlibProxy proxy=new CGlibProxy();
User user=(User )proxy.getProxy(User.class);
user.getMoney();
}
3.2.2 总结
1.创建被代理类
2.创建代理类实现接口MethodInterceptor,并重写intercept方法
3.调用代理类自定义的方法,得到一个代理实例
使用:执行代理实例的业务方法即可
比较总结
3.2.3 比较总结
JDK动态代理
1、 只能代理实现了接口的类,即实现了抽象角色的真是角色。
2、 没有实现接口的类不能实现JDK的动态代理
CGlib动态代理
1、 针对类来实现代理
2、 对执行目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。
3 模拟代理产生步骤
思路:
实现功能:通过Proxy的newProxyInstance返回代理对象
1、 声明一段源码(动态产生代理)
2、 编译源码(JDK Compiler API)产生新的类(代理类)
3、 将这个类load到内存当中,产生一个新的对象(代理对象)
4、 返回代理对象完善动态代理实现
首先得到系统编译器,通过编译器得到文件管理者,然后获取文件,然后编译器执行编译任务,完成编译之后,将class文件加载到类加载器中,通过构造方法得到实例,然后调用newInstance()接收一个对象的实例。
(1)拿到编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
(2)文件管理者 StandardJavaFileManager fileMgr = Compiler.getStandardFileManager(null,null,null);
(3)获取文件 Iterable units = fileMgr.getJavaFileObjects(filename);
(4)编译任务 CompilationTask t =compiler.getTask(null,fileMgr,null,null,null,units);
(5)load到内存
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass(”com.imooc.proxy.$Proxy0”);
(6)通过代理对象的构造器构造实例
Constructor ctr = c.getConstructor(infce);
ctr.newInstance(new Car());
上说所说,内部的业务逻辑是硬编码的,如何实现真正的动态代理,动态的指定业务逻辑呢?
1、需要创建一个事务处理器,首先创建一个接口也就是InvocationHandler,为了模拟JDK,这里把接口的名字和JDK事务处理器名称一样,同样写一个方法叫做invoke(),用来表示对某个对象的某个方法进行业务处理,所以需要把某个对象以及对象的方法作为invoke()方法的参数传递进来,invoke(Object obj,Method method),方法作为参数使用到了java反射,需要把此包引入。这样InvocationHandler接口就完成了。
2、创建事务处理实现类比如说时间代理TimerProxy,实现了InvocationHandler接口,这样结构就成了
TimerProxy implements InvocationHandler{
@override
void invoke(Object obj,Method method){
//业务逻辑<br>——method.invoke(目标对象,参数);
//业务逻辑<br>
}
}
需要将目标对象传入,没有参数可以不写参数,创建代理对象的构造方法,初始化目标对象
3、在Proxy类的newProxyInstance()方法中,除了要把目标Class接口作为参数外,还需要把事务处理器InvocationHandler
传进去,然后更改创建实例对象中硬编码的部分用事务处理器方法替代即可。难点在于字符串的拼接。