代理设计模式
静态代理
直接编写一个代理类,把源码直接编写好,编译后生成一个.class文件
简单实现
public interface Person{
// 租房
public void rentRoom();
}
public class Renter implements Person{
@Override
public void rentRoom(){
租房子
}
}
public class RenterProxy implements Person{
private Renter renter;
@Override
public void rentRoom(){
中介找房子,转租给租客....
租房子
中介给租客钥匙,租客入住...
}
}
租客想租房子,但是自己没有实力,就通过找一个代理(中介)来替他找房子,并租下。实际上来说最终还是租客租房子,只不过通过其他人来代替自己完成该任务,自己领包入住即可。
弊端
通过编写源码来实现代理模式,1000个类就会有一千个代理类,会产生类爆炸。
动态代理
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题
- JDK动态代理技术:只能代理接口。
- CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
- Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架
JDK动态代理
-
只能代理接口。
-
在程序运行过程中创建代理类。
-
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
代码实现
/**
* 被实现的接口
* @author hly
* @version 1.0
*/
public interface Person {
/**
* 相亲方法
*
* */
void blindDate();
}
/**
* 一位男士要去相亲
* @author hly
* @version 1.0
*/
public class Man implements Person {
/**
* 相亲
*/
@Override
public void blindDate() {
System.out.println("开始相亲.....");
}
}
- 代理对象的所有方法调用都会被转发到
InvocationHandler
接口的invoke()
方法中
/**
* @author hly
* @version 1.0
*/
public class MyInvocationHandler implements InvocationHandler {
// 持有目标对象(被代理的对象)
private Object target;
// 有参构造器,给target赋值
public MyInvocationHandler(Object target){
this.target = target;
}
/**
* 代理类底层执行的方法。即当代理类调用目标方法时,jvm底层转发到InvocationHandle接口实现类的invoke方法
* 再通过该方法,利用反射机制调用被代理对象的目标方法。
* 当代理类调用该方法时就可以做一些增强操作。
* 即代理对象就是媒婆,帮你约好人,相亲还是你自己去
* @param proxy 代理对象,后期需要用时,直接用
* @param method 被代理对象(目标对象)的目标方法
* @param args 目标方法的参数列表
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用目标对象方法前的操作
System.out.println("媒婆帮你找了一个相亲对象");
// 将反射机制调用目标对象的目标方法(相亲方法)
Object result = method.invoke(target, args);
// 调用目标对象方法后的操作
return result;
}
}
/**
* @author hly
* @version 1.0
*/
public class TestPerson {
@Test
public void test(){
// 创建目标对象
Person target = new Man();
/*
* 男人条件有点差,没能约到相亲对象
* 他找了个媒婆帮他找到他的相亲对象
* 媒婆就是那个所谓的代理类
*/
/*
Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),调用处理器对象)
第一个参数:目标对象的类加载器,
第二个参数:目标对象接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口
第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler
*/
//强转接口,而不是实现对象。
// 运行类型为class jdk.proxy2.$Proxy7
Person manProxy = ((Person) Proxy.newProxyInstance((target.getClass().getClassLoader()), target.getClass().getInterfaces(), new MyInvocationHandler(target)));
manProxy.blindDate();
}
}
- 注意:当调用其他方法时,也有可能会调用invoke方法。
.getClass()
方法是获取一个对象的运行时类信息,它不会触发invoke()
方法的执行。- 调用代理对象的其他方法:当你调用代理对象的非目标方法时,例如
toString()
,hashCode()
,equals()
等方法,都会触发invoke()
方法的执行。 - Object 类中的方法:如果代理的目标实现了 Object 类中的方法,如
wait()
,notify()
,notifyAll()
等,调用这些方法也会触发invoke()
方法的执行。 - 默认方法(Default Method):如果代理目标实现了接口中的默认方法,并且你调用了这些默认方法,也会触发
invoke()
方法的执行。
CGLIB代理
-
如果被代理类没有实现接口,那么这么实现动态代理?这时候就需要用到CGLIB了。这种代理方式就叫做CGLIB代理。
-
CGLIB代理也叫作子类代理,他是通过在内存中构建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,然后加入自己需要的操作。因为使用的是继承的方式,所以不能代理final 类
代码实现
- 先引入cglib依赖
cglib
cglib
3.3.0
/**
* 没有实现接口的类
* @author hly
* @version 1.0
*/
public class UserService {
public void login(){
System.out.println("用户正在登录系统....");
}
public void logout(){
System.out.println("用户正在退出系统....");
}
}
public void test01(){
// 创建字节码增加器
Enhancer enhancer = new Enhancer();
// 告诉cglib要继承哪个类
enhancer.setSuperclass(UserService.class);
// 设置回调接口
//和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,
// 而是:net.sf.cglib.proxy.MethodInterceptor
enhancer.setCallback(方法拦截器对象);
// 生成源码,编译class,加载到JVM,并创建代理对象
UserService userService = (UserService) enhancer.create();
userService.login();
userService.logout();
}
/**
* @author hly
* @version 1.0
*/
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target){
this.target =target;
}
/**
* 当代理对象调用方法时,底层会走这个方法
* @param proxy 代理对象,方便后期使用
* @param method 被代理对象的方法
* @param args 方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理对象调用方法.......");
//反射机制调用方法
System.out.println("底层通过反射调用被代理对象的method:"+method);
Object result = method.invoke(target, args);
return result;
}
}
@Test
public void test01(){
// 创建字节码增加器
Enhancer enhancer = new Enhancer();
// 告诉cglib要继承哪个类
enhancer.setSuperclass(UserService.class);
// 设置回调接口
//和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,
// 而是:net.sf.cglib.proxy.MethodInterceptor
enhancer.setCallback(new MyMethodInterceptor());
// 生成源码,编译class,加载到JVM,并创建代理对象
UserService userService = (UserService) enhancer.create();
userService.login();
userService.logout();
}
结果
JDK动态代理与CGLIB的区别
- 两者都可以实现动态代理机制,在运行过程中生成代理类写入程序。
- JDK动态代理只能代理接口类,底层通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现
- CGLIB既可以代理类也可以代理接口,大部分情况下代理类,通过继承被代理类实现其子类,来完成代理模式。底层通过net.sf.cglib.proxy.Enhancer类,和net.sf.cglib.proxy.MethodInterceptor;接口实现
- 在不破坏代理类源码的情况下,两者都起到了增加作用,遵循oop原则。解决静态代理带来的类爆炸问题
- 当代理类调用目标方法时,两者底层都转发到各自特定的一个接口实现类通过反射机制,-->method.invoke方法实现,但具体实现又不一样。一个是被代理方法,另一个是代理方法
//JDK动态代理
InvocationHandle
invoke()
// 被代理方法
method.invoke(target,args);
// CGLIB
MethodInterceptor
intercept()
// 代理方法
methodProxy.invokeSuper(o,objects);