代理模式概述
代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是使用了动态代理完成了代码的动态“织入”。
使用代理好处还不止这些,一个工程如果依赖另一个工程给的接口,但是另一个工程的接口不稳定,经常变更协议,就可以使用一个代理,接口变更时,只需要修改代理,不需要一一修改业务代码。从这个意义上说,所有调外界的接口,我们都可以这么做,不让外界的代码对我们的代码有侵入,这叫防御式编程。代理其他的应用可能还有很多。
上述例子中,类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。动态代理目前有两种常见的实现,jdk动态代理和cglib动态代理。
代理的目的:
“代理”的目的是构造一个和被代理的对象有同样行为的对象
代理(Proxy)是一种设计模式,提供了间接对目标对象进行访问的方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能.
这就符合了设计模式的开闭原则,即在对既有代码不改动的情况下进行功能的扩展。
举个例子来说明代理的作用:明星与经纪人之间就是被代理和代理的关系,明星出演活动的时候,明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子。
静态代理
在使用静态代理时,被代理对象与代理对象需要一起实现相同的接口或者是继承相同父类,因此要定义一个接口或抽象类.
代码案例:
package com.fan.domain3;
//①:歌星的接口
interface IStar {
void confer();//面谈
void signContract();//签合同
void sing();//唱歌
void last();//善后工作
}
//②:被代理类:真实对象
class RealStar implements IStar{
//除了唱歌方法自己有具体实现外,其他都是空实现
public void confer() {
}
public void signContract() {
}
//唱歌自己唱
public void sing() {
System.out.println("张韶涵唱歌");
}
public void last() {
}
}
//③: 代理类:也就是经纪人;代理类需要有真实对象的控制权 (引用),即经纪人对歌星有一定的控制权
class Proxy implements IStar {
//真实对象的引用,即你要帮谁代理(包含被代理人)
private IStar star;//接口类型,面向接口编程。
public Proxy() {
super();
}
public Proxy(IStar star) {
super();
this.star = star;
}
public void confer() {
System.out.println("经纪人面谈");
}
public void signContract() {
System.out.println("经纪人签合同");
}
public void sing() {
//注意这里不是代理人干核心业务(非经纪人唱歌),而是调用真实对象的方法
star.sing();
}
public void last() {
System.out.println("经纪人善后工作");
}
}
public class Test01{
public static void main(String[]args){
//创建明星对象(被代理对象) 和经纪人(代理对象)
IStar realStar = new RealStar();
Proxy proxy = new Proxy(realStar);
proxy.confer();//经纪人面谈
proxy.signContract();//经纪人签合同
proxy.sing();//表象代理人唱歌,本质是歌星唱歌//张韶涵唱歌
proxy.last();//经纪人善后工作
}
}
运行结果:
经纪人面谈
经纪人签合同
张韶涵唱歌
经纪人善后工作
静态代理总结:
代码中注意:
第一点:代理类和被代理类都实现同一个接口,表示两个类都是为了完成同一件事/功能(此功能/方法是抽象的,由实现类具体去实现)。
第二点:代理类 拥有 被代理类的 引用,以便对代理类进行其方法的调用。因为核心的业务逻辑不能由代理类完成,不安全或者没那种能力完成。
优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展.
缺点:
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
而动态代理方式可以解决上面的问题
动态代理:
没有代理类:方法运行过程中动态生成代理对象。
JDK动态代理:
我们也用一个歌星来实现动态代理的代码:
(1)创建接口,定义方法
package com.fan;
//被代理类实现的接口
public interface IStar {
public void sing(String name);//唱歌
public int eat(int money);//吃饭花钱
}
(2)创建接口的实现类(需要增强的类,被代理类):
package com.fan;
//被代理类/需要增强的类
public class RealStar implements IStar {
@Override
public void sing(String name) {
System.out.println(name +"歌星唱歌好听");
}
@Override
public int eat(int money) {
return money;//返回花销的钱数
}
}
3)创建一个单独的增强类(生产接口实现类的代理对象,并增强被代理类中的一些方法),并在增强类中使用Proxy类创建接口实现类的代理对象。
这里可以总结为:3+3(三个东西(要增强的接口,接口的实现类,代理类/增强类),三个参数(代理类中newProxyInstance有三个参数))
注意:增强类的名字一般以Proxy结尾,但他不是代理类,原因是它没有实现我们的明星接口,无法对外服务。
package com.fan;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//增强类,并不是代理类(此类中可以动态生成实现类的代理对象)
public class JDKRealStarProxy {
public static void main(String[] args) {
Class[] interfaces = {IStar.class};
/*
* newProxyInstance方法参数 :
第一个参数:增强类的类加载器
第二个参数:实现类实现的接口的Class数组
第三个参数:InvocationHandler(调用处理器类)接口
* */
//生成代理对象iStar,
IStar iStar = (IStar) Proxy.newProxyInstance(JDKRealStarProxy.class.getClassLoader(),
interfaces, new MyInvocableHandler(new RealStar()));
//测试代理对象
iStar.sing("周杰伦");
int n = iStar.eat(1000);
System.out.println("吃饭花了"+ n +"元");
}
}
//调用处理器类(InvocationHandler)
class MyInvocableHandler implements InvocationHandler{
//被代理对象,//(1.1)把创建的是谁的代理对象(这里是实现类),把谁传递过去
private Object obj;
//(1.2)有参构造传递被代理对象
public MyInvocableHandler(Object obj) {
this.obj = obj;
}
/*
Object proxy, 代理对象
Method method, 被代理的方法(通过反射获得的)
Object[] args,被代理方法的参数
* */
//(2)增强逻辑的方法invoke,会将这部分代码横向插入
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("获取增强方法的方法名"+method.getName());
//obj为真实对象/被代理对象;去调用自己类中的方法即method,args为方法参数。类似于对象.方法(参数)
Object invoke = method.invoke(obj, args);//让被代理对象的方法执行,obj为被代理对象
System.out.println("增强方法后的逻辑");
return invoke;
}
}
Proxy(jdk类库提供)根据B(B是接口的实现类)的接口生成一个实现类,我们成为C,它就是动态代理类(该类型是 $Proxy+数字 的“新的类型”)。生成过程是:由于拿到了接口,便可以获知接口的所有信息(主要是方法的定义),也就能声明一个新的类型去实现该接口的所有方法,这些方法显然都是“虚”的,它调用另一个对象的方法。当然这个被调用的对象不能是对象B(实现类B),如果是对象B,我们就没法增强了,等于饶了一圈又回来了。
所以它调用的是B的包装类,这个包装类需要我们来实现,但是jdk给出了约束,它必须实现InvocationHandler(调用处理器类),上述例子中就是JDKRealStarProxy , 这个接口里面有个方法,它是所有Target的所有方法的调用入口(invoke),调用之前我们可以加自己的代码增强。
看下我们的实现,我们在InvocationHandler里调用了实现类对象B(obj)的方法,调用之前增强了B的方法。
所以可以这么认为动态代理类C代理了InvocationHandler,InvocationHandler代理了我们的实现类B,两级代理。
整个JDK动态代理的秘密也就这些,简单一句话,动态代理就是要生成一个包装类对象,由于代理的对象是动态的,所以叫动态代理。由于我们需要增强,这个增强是需要留给开发人员开发代码的,因此代理类不能直接包含被代理对象,而是一个InvocationHandler,该InvocationHandler包含被代理对象,并负责分发请求给被代理对象,分发前后均可以做增强。从原理可以看出,JDK动态代理是“对象”的代理。
动态代理底层实现
动态代理具体步骤:
通过实现 InvocationHandler 接口创建自己的调用处理器;
通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
cglib动态代理
我们了解到,“代理”的目的是构造一个和被代理的对象有同样行为的对象,一个对象的行为是在类中定义的,对象只是类的实例。所以构造代理,不一定非得通过持有、包装对象这一种方式。
通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是cglib的思想。根据里氏代换原则(LSP),父类需要出现的地方,子类可以出现,所以cglib实现的代理也是可以被正常使用的。
这是Spring使用的方式,与JDK Proxy不同之处在于它不是面向接口的,而是基于类的继承。这似乎是有点违背代理模式的标准格式,不过这没有关系,所谓的代理模式只是一种思想而不是严格的规范。我们直接看它是如何使用的。
第一步:没有接口,我们直接有实体类:
package com.fan.cglibproxy;
public class Dog {
public void isAnimal()
{
System.out.println("我是动物");
}
}
第二步:类似于InvocationHandler,这里cglib直接使用一个叫MethodInterceptor的类,我们实现它,重写里面的唯一方法intercept。
package com.fan.cglibproxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyMethodInterceptor implements MethodInterceptor {
/**
* getProxy方法生成CGLIB代理对象
*
* @param cls 为被代理对象的class/被代理目标
* @return
*/
//利用Enhancer类生成代理对象,它的功能与java自带的Proxy类挺相似的。
public Object getProxy(Class cls) {
//CGLIB enhancer增强类对象
Enhancer enhancer = new Enhancer();
//设置代理目标
enhancer.setSuperclass(cls);
//定义代理逻辑对象为当前对象 ,对象要继承MethodInterceptor。并实现逻辑方法intercept
enhancer.setCallback(this);
return enhancer.create();
}
/**
* 方法描述 当对基于代理的方法回调时,在调用原方法之前会调用该方法
* 拦截对目标方法的调用
*
* @param proxy 代理对象
* @param method 拦截的方法
* @param args 拦截的方法参数
* @param methodProxy 方法代理
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("调用代理对象前");
Object result = methodProxy.invokeSuper(proxy, args);//真的是代理对象,不像JDk代理,还是真实对象
System.out.println("调用代理对象后");
return result;
}
}
Enhancer是啥:
1.Enhancer是cglib中使用频率很高的一个类,它是一个字节码增强器,可以用来为无接口的类创建代理。它的功能与java自带的Proxy类挺相似的。它会根据某个给定的类创建子类,并且所有非final的方法都带有回调钩子。
2.1 Callback
那么Enhancer使用的Callback具体有哪些呢?下面介绍以下这几种Callback。在cglib中Callback是一个标记接口,Enhancer使用的回调就是cglib中Callback接口的子接口。
2.1.1 Callback-MethodInterceptor
方法拦截器。这个东西和JDK自带的InvocationHandler很类似
Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable
这其中MethodProxy proxy参数一般是用来调用原来的对应方法的。比如可以proxy.invokeSuper(obj, args)。那么为什么不能像InvocationHandler那样用method来调用呢?因为如果用method调用会再次进入拦截器。为了避免这种情况,应该使用接口方法中第四个参数methodProxy调用invokeSuper方法。
第三步:测试使用:
package com.fan.cglibproxy;
public class CglibTest {
public static void main(String arg[])
{ MyMethodInterceptor cglib=new MyMethodInterceptor();
Dog a=(Dog)cglib.getProxy(Dog.class);
a.isAnimal(); //调用代理对象的isAnimal()方法
}
}
测试结果:
调用代理对象前
我是动物
调用代理对象后
如果你仔细和JDK Proxy比较,会发现它们其实是类似的:
首先JDK Proxy提供interface列表,而cglib提供superclass供代理类继承,本质上都是一样的,就是提供这个代理类的签名,也就是对外表现为什么类型。
然后是一个方法拦截器(MethodInterceptor),JDK Proxy里是调用处理器(InvocationHandler),而cglib里一般就是MethodInterceptor,所有被代理的方法的调用都是通过它们的invoke方法进行转接的,AOP的逻辑也是在这一层实现。
小结
对比JDK Proxy和cglib动态代理的使用方法和实现上的区别,就会发现,它们本质上都差不多,都是提供两个最重要的东西:
接口列表或者基类,定义了代理类(当然也包括原始类)的签名。
一个方法拦截器,完成方法的拦截和代理,是所有调用链的桥梁。
参考链接;
http://www.php.cn/java-article-407212.html
https://www.cnblogs.com/chinajava/p/5880887.html
https://www.nonelonely.com/article/1551711955457
https://www.nonelonely.com/article/1550247872963