一、代理模式介绍
什么是代理模式呢?我很忙,忙的没空理你,那你要找我呢就先找我的代理人吧,那代理人总要知道被代理人能做哪些事情不能做哪些事情吧,那就是两个人具备同一个接口,代理人虽然不能干活,但是被代理的人能干活呀。说白了经纪人懂吧,整不好就绿你的那种。
二、JDK静态代理
场景:比如西门庆找潘金莲,那潘金莲不好意思答复呀,咋办,找那个王婆做代理呀,然后就绿了大郎。。。
第一步:定义一种类型的女人,王婆和潘金莲就属于这个类型的女人
/**
* 定义一种类型的女人,王婆和潘金莲就属于这个类型的女人
*/
public interface KindWomen {
//这种类型的女人能做什么事情呢?
public void makeEyesWithMan(); //抛媚眼
public void happyWithMan(); //你懂吧...
}
第二步:定义一个潘金莲是什么样的女人
//定义一个潘金莲是什么样的女人
public class PanJinLian implements KindWomen {
@Override
public void makeEyesWithMan() {
System.out.println("潘金莲抛媚眼");
}
@Override
public void happyWithMan() {
System.out.println("潘金莲在和男人做那个.....");
}
}
第三步:定义一个王婆,作为经纪人。
public class WangPo implements KindWomen {
private KindWomen kindWomen;
public WangPo() { //潘金莲的代理
this.kindWomen = new PanJinLian();
}
//她可以是KindWomen的任何一个女人的代理,只要你是这一类型
public WangPo(KindWomen kindWomen) {
this.kindWomen = kindWomen;
}
public void happyWithMan() {
this.kindWomen.happyWithMan(); //自己老了,干不了,可以让年轻的代替
}
public void makeEyesWithMan() {
this.kindWomen.makeEyesWithMan(); //王婆这么大年龄了,谁看她抛媚眼?他只能帮忙传递一下了。。
}
}
第四步:整一个西门庆。
/**
* 定义一个西门庆,他是个什么都懂吧。
* 用快递小哥的话:您是什么东西?
* 用垃圾分拣阿姨的话:您是个什么垃圾?
*/
public class XiMenQing {
/**
* 看过水浒传的都知道,潘金莲抛了个媚眼,然后西门庆就把持不住了,
* 然后西门庆去找王婆撮合,王婆因为贪图西门庆的钱财就答应做潘金莲的经纪人了,
* 然后绿了的大郎。。
* 那我们假设一下:
* 如果没有王婆在中间牵线,这两个不要脸的能成吗?难说的很 !
*/
public static void main(String[] args) {
//把王婆叫出来
WangPo wangPo = new WangPo();
//然后西门庆就说,我要和潘金莲happy,然后王婆就安排了西门庆那出戏。
wangPo.makeEyesWithMan(); //看到没,虽然表面上时王婆在做,实际上happy的是潘金莲
wangPo.happyWithMan();
}
}
由此可见:代理模式是对开闭原则的典型实践,对扩展开放,对修改关闭
缺点:一个代理类只能代理一个业务接口,如果要代理多个业务接口需要定义多个实现类和代理类,如果在调用代理业务类前后的代码是一样的,则多个代理类会有很多冗余代码。
三、JDK动态代理
动态代理是指,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理 对象只是由代理生成工具(不是真实定义的类)在程序运行时由 JVM 根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。
对比静态代理,静态代理是指在程序运行前就已经定义好了目标类的代理类。代理类与目标类的代理关系在程序运行之前就确立了。
动态代理的实现方式常用的有两种:使用 JDK 的 Proxy,与通过 CGLIB 生成代理。
Jdk 的动态要求目标对象必须实现接口,这是 java 设计上的要求。 从 jdk1.3 以来,java 语言通过 java.lang.reflect 包提供三个类支持代理模式 Proxy, Method 和 InovcationHandler。
A、通过 JDK 的 java.lang.reflect.Proxy 类实现动态代理,会使用其静态方法 newProxyInstance(),依据目标对象、业务接口及调用处理器三者,自动生成一 个动态代理对象。
public static newProxyInstance ( ClassLoaderloader, Class<?>[]interfaces, InvocationHandlerhandler)
loader:目标类的类加载器,通过目标对象的反射可获取
interfaces:目标类实现的接口数组,通过目标对象的反射可获取
handler:调用处理器。
B、 InvocationHandler 是个接口,其具体介绍如下:
实现了 InvocationHandler 接口的类用于加强目标类的主业务逻辑。这个接口中有一个 方法 invoke(),具体加强的代码逻辑就是定义在该方法中的。程序调用主业务逻辑时,会自动调用 invoke()方法。
//Object proxy:代理对象
//Method m :调用的方法
//Object [] args: 调用方法的参数
public Object invoke(Object proxy, Method m, Object[] args)
C、 Method 类对象,该类有一个方法也叫 invoke(),可以调用目标类的目标方法。 这两个 invoke()方法,虽然同名,但无关。 public Object invoke ( Object obj, Object... args) obj:表示目标对象 args:表示目标方法参数,就是其上一层 invoke 方法的第三个参数
JDK动态代理实现如下:
场景:计算每个方法的执行时间
第一步:创建业务接口类
public interface SomeService {
void doSome();
void doOther();
}
第二步:创建业务接口的实现类
public class SomeServiceImpl implements SomeService {
public void doSome(){
int sum = 0;
for(int i=1;i<9000000;i++){
sum += i;
}
System.out.println("do some...");
}
public void doOther(){
int sum = 0;
for(int i=1;i<7000000;i++){
sum += i;
}
System.out.println("do other...");
}
}
第三步:创建代理类
public class TimeInvocationHandler implements InvocationHandler {
//目标对象
private Object target;
public TimeInvocationHandler(Object obj){
this.target = obj;
}
/**
* proxy:
* 代理对象的引用
* method:
* 目标对象的方法
* args:
* 目标方法的实际参数列表(实参)
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long begin = System.currentTimeMillis();
//无论是反射机制还是传统方式调用一个方法,必须具备四个要素:对象、方法、参数、返回值。
//比较传统的方法调用方式:
// boolean loginSuccess = userService.login("zhangsan" , "123");
// 返回值 = 对象.方法(实参);
//使用反射机制调用方法:
// 返回值 = 方法.调用(对象, 参数);
Object retValue = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println(method.getName() + "耗时" + (end - begin) + "毫秒");
return retValue;
}
//实例方法,绑定业务对象并返回一个代理类
public Object getProxy(){
// 通过反射机制,创建一个代理类对象并返回实例,用户进行方法调用时使用
// 创建代理对象时,需要传递该业务类的类加载器(用来获取业务实现类的元数据,调用真的的业务方法)、接口、handler实现类(this是当前对象)
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
}
第四步:创建测试类
public class Test {
public static void main(String[] args) {
//目标对象(真正执行/处理业务的对象)
SomeService someService = new SomeServiceImpl();
//代理对象(JDK的动态代理只能代理接口)
/*
* loader:类加载器
* 动态代理当中,代理类是反射机制构造的,在硬盘上看不见,反射机制中拼接的class字节码,
* 虽然看不到,但是在内存中会生成临时的“代理类.class”
* 只要是类,一定要通过类加载器加载才行,所以第一个参数必须传类加载器。
* 规定:代理类和目标类必须通过同一个类加载加载。
*
* interfaces:指定代理的哪些接口
* 代理类和目标类所实现的接口应该是一样的。
*
* h: 调用处理器(InvocationHandler)
* 调用处理器中有一个invoke方法。
* 这个invoke方法什么时候执行?
* SUN规定:
* 当代理对象调用代理方法的时候,“注册”在调用处理器当中的invoke方法会被自动调用。
*/
/*
SomeService someServiceProxy = (SomeService)Proxy.newProxyInstance(
someService.getClass().getClassLoader(),
someService.getClass().getInterfaces(),
new TimeInvocationHandler(someService));
*/
SomeService someServiceProxy = (SomeService)new TimeInvocationHandler(someService).getProxy();
//代理对象执行代理方法
someServiceProxy.doSome(); //这个doSome()是代理对象的代理方法。
someServiceProxy.doOther();
}
}
JDK动态代理:通过传进来的业务实现类和方法进行调用业务实现类的同名方法
缺点:JDK动态代理的代理对象在创建时,需要有业务实现类所实现的接口作为参数(因为后面代理方法需要根据接口内的方法名进行调用)。如果业务实现类没有实现接口而是直接定义接口的话,或者该业务实现类中增加了接口没有的方法(因为无法调用),就无法使用JDK动态代理。
四、CGLIB 动态代理
CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展Java 类与实现 Java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP。
使用 JDK 的 Proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在接口,则无法使用该方式实现。但对于无接口的类,要为其创建动态代理,就要使用 CGLIB 来实现。
CGLIB 代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。所以,使用 CGLIB 生成动态代理,要求目标类必须能够被继承,即不能是 final 的 类。
CGLIB 经常被应用在框架中,例如 Spring ,Hibernate 等。cglib 的代理效率高于 jdk。 项目中直接使用动态代理的地方不多。一般都使用框架提供的功能。
场景:目标类返回值改变为大写
第一步:创建目标类
//业务目标类
public class SomeService {
public String doSome(){
System.out.println("业务方法doSome");
return "abcd";
}
}
第二步:创建方法拦截器的类
//定义方法拦截器的类, 需要实现cglib的接口 MethodInterceptor(等同于jdk中的InvocationHandler)
public class MyMethodInterceptor implements MethodInterceptor {
private Object target;
public MyMethodInterceptor(Object target) {
this.target = target;
}
/**
* intercept特点:截取对目标方法的调用
* 参数:
* Object obj:代理对象
* Method method:目标方法
* Object[] args:方法参数
* MethodProxy proxy:方法的代理对象
* 返回值:
* Object:目标方法的执行结果
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("=========intercept=========");
//调用目标方法
Object result = method.invoke(target, args);
if( result != null){
String str = (String)result;
result = str.toUpperCase();
}
return result;
}
}
第三步:创建获取代理对象的工具类
/**
* 创建代理对象
*/
public class ProxyFactory {
public Object createProxy(Object target){
//使用Enhancer对象创建目标类的代理对象
//创建Enhancer对象
Enhancer en = new Enhancer();
//指定目标类
en.setSuperclass(SomeService.class);
//指定方法拦截器对象,
en.setCallback(new MyMethodInterceptor(target));
//创建代理对象
return en.create();
}
}
第四步:创建测试类
public class MyTest {
public static void main(String[] args) {
//创建目标对象
SomeService target = new SomeService();
//创建工具类
ProxyFactory factory = new ProxyFactory();
//创建代理对象
SomeService proxy = (SomeService) factory.createProxy(target);
System.out.println("代理对象父类名称:"+proxy.getClass().getSuperclass().getName());
System.out.println("代理类名称:"+proxy.getClass().getName());
//通过代理对象执行方法。实现功能增强
String str = proxy.doSome();
System.out.println("str:"+str);
}
}
运行:查看代理对象父类名可以看出是继承自目标类的
在Spring框架中AOP动态代理机制,如果被代理对象实现了需要被代理的接口,则使用JDK的动态代理,如果没有实现接口则使用CGLIB动态代理,spring会自动在JDK动态代理和CGLIB动态代理之间来回切换, 如果需要强制使用CGLIB动态代理可以在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>