代理(Proxy)
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法
定义:给目标对象提供一个代理对象,并且由代理对象控制对目标对象的引用
目的:
①:通过代理对象的方式间接的访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性 ②:通过代理业务对原有业务进行增强
java当中有三种方式来创建代理对象:
- 静态代理
- 基于jdk(接口)的动态代理
- 基于CGLLIB(父类)的动态代理。
静态代理
创建一个 Image 接口和实现了 Image 接口的实现类。ProxyImage 是一个代理类,屏蔽具体的实现过程并且提供比真实实现过程更多的前后增强操作。
ProxyPatternDemo 类使用 ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示
public interface Image {
void display();
}
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName){
this.fileName = fileName; loadFromDisk(fileName);
}
@Override public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
/**proxy**/
public class ProxyImage implements Image{
private RealImage factory;
private String fileName;
public proxy(String fileName) {
// TODO Auto-generated constructor stub
if(realImage == null){
factory = new RealImage(fileName);
}
this.fileName = fileName;
}
@Override public void display() {
//before
System.out.println("image before.... ");
realImage.display();
System.out.println("image after.... ");
//after
}
}
public class ProxyTestDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg"); // 图像将从磁盘加载 image.display();
// 图像不需要从磁盘加载
image.display();
}
}
细节:我们的真实实现对象必须实现我们的接口,同时代理对象也必须实现这一接口
静态代理存在哪些问题?
静态代理类优缺点:
优点:可以在不修改目标代码的情况下,扩展额外功能
缺点:因为代理类和目标类必须实现相同的接口,所以会有很多代理类,类太多,同时,一旦接口增加方法,目标对象很代理对象都需要维护
违反了开闭原则:程序对访问开放,对修改关闭,换句话来说,当需求发生变化时,我们可以增加新模块来解决新需求,而不是通过改变原来的代码来解决我们的新需求,这里我们的新需求都是通过ProxyImage中添加代码实现的。
假设image是一个定义的播放工具,如同数据库工具定义的规范。现在出了播放图片,又要新增一个播放视频的功能vedioImage,当前ProxyImage里面定义的RealImage是写死的。难道要新增一个vedioImage变量么,类似的在数据库驱动程序中也存在类似现象,javajdk只会定义广义统一的流程接口规范,每个数据库厂商根据该jdk接口自己去实现自家数据库的链接驱动。因此调用驱动时底层实现链接逻辑并不指定具体的实现类型,而是由上层启动类在外部指定,从而动态的加载相应的驱动类,并执行对应的方法流程。
JDK动态代理
动态代理的主要特点就是能够在程序运行时JVM才为目标对象生成代理对象。
常说的动态代理也叫做JDK 代理也是一种接口代理,之所以叫做接口代理,是因为被代理的对象也就是目标对象必须实现至少一个接口,没有实现接口不能使用这种方式生成代理对象,JDK中生成代理对象的代理类就是Proxy,所在包是java.lang.reflect.
import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
*动态代理工厂:不具体指定代理对象,代理类也不用实现代理接口,由底层JDK根据代理接口类型自动生成代理对象
* @author bamboo
* @description:
* @date 2020/12/14
*/
public class ProxyFactory implements InvocationHandler {
private Object target;
public StarInvocationHandler(Object target) {
this.target = target;
}
@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 Object getProxyInstance() {
// TODO Auto-generated method stub
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
public class JdkTest {
public static void main(String[] args) {
//由调用层决定具体由谁来执行
RealImage realImage = new RealImage();//1
Image image =(Image) new ProxyFactory(realImage).getProxyInstance();
image.display("d://image.jpg");
}
}
JDK代理方式不需要代理对象实现接口,但是实现对象一定要实现接口,但是我们在项目中有很多需要代理的类并没有实现接口,所以这也算是这种代理方式的一种缺陷
优点:代理类和实现类脱离了依赖关联,不用强绑定,完全由调用方使用时动态反射出具体的实现类
缺点:代理的对象必须有实现类并且重写了该接口,否则无法实现代理。
比如,我们在JdkTest中//1行并不知道具体实现类,更确切说压根就没有实现类,那么通用驱动就不用写了吗?
cglib动态代理
如果我们 需要给没有是实现任何接口的目标类生成代理对象,JDK方式是做不到的。这是就可以使用继承目标类以目标对象子类的方式实现代理,这种方法就叫做Cglib代理,也叫做子类代理,他是在内存中构建一个子类对象从而 实现对目标对象功能的扩展。
Cglib是一个强大的高性能代码生成包,他可以在运行期扩展java类和扩展java接口。它广泛的被许多AOP框架使用,例如Sring AOP和synaop,为他们提供方法的intercepion(拦截)
Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类。
Cglib子类代理实现方法:
1.需要引入cglib的jar文件 cglib-2.2.2.jar asm-3.3.1.jar,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
*cglib代理:不需要实现类,只需要提供普通类,剩下的工作有底层完成
* @author bamboo
* @description:
* @date 2020/12/14
*/
public class CglibProxyFactory implements MethodInterceptor {
//维护目标对象
private Object target;
public CglibProxyFactory(Object target) {
this.target = target;
}
/**
* @Description:获得目标类的代理对象
*/
public Object getProxyObject(){
//1、工具类
Enhancer enhancer = new Enhancer();
//2、设置父类
enhancer.setSuperclass(target.getClass());
//3、设置回调
enhancer.setCallback(this);
//4、创建代理类
Object proxyObject = enhancer.create();
return proxyObject;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("工作前");
//执行目标对象的目标方法
method.invoke(target,objects);
System.out.println("工作后");
return null;
}
}
//普通类
class Image {
void display() {
System.out.println("display");
}
}
public class CglibTest {
public static void main(String[] args) {
Image image = new Image();
Image proxyObject = (Image)new CglibProxyFactory(image).getProxyObject();
proxyObject.sing();
}
}
总结:其实cglib可jdk的动态代理很类似,最终都是通过被代理类执行原始方法,只是jdk使用的是反射,cglib使用的fastcalss机制
到这三种代理方式我们都介绍完了,下面总结一下:
1、如果目标对象实现了接口,我们就采用JDK方式实现动态代理
2、如果目标对象没有实现接口,我们就需要采用cglib方式实现动态代理;
扩展在实际项目中使用样例:接口+注解的动态代理
mybatis中Map接口定义了方法,不用具体实现接口和xml的sql语句映射,代理类自动完成这些工作。
springCloud中对于feign调用微服务时只依赖了服务方提供的interface方法包,调用方这边并没有imp相关的接口实现类,这个过程也是由代理类完成,从而实现http访问服务方的接口返回结果给调用方
这里以微服务client端为例
/**
* @author bamboo
* @description: 接口类
* @date 2020/12/14
*/
public @interface UserService {
String getById(Integer id) ;
}
/**
* @author bamboo
* @description: 接口方法执行拦截器:执行接口方法时做增强,使用http调用远程服务并返回值
* @date 2020/12/14
*
public class CustomerInvocationHandler implements InvocationHandler {
//被代理类
private final Class<?> serviceClass;
public CustomerInvocationHandler(Class<?> serviceClass) {
this.serviceClass = serviceClass;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy = " + serviceClass.getName() + ", method = " + method.getName() + ", args = " + Arrays.deepToString(args));
Socket socket = new Socket("127.0.0.1", 8889);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
//数据打包
RpcRequest rpcRequest = new RpcRequest(serviceClass.getName(), method.getName(), method.getParameterTypes(), args);
//通过socket发送数据
objectOutputStream.writeObject(rpcRequest);
//接收返回数据
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
return inputStream.readObject();
}
}
import java.lang.reflect.Proxy;
/**
* @author bamboo
* @description: 代理工厂
* @date 2020/12/1411:47
*/
public class ProxyFactory {
public static Object getProxyObject( ClassLoader target,
Class<?>[] interfaces,
InvocationHandler h){
return Proxy.newProxyInstance(target,interfaces,h);
}
}
通过RPC远程调用
- 创建代理类的InvocationHandler,相当于AOP拦截器,使得调用代理类的方法都会转入invoke中执行。
- 创建代理类(调用方法已经在invoke中完成)。
- 调用相应的方法。
public class TestRpc {
public static void main(String[] args) {
//创建代理类的InvocationHandler拦截器
CustomerInvocationHandler customerInvocationHandler=new CustomerInvocationHandler(UserService.class);
//创建代理类
//代理类会声明被代理类的接口
UserService userService=(UserService) ProxyFactory.getProxyObject(UserService.class.getClassLoader(),new Class<?>[]{UserService.class},customerInvocationHandler);
//调用
String a=userService.getById(1);
System.out.println("RPC CONNECTED!"+a);
}
}
-------------------------------------------------------------------------------------
proxy = com.bamboo.demo.rpc.UserService, method = getById, args = [1]
RPC CONNECTED!bamboo
以上过程可以看出代理在我们常用场景中的重要性,不过这种使用也屏蔽了底层具体实现的细节,比如我们使用Mybatis如果不去看源码不关注底层其实直接一个接口就可以完成工作。同样的微服务中,我们只是使用其实只用给调用方一个接口它直接拿着用就可以了,连TestRpc注释调用之前的代码都不需要关注,是我们更加关注业务本身,从而抽繁化简,这才是编程工具使用称心的本心和初衷。