何为代理?
Java中的代理,开源理解为通过代理去访问实际的目标对象,比如呢?我们平常买卖二手车的中间商,就可以看作一个代理类,不过你也可以直接去和二手车的主人买卖。
那这种情况,在Java中就被称之为代理,代理类除了去实现目标对象外,他还可以去在其中增加许多额外功能。
理论扩展:
主要解决的问题:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。 代理模式的必要条件:共同接口、代理对象、目标对象。 宏观特性:对客户端只暴露出接口,不暴露它以下的架构。 优点:中间隔离了一层,更加符合开闭原则。
代理是一种?
代理是一种设计模式
- 他并非一种自带的功能,而是一种设计模式。
- 在代理模式中,一个类代表另一个类的功能。
- 这种类型的设计模式属于结构型模式。
- 在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
- 目的:为其他对象提供一种代理以控制对这个对象的访问。
代理模式分类
代理模式分为两种类型:
- 静态代理
- 动态代理
实现静态代理
理论不多说,从代码中理解。
创建接口
首先,我们创建一个接口,一般我们一个功能都会去设计一个接口,包括我们的三层架构也是这样,所以我这也写一个接口。
代码语言:javascript复制/**
* @author JanYork
* @date 2022/10/25 8:59
* @description 假设Demo是买东西的接口
*/
public interface Demo {
/**
* 买东西
* @param name 东西的名字
* @param price 东西的价格
* @return 买东西的结果
*/
String buy(String name, int price);
}
那我们就,假设这个Demo
是买东西的接口。
也是提供一个简简单单的购买东西的方法(实际上就是输出测试一下)。
实现接口
我们还要去实现这个接口,也就是我们的接口实现类。
代码语言:javascript复制/**
* @author JanYork
* @date 2022/10/25 9:03
* @description 真实的Demo类(被代理类)
*/
public class RealDemo implements Demo {
public RealDemo(String name, int price) {
this.name = name;
this.price = price;
}
private String name;
private int price;
@Override
public String buy(String name, int price) {
System.out.println("买了" name "花了" price "元");
return "买了" name "花了" price "元";
}
}
给他两个参数,一个构造,然后重写接口提供的方法,这个不需要多说。
代理类
代码语言:javascript复制/**
* @author JanYork
* @date 2022/10/25 9:05
* @description Demo的代理类
*/
public class ProxyDemo implements Demo {
private RealDemo realDemo;
public ProxyDemo(String name, int price) {
this.realDemo = new RealDemo(name, price);
}
@Override
public String buy(String name, int price) {
System.out.println("-----代理类开始买东西------");
String result = realDemo.buy(name, price);
System.out.println("------代理类买东西结束------");
return result;
}
}
创建代理后我们也需要去实现这个Demo
接口。
然后需要注入实现接口的类的对象(也就是真实类)。
代码语言:javascript复制private RealDemo realDemo;
然后实现构造:
代码语言:javascript复制public ProxyDemo(String name, int price) {
this.realDemo = new RealDemo(name, price);
}
然后在重写方法里面调用,可以在调用方法前后干一些事情。
然后我们创建一个Test
类测试:
静态代理缺陷
问:既然静态代理可以方便的达到目的,那他有什么缺点吗?
静态代理在代码运行之前就需要创建好代理类,因此对于每一个代理对象都需要建一个代理类去代理。如果说,你需要代理的对象很多,那就需要创建很多代理类,降低程序的可维护性。
问:那如何解决这个缺陷呢?
动态构建代理类,也就是动态代理。
动态代理
动态代理的代理类是在运行过程中产生的。
Java
提供了两种实现动态代理的方式:
- 基于
JDK
的动态代理。 - 基于
Cglib
的动态代理。
特点:字节码随用随创建,随用随加载。 作用:在不修改源码的基础上对方法增强。
基于JDK实现
实现
Jdk
的动态代理需要实现InvocationHandler
接口,然后实现其中的invoke
方法。
我们创建一个类,实现InvocationHandler
(来自java.lang.reflect
反射包下)接口。
这个类下,有非常丰富的详细的解释,可以看看。
实现了接口后,我们给他用Object
来替代原先静态代理类中的这一段:
private RealDemo realDemo;
public ProxyDemo(String name, int price) {
this.realDemo = new RealDemo(name, price);
}
也就是这样:
代码语言:javascript复制private Object target;
public ProxyHandler(Object target) {
this.target = target;
}
然后重写invoke
方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-----动态代理开始------");
Object invoke = method.invoke(target, args);
System.out.println("------动态代理结束------");
return invoke;
}
里面有多个参数,稍后说明。
调用
首先还是创建真实对象,并且给出构造。
代码语言:javascript复制RealDemo realDemo = new RealDemo("苹果", 10);
然后创建动态代理对象,将正式对象传输过去。
代码语言:javascript复制ProxyHandler proxyHandler = new ProxyHandler(realDemo);
然后我们调用,实际上就是使用Proxy
这个类来调用newProxyInstance
这个方法。
Proxy.newProxyInstance(realDemo.getClass().getClassLoader(), realDemo.getClass().getInterfaces(), proxyHandler);
这个方法是一个静态方法,参数如下。
代码语言:javascript复制newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
第一个是一个类加载器,第二个是一个带泛型的Class数组,并且要求是一个interfaces
,第三个参数就是实现了InvocationHandler
的对象了。
那我们调用时给出的参数就是:
代码语言:javascript复制//获取真实类的类加载器
realDemo.getClass().getClassLoader(),
//获取真实类所实现的接口
realDemo.getClass().getInterfaces(),
//实现InvocationHandler接口的对象
proxyHandler
这部分对反射这方面知识有很多设计,不懂的还是建议去折腾折腾。
那我们整个调用动态对象的方法,如下:
代码语言:javascript复制public class TestDemo {
public static void main(String[] args) {
// ProxyDemo proxyDemo = new ProxyDemo("苹果", 10);
// proxyDemo.buy("苹果", 10);
//调用动态代理
RealDemo realDemo = new RealDemo("苹果", 10);
ProxyHandler proxyHandler = new ProxyHandler(realDemo);
Demo demo = (Demo) Proxy.newProxyInstance(realDemo.getClass().getClassLoader(), realDemo.getClass().getInterfaces(), proxyHandler);
demo.buy("苹果", 10);
}
}
Proxy.newProxyInstance
方法返回的是一个Object
,我们将他转型为我们的接口对象。
然后通过对象调用对应方法。
效果
优势
一个动态代理可以对N个需要代理的对象起作用,只需要将需要代理类的参数放入Proxy.newProxyInstance
方法即可。
缺陷
JDK
只能代理接口!
JDK
动态代理确实只能代理接口,JDK动态代理是基于接口的方式,换句话来说就是代理类和目标类都实现同一个接口。如果想要代理类的话可以使用CGLib
,CGLib
动态代理是代理类去继承目标类,然后实现目标类的方法。
基于CGLib实现
首先引入依赖:
代码语言:javascript复制<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>版本号</version>
</dependency>
如果你是Spring
项目,那就不必了,已经整合了。
我们创建一个类,用于给CGLib
测试。
/**
* @author JanYork
* @date 2022/10/25 10:32
* @description 基于Cglib的动态代理的目标类
*/
public class CgDemo {
public String buy(String name, int price) {
System.out.println("买了" name ",价格是" price);
return "买了" name ",价格是" price;
}
}
然后创建CGLib
的代理类:
/**
* @author JanYork
* @date 2022/10/25 10:34
* @description
*/
public class CgProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("-----cglib代理开始------");
Object invoke = methodProxy.invokeSuper(o, objects);
System.out.println("------cglib代理结束------");
return invoke;
}
}
调用
代码语言:javascript复制//创建Enhancer对象
Enhancer enhancer = new Enhancer();
//设置父类(也就是目标类)
enhancer.setSuperclass(CgDemo.class);
//设置回调函数
enhancer.setCallback(new CgProxy());
//创建代理对象
CgDemo cgDemo = (CgDemo) enhancer.create();
//调用代理对象的方法
cgDemo.buy("苹果", 10);
Enhancer
是cglib.proxy
下的一个生成动态子类以启用方法拦截的类。
Enhancer
类是CGLib
中最常用的一个类,和JDK 1.3
动态代理中引入的Proxy
类差不多(Proxy
类是Java
动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象)。 和Proxy
不同的是,Enhancer
类既能够代理普通的class
,也能够代理接口。Enhancer
创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object
中继承的toString()
和hashCode()
方法)。
效果
应用场景
问:动态代理这么牛,平常工作中有使用到吗?
在平常的业务代码,几乎是用不到代理的。
但是,Spring
系列框架中的AOP
,以及RPC
框架中都用到了动态代理。
如:AOP通过动态代理对目标对象进行了增强,比如我们最常用的前置通知、后置通知等。
全篇完,我是小简,下篇再见。