文章目录
一些闲话:看了Spring的AOP部分,又感觉没有理解,先学习一下代理模式了解一下底层然后再回去品一品
1. 结构型模式
1.1 什么是结构型模式
用来将类或者对象按照某种布局组成更大的结构的思想方法
1.2 结构型模式的分类
可分为采用继承机制的类结构型模式与组合或聚合机制的对象结构型模式,其中对象结构型模式耦合更低,有更多的应用
1) 代理模式
2) 适配器模式
3)装饰者模式
4)桥接模式
5)外观模式
6)组合模式
7)享元模式
2.代理模式
2.1 代理模式的小栗子
通常情况下,消费者想购买汽车不会直接找到汽车生产厂家,而是通过各地代理商的4S店订购并进行后续保养,这样厂商就不用花费大力气去和消费者直接沟通以及进行售后服务。在这个例子里,消费者可以看做是访问对象,而生产厂家可以看作是目标对象,而代理对象就是4S店。
2.2 什么是代理模式
属于对象结构型模式,在访问对象A要访问目标对象B时,因为某些原因访问对象无法直接引用目标对象,而需要一个代理对象作为访问对象和目标对象之间的中介。
2.3 代理模式的分类
根据代理对象生成时机的不同分为静态和动态两种代理,静态代理生成在编译期间,动态代理生成在Java运行时,动态代理又分为通过接口实现的JDK代理和通过父类实现的CGLib动态代理。
2.4 代理模式的结构
3个组成部分
1)抽象主题类 Subject: 定义了真实主题和代理对象类所实现的业务方法,接口or抽象类
2)真实主题类 Real Subject: 实际被访问的对象,实现了抽象主题中的具体业务
3)代理类 Proxy:提供与真实主题中相同的接口,可以访问,控制或扩展真实主题的功能
2.5 静态代理
2.5.1 静态代理小栗子
需要对sell()方法做一个增强,各个类之间的关系如UML图所示
代码实现:
//接口SellTickets
public interface SellTickets {
void sell();
}
//TrainStation类
public class TrainStation implements SellTickets{
public void sell(){
System.out.println("火车站卖票");
}
}
//ProxyPoint类
public class ProxyPoint {
//声明火车站类对象
private TrainStation station = new TrainStation();
//增强sell()方法
public void sell(){
System.out.println("收点手续费");
station.sell();
}
}
//用于测试的Client类
public class Client {
public static void main(String[] args) {
ProxyPoint pp = new ProxyPoint();
//用的是ProxyPoint对象实例的方法
pp.sell();
}
}
2.6 JDK动态代理
2.6.1 JDK动态代理的使用
Java中提供了一个动态代理类,提供了一个创建代理对象的newProxyInstance()
静态方法,这个方法的参数在注释中写了。在上面例子的基础上进行修改。把接口和真实主题类车站类复制到新的package中,再增加一个工厂类ProxyFactory。通过这个工厂类来获取代理对象,并进行使用。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author Cyan
* @version 1.0
* @description: 代理工厂:用来创建代理对象的工厂类
* @date 2021/11/29 12:59
*/
public class ProxyFactory {
//一个私有的目标对象实例
private TrainStation station = new TrainStation();
//获取代理对象的方法
public SellTickets getProxyObject(){
/*
newProxyInstance()方法参数说明:
ClassLoader loader : 类加载器,用于加载代理类,使用station对象的类加载器即可
Class<?>[] interfaces : 真实对象所实现的接口,代理模式station对象和代理对象实现相同的接口
InvocationHandler h : 代理对象的调用处理程序
该方法返回的是Object对象,需要做一个强转,转为接口的类型
*/
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
/*
InvocationHandler中invoke方法参数说明:
proxy : 代理对象
method : 对应于在代理对象上调用的接口方法的 Method 实例
args : 代理对象调用接口方法时传递的实际参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理点收取手续费(jdk动态)");
//使用真实对象的方法
Object result = method.invoke(station, args);
return result;
// return null;
}
}
);
return sellTickets;
}
}
2.6.2 JDK动态代理类代码
//只保留关键信息的动态代理类
public final class $Proxy0 extends Proxy implements SellTickets {
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
}
public final void sell() {
this.h.invoke(this, m3, null);
}
}
这个代理类实现了SellTickets接口,同时将这个invocationHandler接口所创建的匿名内部类对象传给了父类
2.6.3 动态代理类的执行
1.在测试类中通过代理对象调用sell()
方法
2.根据多态的特性,执行的是代理类($Proxy0)
中的sell()
方法
3.代理类($Proxy0)
中的sell()
方法中又调用了InvocationHandler
接口的子实现类对象的invoke()
方法
4.invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()
方法
2.6.4 遇到的问题
发现了异常:具体信息如下:
“C:\Program Files\Java\jdk1.8.0_131\bin\java.exe” “-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2021.1.3\lib\idea_rt.jar=65328:D:\Program Files\JetBrains\IntelliJ IDEA 2021.1.3\bin” -Dfile.encoding=UTF-8 -classpath “C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;D:\myGit\IdeaProjects\design_patterns\target\classes” com.cyan.pattern.proxy.jdk_proxy.Client
Exception in thread “main” java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to com.cyan.pattern.proxy.jdk_proxy.SellTickets
at com.cyan.pattern.proxy.jdk_proxy.ProxyFactory.getProxyObject(ProxyFactory.java:26)
at com.cyan.pattern.proxy.jdk_proxy.Client.main(Client.java:15)
Process finished with exit code 1
在网上查找到的类似的类型强转出现的异常,通常都是由于没有使用接口来获得代理而是使用了接口下面的子类,但是我是使用的接口也发生报错。。。。。。完全搞不明白了,蹲一个解答
2.7 CGLib动态代理
在没有定义接口的时候,JDK代理就扑街了,因为JDK代理就是对接口进行代理。这里我们用CGLib来实现。
2.7.1 CGLib是个啥
CGLib是个第三方的代码生成包,为没有实现接口的类实现代理
2.7.2 CGLib代理怎么用
1.注入CGLib的依赖(导入CGLib这个jar包)
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>
- 代理工厂与真实主题类
//真实主题类:火车站
public class TrainStation{
public void sell(){
System.out.println("火车站卖票");
}
}
//工厂类
mport net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author Tony
* @version 1.0
* @description: TODO
* @date 2021/11/29 17:07
*/
// 规范接口MethodInterceptor
public class ProxyFactory implements MethodInterceptor {
private TrainStation station = new TrainStation();
//获取代理对象的方法
public TrainStation getProxyObjects(){
//创建一个Enhancer对象,这里面的enhancer就是类似于JDK动态代理的Proxy类
Enhancer enhancer = new Enhancer();
//获得父类的字节码对象,指定父类
enhancer.setSuperclass(station.getClass());
//设置回调diao函数,参数是MethodInterceptor的子实现类的对象,目的就是为了用重写的intercept函数
enhancer.setCallback(this);
//创建代理对象
TrainStation proxyObj = (TrainStation) enhancer.create();
return proxyObj;
}
/*
intercept方法是具体来
intercept方法参数说明:
o : 代理对象
method : 真实对象中的方法的Method实例
args : 调用方法的实际参数
methodProxy :代理对象中的方法的method实例
*/
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//增强功能
System.out.println("代理点收取一些服务费用(CGLib动态代理方式)");
//调用TrainStation的sell()方法
Object result = method.invoke(station,args);
return result;
}
}
//用于测试的客户端类
public class Client {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory();
TrainStation proxyObj = factory.getProxyObjects();
proxyObj.sell();
}
}
2.8 JDK动态代理与CGLib动态代理的对比
- JDK1.8以后,CGLib的效率低于JDK代理,如果有接口的话就使用JDK动态代理
- CGLib不能对声明为final的类或者方法进行代理,因为final类是不能被继承的,而CGLIb需要动态生成被代理类的子类
- 相比于静态代理,动态代理会把声明的所有方法转移到一个集中的方法进行处理,JDK动态代理会用
InvocationHandler.invoke()
方法处理,CGLib动态代理会放在intercept()
里面。这样在方法比较多的时候进行变动时,可以不要像静态代理那样在实现类和代理类中都需要加以更改
2.9 代理模式的优缺点
优点:保护、扩展、解耦
缺点:增加系统复杂度
2.10 代理模式的使用场景(结合项目再自己体会吧)
1.远程代理
RPC思想
2.防火墙代理
VPN
3.保护代理
访问控制,在代理对象中给不同用提供不同的权限