不知道你有没有自己租过房子的经验,尤其是初到一个城市,相信大家都会因为租房问题而搞得心烦意乱,如果是自己租房的话还需要自己去联系房东,因为手里没有资源而浪费大量的时间,当每次大费周章一番后最后却又会发现对应的房源信息让我们大失所望,最后搞得自己心疲力竭。
同样的,不知道你有没有编码的经验,往往在实际开发的过程中,除了完成一些核心的业务功能之外,还需要大费周章的实现一些必须完成的非业务功能。比如开发每个交易系统时,需要进行输入参数的检验以及统一日志的生成。
还有就是每个同学在进行演讲的这个过程中,如果都使用自己的电脑进行控屏分享的话,会发现不仅要完成PPT的制作,除此之外每个人的电脑都需要完成一些控屏软件之类的安装工作。如果某个时刻,比如这些控屏软件失效不能使用了,会发现所有人的电脑又需要重新安装一遍,费时又费力,最后把我们忙得心烦意乱。
在今天这篇文章中了,我们讲解一种全新的设计模式,这种模式就是基于以上几种场景而出现的一种解决方式,也就是代理模式。大家一定要相信,所有的编程技术毫无疑问都是为了解决现实生活中的问题而出现的,任何技术的出现也都是源于我们的生活场景。比如:针对租房问题,现在大家百分之九十都不会直接去寻找房东,而是直接去找对应的中介公司,只需要将需求告诉中介公司即可,就可以轻松的获取到自己想要的房源。针对演讲了,只需要使用一台公用的电脑,比如只需要在老师的电脑装上控屏软件,哪个同学需要进行演讲,只需要将自己的PPT发送给老师即可,对每个同学来说,只需要专心完成自己的PPT即可。哪怕后面即使控屏软件失效不能用了,也不用每台电脑都大刀阔斧的改变,只需要让老师的这台电脑重新安装即可。这样一来,效率是不是提高了很多。
接下来了,来好好认识一下代理模式。值得一提的是,代理模式是23种设计模式当中极其重要的一种设计模式,作为Spring核心之一的AOP以及极其重要的声明式事务,还有就是Mybatis框架中的mapper接口都是使用代理模式的思想进行完成的。下面呢,不妨好好的来认识今天的主角-代理模式。
一、代理模式三种实现
概念:
代理模式的核心就是在不改变原有代码的基础上对一个方法进行功能性的增强,通常是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。说简单点,代理模式就是设置一个中间代理者来控制原目标对象的访问,以达到在不改变目标对象方法的同时对目标方法进行增强与拓展。
比如租房子的这个过程,貌似是自己在找房子,其实访问的并不是房东或者房子本身,而是直接访问的中介公司,中介公司把房子登记以及和房东交接的一系列工作都完成了,对于我们来说,只需要交钱选择最合适的房子进行入住即可。还有就是最常见的网络翻墙,对于一些境外的网站,每个人的电脑是没有办法进行直接访问的,通常的情况一些不合法的商家会配置一台服务器,经过一系列的技术处理,让其能够访问国外的服务器,每个人的电脑只需要访问商家配置的那一台服务器即可,间接性的访问到了国外的服务器。
代理模式,一共分为两种,一种为静态代理,其次就是动态代理。接下来呢,我们一起来正式认识一下,感受一下代理模式带来的魅力。
1.1静态代理
为了更简单更快速的理解静态代理模式,我们针对一个小案例来体会一下静态代理的思想,还是针对租房子的这个场景展开讲解,在现在这个大时代下,租房子只需要寻找中介公司就可以了。
第一步:创建接口
package com.ignorance.staticProxy;
public interface HouseInter {
public void rentHouse();
}
在静态代理模式中,需要创建一个公有的接口,并提供一个抽象的租房子rentHouse()方法,让房东以及中介对象去进行实现就可以了。
第二步:创建房东类,并且实现rentHouse()方法。
package com.ignorance.staticProxy;
public class HouseOwner implements HouseInter {
@Override
public void rentHouse() {
System.out.println("我是房东,我正在出租房子...");
}
}
针对房东这个类,只需要实现接口中的抽象方法即可。
第三步:创建中介类,实现rentHouse()方法。
package com.ignorance.staticProxy;
public class Intermediary implements HouseInter{
private HouseInter houseInter;
public Intermediary(HouseInter houseInter) {
this.houseInter = houseInter;
}
@Override
public void rentHouse() {
System.out.println("我是中介公司,正在处理房子的前置手续...");
this.houseInter.rentHouse();
}
}
针对中介类,也就是静态代理模式思想的核心,都说中介这个角色是帮助我们完成了目标对象的访问并且对目标对象也就是房东这个类进行了增强。那么是怎么增强的呢,只需要定义一个HouseInter类型的成员变量,在创建中介对象也就是在构造器初始化的时候给房东类赋值即可。然后在实现接口方法时进行调用目标对象的rentHouse()方法即可。
第四步:创建Client类进行测试
package com.ignorance.staticProxy;
public class Client {
public static void main(String[] args) {
HouseInter houseInter = new Intermediary(new HouseOwner());
houseInter.rentHouse();
}
}
测试结果如下:
通过上述案例,我们可以看出静态代理还是有一定优势的,在一定的程度上,在没有改变原有类方法基础上对方法进行了增强,完全满足对修改关闭、对拓展开放的原则,对维护代码时还是有一定的帮助的。但是通过上述的案例也会发现静态代理模式带来好处的同时也会带来一些缺陷性问题,比如完成一个功能,除了创建一个目标类,还需要额外的创建一个公用接口,以及创建一个代理对象,而且因为在代理对象中,使用的是硬编码,就会导致每次代理一个目标对象时都需要创建一个代理对象,并且通过硬编码的方式实现对应的方法。优点也有,就是在维护代码时能找到现成的代码,更容易定位到项目中出现的问题。缺点就是类与类之间的数量会急剧暴增,比如为了完成一个业务,需要创建三个类来协同完成,对程序来说还是有极大的负面影响。
所以说呢,针对静态代理功能单一,且类与类之间的膨胀问题,后续也就出现了动态代理的思想。
1.2JDK动态代理
在上面呢,我们讲解了静态代理这种模式的优缺点。优点是符合开闭原则,缺点嘛不言而喻,就是功能太过单一,类的数量会急剧膨胀。所以呢在静态代理的基础上,出现了动态代理的思想来进行加强和补充。
通过以上的案例,我们知道静态代理之所以会有问题,是因为在代理类已经把目标对象以及处理代码写死了,比如我有个用户类以及订单类,需要在该方法执行之前都加入一些逻辑,需要额外的定义两套接口,然后分别对这两个目标类建立两个代理类,然后通过硬编码的方式进行加强。
这样的设计肯定是有很大问题,对我们来说,工作都是重复而且繁琐没有任何技术性挑战的,所以呢,要进行改变。
所谓动态代理,就是希望代理类可以根据实际的业务需要动态进行生成,从而避免硬编码带来的麻烦。在Java语言中,说起动态,那么必不可少的就是反射技术,通过当前类的class字节码对象,不需要知道这个类是什么,就可以动态的获取到当前类的各种信息。
而针对动态代理,不需要额外实现,在JDK的API中,已经为提供了现成的方法和代码方便在业务中进行实现,我们只需要在合适的场景下对其进行灵活使用就可以了。
下面呢,我使用动态代理模式来完成对上面案例的改造:
第一步:创建公有接口HouseInter,并且定义抽象方法rentHouse()
package com.ignorance.dynamicsProxy;
public interface HouseInter {
void rentHouse();
}
第二步:创建房东类并且实现对应的rentHouse方法()
package com.ignorance.dynamicsProxy;
public class HouseOwner implements HouseInter {
@Override
public void rentHouse() {
System.out.println("我是房东,正在出租房子...");
}
}
第三步:也就是咱们动态代理的核心,对于动态代理来说,代理类并不是通过硬编码的方式实现的,而是调用JDK的相关API动态的帮我们生成在内存中的,接下来呢,创建一个ProxyFactory工厂类来帮助我们获取对应的代理对象。
package com.ignorance.dynamicsProxy;
public class ProxyFactory<T> {
//目标对象
private T targetObj;
//调用构造器时对目标对象进行初始化
public ProxyFactory(T targetObj) {
this.targetObj = targetObj;
}
//返回targetObj目标对象的一个代理对象
public Object getProxyInstance() {
return null;
}
}
对代理工厂类ProxyFactoy来说,它主要是用于帮助创建对应的代理类。对代理类的生成来说,它需要知道自己代理的目标对象是谁,所以在ProxyFactory这个类中定义了一个T类型的targetObj,也就是代理对象将要代理的目标对象,只需要在创建ProxyFactory类也就是调用构造方法传递具体的目标对象即可。
另外就是getProxyInstance()方法,这个方法是极其重要的,它主要负责创建并且返回targetObj目标对象所对应的代理对象。那么针对这个方法,那怎么进行实现呢?
前面讲到过,在JDK中,已经提供了开箱即用的API方法,只需要进行使用即可,如下代码所示:
//返回targetProxy目标对象的一个代理对象
public Object getProxyInstance() {
//目标对象所对应的类加载器
ClassLoader classLoader = targetObj.getClass().getClassLoader();
//目标对象大所对应的接口
Class<?>[] interfaces = targetObj.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
}
只需要调用Proxy类所对应的newProxyInstance()方法即可,这个方法就可以为返回相应的代理对象,通过方法声明来看,该方法需要提供三个参数。
第一个是当前目标对象所对应的类加载器,只需要通过目标对象的字节码对象进行获取即可,如上代码所示;
第二个参数需要获取目标对象所在的接口,同样可以通过目标对象的字节码对象进行获取。第三个参数为事件对象,通过参数来看;
第三个参数需要提供InvocationHandler接口的子对象,通过匿名内部类的方式传递一个子对象,在这个类中,同时需要实现invoke()方法。
这样一来目标对象被调用时,就会将目标对象的方法信息以及参数信息当做实际参数传递到invoke()方法的形参中,也就是invoke()方法对应的第二个以及第三个参数。这样一来,拿到目标对象的方法签名以及参数信息后,就可以轻松的控制代码逻辑,对目标类进行任意加强和处理。
如下所示:
package com.ignorance.dynamicsProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory<T> {
//目标对象
private T targetObj;
//调用构造器时对目标对象进行初始化
public ProxyFactory(T targetObj) {
this.targetObj = targetObj;
}
//返回targetObj目标对象的一个代理对象
public Object getProxyInstance() {
//目标对象所对应的类加载器
ClassLoader classLoader = targetObj.getClass().getClassLoader();
//目标对象大所对应的接口
Class<?>[] interfaces = targetObj.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("************开始执行代理前置方法************");
System.out.println("我是中介,请将你的信息告诉我,我将全力以赴帮助你找到满意的房子...");
System.out.println("************开始执行目标对象方法************");
Object returObj = method.invoke(targetObj, args);
return returObj;
}
});
}
}
下面呢,主要来说下第三个参数InvocationHandler,前面说过这个参数为事件对象,在调用目标对象的方法时,这个事件对象的抽象方法invoke方法会被调用,在invoke方法中,也存在三个参数。第一个参数为当前代理对象,第二个参数为目标对象当前执行的方法,最后一个参数为调用目标对象方法传入的实参。在invoke方法能够拿到目标方法以及其对应的实参列表后,那么就可以通过反射的方法来动态控制咱们目标对象的调用了。
接下来呢,创建一个Client来完成动态代理的测试:
package com.ignorance.dynamicsProxy;
public class Client {
public static void main(String[] args) {
HouseInter houseInter = new HouseOwner();
ProxyFactory<HouseInter> proxyFactory = new ProxyFactory<>(houseInter);
HouseInter proxyInstance = (HouseInter)proxyFactory.getProxyInstance();
proxyInstance.rentHouse();
}
}
测试结果如下所示:
接下来呢,不妨通过Debug的方式看一下通过ProxyFactory创建的这个代理对象到底是什么?
注意我标注红框的地方,在这个地方真正的暴露出了这个对象的目标地址,可以看出它存在一个$Proxy0开头的前缀,这个前缀相信大家都不会陌生,这也就是在内存中动态生成的代理对象。
1.3Cglib代理
继上面的内容,我们讲解代理模式的最后一种,也就是Cglib代理。通过静态代理和JDK动态代理的案例,不知道细心的你有没有发现两者之间的共同点,那就是两者都需要实现公共的接口,静态代理以及动态代理的代理对象必须是基于接口才能实现的。这也就导致在选择使用这两种代理模式之前,必须要让咱们的目标对象实现约定的接口。
那么如果觉得定义接口太麻烦,不想让目标类实现接口,而且还想动态的生成代理类动态的来增强以及拓展目标方法,这个时候就出现了一种新的代理模式,也就是Cglib代理模式。
Cglib代理也是属于动态代理的范围,跟JDK代理最大的区别是否需要基于接口生成,如果需要接口那么就是JDK代理,反之则就是Cglib代理。
Cglib代理也叫作子类代理,它主要是在内存中构建了一个目标对象的子类对象从而实现对目标对象的功能拓展,它的底层是通过字节码处理框架ASM来转换字节码并生成新的类。
因为Cglib代理走得是继承路线,所以就要求目标类不能被final关键字修饰,这样也比较容易理解,被final修饰的类为最终类,是无法被任何类继承的。
对于Cglib代理呢,因为它不属于JDK内置类,所以在使用的时候需要额外的导入关于Cglib的jar包,其中maven坐标如下:
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
接下来呢,使用Cglib代理来完成一个小案例:
第一步:创建目标类Student,并且完成progress()方法的编写。如下所示:
package com.ignorance.cglib;
public class Student {
public String progress(){
System.out.println("我是学生,我正在演示PPT");
return "ok";
}
}
第二步:创建CglibProxyFactory,但是必须要实现MethodInterceptor接口,并重写intercept()方法。如下代码所示:
package com.ignorance.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyFactory implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return null;
}
}
第三步:不管是哪种代理模式,都不能缺少目标对象,所以在Cglib代理中同样需要依赖目标对象,和JDK代理同样的思想,在构造器初始化目标对象:
package com.ignorance.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyFactory<T> implements MethodInterceptor {
//目标对象
private T targetObj;
//调用构造器时为目标对象赋值
public CglibProxyFactory(T targetObj) {
this.targetObj = targetObj;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return null;
}
}
第四步:和JDK代理一样,提供代理对象的创建方法getProxyInstance():
public Object getProxyInstance(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetObj.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
在JDK代理中,代理对象的生成是使用JDK为提供的内置类,也就是Proxy类的newProxyInstance()方法帮助在内存中构建的代理对象,在Cglib代理类中,同样提供了Enhancer工具类,可以使用该类的create()方法帮助构建代理类,值得注意的是,前面说Cglib代理类是通过继承的方式在内存中构建的子类,所以需要setSuperclass()方法指定代理的父类为当前目标对象,同时也需要通过setCallback()方法指定回调方法。
第五步:实现intercept()方法,这个方法同JDK代理的invoke()方法一样,当目标方法被调用时,这个时候会触发intercept()方法执行,并且将目标类的目标方法以及方法实参当作参数传入过来。这个时候获取到目标方法以及实参列表后,就可以根据业务需要灵活操作。如下代码所示:
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("*******************开始执行Cglib代理*******************");
System.out.println("我是公共电脑,我已经安装好了控屏以及PPT演示软件...");
Object returnVal = method.invoke(targetObj, objects);
return returnVal;
}
接下来了,创建一个测试类对Cglib代理案例进行测试:
package com.ignorance.cglib;
public class Client {
public static void main(String[] args) {
CglibProxyFactory<Student> cglibProxyFactory = new CglibProxyFactory<>(new Student());
Student proxyInstance = (Student)cglibProxyFactory.getProxyInstance();
proxyInstance.progress();
}
}
运行结果如下:
以上呢,就是讲解的代理模式的三种。下面呢,对代理模式整体来做一下总结:
1.代理模式的核心是在不修改原有代码的基础上对某个方法进行拓展和加强,从而符合开闭原则,让程序耦合度更低,从而让代码更加灵活富有生命力;
2.代理模式一共存在两个主体对象,一是被代理对象,也就是目标类对象;其次则是代理对象,在代理类中,聚合了被代理对象,从而可以在代理类中引用被代理对象,进而实现动态的加强效果。
3.静态代理和JDK代理都需要实现公共接口,而Cglib代理则不需要实现接口,其本质是通过继承的方式在内存中创建了一个目标类的子类。
二、代理模式使用场景以及案例
讲解了代理模式的几种实现方式后,可能比较抽象,大家理解的还是有点模糊。其次就是要想真正掌握一种编程思想,光靠空泛的理论知识是远远不够的,还需要在开发场景以及案例中不断地进行体会,从而达到融会贯通的地步。下面呢,我们通过几个小案例来加强对代理模式的理解。
2.1日志问题
在开发工作中,对于每个核心业务功能都需要考虑记录日志,以方便后期存在历史记录,从而解决系统引起的一系列问题。比如一个购买功能,用户说他买了1000,只给了他900,谁都说不清楚。这个时候就可以通过查询他的操作日志从而知道他具体做了什么操作,从而做到有据可查,从而避免很多麻烦,也让维护数据更加方便。
那么对于这个问题就完全可以使用代理模式,通过这个场景可以得出结论:对于每个功能来说,核心业务才是真正功能,记录日志并不算得上是核心功能,但是每个地方又不能不加日志。就跟之前说得演讲一个道理,对学生来说核心任务就是做PPT,但是为了完成演讲工作又不得不安装投屏以及PPT等软件。
所以呢,可以将日志功能作为代理类,将业务功能抽取为目标类,从而完成对应的功能,接下来呢,来模拟这个案例来进行设计。因为本次主要讲代理模式,所以就省略数据库操作,使用下面的工具类进行模拟这个场景。
第一步:创建OperateLog日志实体类
package com.ignorance.cglib.fact;
import java.io.Serializable;
import java.time.LocalDateTime;
public class OperateLog {
private Long id;
private String operateSerno;
private String operateCode;
private String operateType;
private LocalDateTime createTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOperateSerno() {
return operateSerno;
}
public void setOperateSerno(String operateSerno) {
this.operateSerno = operateSerno;
}
public String getOperateCode() {
return operateCode;
}
public void setOperateCode(String operateCode) {
this.operateCode = operateCode;
}
public String getOperateType() {
return operateType;
}
public void setOperateType(String operateType) {
this.operateType = operateType;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "OperateLog{" +
"id=" + id +
", operateSerno='" + operateSerno + '\'' +
", operateCode='" + operateCode + '\'' +
", operateType='" + operateType + '\'' +
", createTime=" + createTime +
'}';
}
}
第二步:创建Product实体类
package com.ignorance.cglib.fact;
import java.io.Serializable;
import java.math.BigDecimal;
public class Product {
private Serializable productId;
private String productName;
private BigDecimal price;
public Serializable getProductId() {
return productId;
}
public void setProductId(Serializable productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
@Override
public String toString() {
return "Product{" +
"productId=" + productId +
", productName='" + productName + '\'' +
", price=" + price +
'}';
}
}
第三步:创建DataUtils工具类模拟数据库操作
在的DataUtils这个类,主要有两个成员变量,一个为operateLogList,主要用于存储操作日志。用于模拟数据库的日志插入操作;businessList为核心交易,主要模拟核心交易的操作。
下面呢,创建这个工具类,主要模拟产品的插入以及删除操作,同时每个功能都要插入日志。
package com.ignorance.cglib.fact;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class DataUtils {
//日志list
private static List<OperateLog> operateLogList = new ArrayList<>();
//交易
private static List<Product> bussinessList = new ArrayList<>();
public static void createOperateLog(OperateLog operateLog){
operateLogList.add(operateLog);
}
public static void createBussiness(Product business){
bussinessList.add(business);
}
public static List<OperateLog> getOperateLogList() {
return operateLogList;
}
public static List<Product> getBussinessList() {
return bussinessList;
}
public static void removeBusinessById(Serializable id){
bussinessList = bussinessList.stream().filter(o -> !id.equals(o.getProductId())).collect(Collectors.toList());
}
}
第四步:创建业务类,也就是我们代理模式的目标类:
package com.ignorance.cglib.fact;
import java.io.Serializable;
public class Business {
public void saveProduct(Product product){
System.out.println("**************开始执行产品的添加**************");
DataUtils.createBussiness(product);
System.out.println("**************产品添加完成**************");
}
public void deleteById(Serializable id){
System.out.println("开始执行产品的删除");
DataUtils.removeBusinessById(id);
System.out.println("**************产品删除完成**************");
}
}
第五步:创建操作类型枚举类
package com.ignorance.cglib.fact;
import java.util.Arrays;
import java.util.stream.Collectors;
public enum OperateType {
OPERATE_TYPE_ADD("01","新增产品操作"),
OPERATE_TYPE_DELETE("02","删除产品操作");
private String operateCode;
private String operateDescr;
OperateType(String operateCode, String operateDescr) {
this.operateCode = operateCode;
this.operateDescr = operateDescr;
}
public String getOperateCode() {
return operateCode;
}
public void setOperateCode(String operateCode) {
this.operateCode = operateCode;
}
public String getOperateDescr() {
return operateDescr;
}
public void setOperateDescr(String operateDescr) {
this.operateDescr = operateDescr;
}
public static OperateType getTargetOperateTypeByCode(String code){
return Arrays.stream(OperateType.values()).
filter(o -> code.compareTo(o.getOperateCode()) == 0).
collect(Collectors.toList()).get(0);
}
}
第六步:创建注解类
package com.ignorance.cglib.fact;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateAnnotation {
OperateType value() ;
}
第七步:操作注解标记在业务方法上
package com.ignorance.cglib.fact;
import java.io.Serializable;
public class Business {
@OperateAnnotation(value = OperateType.OPERATE_TYPE_ADD)
public void saveProduct(Product product){
System.out.println("**************开始执行产品的添加**************");
DataUtils.createBussiness(product);
System.out.println("**************产品添加完成**************");
}
@OperateAnnotation(value = OperateType.OPERATE_TYPE_DELETE)
public void deleteById(Serializable id){
System.out.println("开始执行产品的删除");
DataUtils.removeBusinessById(id);
System.out.println("**************产品删除完成**************");
}
}
第八步:创建Cglib代理类,因为目标类Business没有实现接口,所以代理模式只能使用Cglib代理:
package com.ignorance.cglib.fact;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.UUID;
public class BusinessProxyFactory<T> implements MethodInterceptor {
private T targetObj;
public BusinessProxyFactory(T targetObj) {
this.targetObj = targetObj;
}
public Object getProxyInstance(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetObj.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("*******************开始执行日志操作*******************");
OperateAnnotation operateAnnotation = method.getDeclaredAnnotation(OperateAnnotation.class);
OperateType operateType = operateAnnotation.value();
OperateLog operateLog = new OperateLog();
Long id = DataUtils.getOperateLogList().size() == 0 ? 1L
: DataUtils.getOperateLogList().stream().map(OperateLog::getId).max(Long::compareTo).get() + 1;
operateLog.setId(id);
operateLog.setOperateCode(operateType.getOperateCode());
operateLog.setOperateType(operateType.getOperateDescr());
operateLog.setOperateSerno(UUID.randomUUID().toString().replaceAll("\\-",""));
operateLog.setCreateTime(LocalDateTime.now());
DataUtils.createOperateLog(operateLog);
System.out.println("代理对象完成日志的添加");
Object returnVal = method.invoke(targetObj, objects);
return returnVal;
}
}
在代理类中,主要用于获取目标方法上的操作注解,然后在目标对象方法执行之前将日志完成创建,这样一来,在执行业务方法时,代理对象都会对目标方法进行拓展和加强,所有的业务在不进行修改代码的情况都会进行日志添加操作。
值得一提的是,这个日志功能相对比较简单,之所以会写给大家是想让大家更深一步的理解到代理模式的设计魅力以及使用场景,在代理类中,可以尽情的展示反射操作,从而在不改变核心代码的基础上实现出更为强大的功能。
接下来呢,对这个案例进行测试。测试代码如下所示:
package com.ignorance.cglib.fact;
import com.ignorance.cglib.CglibProxyFactory;
import java.math.BigDecimal;
public class Client {
public static void main(String[] args) {
BusinessProxyFactory<Business> businessBusinessProxyFactory = new BusinessProxyFactory<>(new Business());
Business proxyInstance = (Business)businessBusinessProxyFactory.getProxyInstance();
Product product = new Product();
product.setProductId(1001);
product.setProductName("MacBook Pro");
product.setPrice(new BigDecimal("24999"));
proxyInstance.saveProduct(product);
Product product1 = new Product();
product1.setProductId(1002);
product1.setProductName("iphone14 Pro");
product1.setPrice(new BigDecimal("7999"));
proxyInstance.saveProduct(product1);
proxyInstance.deleteById(1001);
System.out.println("日志信息:");
DataUtils.getOperateLogList().forEach(System.out::println);
System.out.println("产品信息:");
DataUtils.getBussinessList().forEach(System.out::println);
}
}
运行结果如下图所示:
从上面的运行结果来看,逻辑是没有任何问题的,通过使用代理模式的设计思想对业务方法进行了动态的增强与拓展,让其在不改变原有代码的基础上无缝的融合了日志功能,并且类与类之间的耦合度也是相对比较低的,维护起来也遵从开闭原则。
2.2数据库事务案例
接触过数据库的同学们都应该知道,数据安全是一个软件必须需要保证的前提,在数据库中,提供了事务机制来确保数据的一致性。有个业务涉及到多个写操作,而且这几个单独的写操作又有着数据的强一致性。比如订单总表以及订单明细表的写操作,再比如支付成功用户余额的扣减、库存的更新以及订单状态修改等操作。整体的业务功能都是由各个小步骤组合而成的,而且每个步骤要求数据保证强一致性,不能出现一个步骤成功一个步骤失败的情况,要不全部失败回滚,要不全部成功提交。对于这些场景,就必须使用到事务机制来保证数据的强一致性。
下面呢,使用订单以及订单明细这一模型来完成接下来的讲解。
因为此案例涉及到数据库的操作,所以需要导入数据库的驱动包,maven坐标如下:
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
接下来呢,需要创建一系列跟数据库以及事务相关的类,因为这些不是要讲解的核心,所以就不用再花太多的时间去讲解这些类。
第一步:前置工作一,创建数据库配置文件jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql:///order_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=123123
第二步:前置工作二,创建数据库连接类ConnUtil
package com.ignorance.cglib.trans;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class ConnUtil {
private static String DRIVER = "";
private static String URL = "";
private static String USER = "";
private static String PWD = "";
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
static {
loadJDBCProperties();
}
public static Connection getconn() {
Connection conn = threadLocal.get();
if (conn == null) {
conn = createConn();
threadLocal.set(conn);
}
return threadLocal.get();
}
private static void loadJDBCProperties(){
try {
InputStream inputStream = ConnUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(inputStream);
DRIVER = properties.getProperty("jdbc.driver");
URL = properties.getProperty("jdbc.url");
USER = properties.getProperty("jdbc.username");
PWD = properties.getProperty("jdbc.password");
} catch (IOException e) {
e.printStackTrace();
}
}
private static Connection createConn(){
try {
Class.forName(DRIVER);
return DriverManager.getConnection(URL,USER,PWD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null ;
}
public static void close() throws SQLException {
Connection conn = threadLocal.get();
if (conn!=null){
if (!conn.isClosed()){
conn.close();
threadLocal.set(null);
}
}
}
}
第三步:前置工作三,创建数据库基础操作类BaseDao
package com.ignorance.cglib.trans;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public abstract class BaseDao<T> {
//定义一个实体类的Class变量
private Class<T> entityClass ;
public BaseDao() {
//this : 当前实例对象 , 不是BaseDao!!
//将来我们是执行如下代码:FruitDao fruitDao = new FruitDaoImpl();
//因此,this代表当前new出的实例,也就是FruitDaoImpl的实例
//getGenericSuperclass : 获取带有泛型的父类Class -> BaseDao<Fruit>
//Type是父接口;parameterizedType是子接口,翻译过来叫:参数化类型
Type genericSuperclass = this.getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType)genericSuperclass ;
try {
//获取实际传入的类型参数
// FruitDaoImpl extends BaseDao<Fruit> 那么此时参数化类型就是Fruit
// MemberDaoImpl extends BaseDao<Member> 那么此时参数化类型就是Member
// 返回的是数组,因为在定义的时候,语法上可以这么去定义:BaseDao<T, K , V , M > 表示我们可以定义多个泛型类型
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
//因为当前Base后面的尖括号中我们只定义了<Fruit>,表示只有一个类型,因此我们获取数组的第一个元素
Type type = actualTypeArguments[0];
//获取Fruit的全类名
String className = type.getTypeName();
//通过反射技术获取指定全类名对应的Class对象
entityClass = (Class<T>) Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.out.println("T类型没有指定!或者指定错误!");
}
}
protected Connection conn ;
protected PreparedStatement psmt ;
protected ResultSet rs ;
//执行更新操作
protected int executeUpdate(String sql , Object... params){
boolean insertFlag = sql.trim().toUpperCase().startsWith("INSERT");
try {
conn = ConnUtil.getconn() ;
if(!insertFlag) {
psmt = conn.prepareStatement(sql);
}else {
psmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
}
setParams(psmt,params);
int count = psmt.executeUpdate();
if(insertFlag){
rs = psmt.getGeneratedKeys();
if(rs.next()){
Long genId = rs.getLong(1);
return genId.intValue() ;
}
}else{
return count ;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
}
return 0 ;
}
//填充参数
private void setParams(PreparedStatement psmt , Object... params) throws SQLException {
if(params!=null && params.length>0){
for (int i = 0; i < params.length; i++) {
psmt.setObject(i+1,params[i]);
}
}
}
//执行查询,返回列表
protected List<T> executeQuery(String sql , Object... params){
List<T> list = new ArrayList<>();
try {
conn = ConnUtil.getconn();
psmt = conn.prepareStatement(sql);
setParams(psmt,params);
rs = psmt.executeQuery();
//结果集元数据:说明结果集结构的数据(例如,有多少列,列名叫什么,什么类型等等)
ResultSetMetaData rsmd = rs.getMetaData();
//获取结果集的列数
int colCount = rsmd.getColumnCount();
//6.解析结果集
while(rs.next()){
//通过反射技术创建出entityClass的一个实例对象(底层仍然是调用的无参构造方法)
T entity = entityClass.newInstance() ;
for(int i = 1; i<=colCount ; i++){
//获取当前列的列名 getColumnLabel获取当前列的别名
String columnName = rsmd.getColumnName(i);
//获取结果集中当前行的每一列数值
Object columnValue = rs.getObject(i);
//给entity实例的columnName对应的属性赋columnValue值
//通过反射技术获取指定名称的属性
Field field = entityClass.getDeclaredField(columnName);
//强制访问,即使是private,也可以访问
field.setAccessible(true);
//通过反射给entity的field属性赋columnValue值
field.set(entity , columnValue);
}
list.add(entity);
}
return list ;
} catch (SQLException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} finally {
}
return null ;
}
//执行查询,返回单个实体
protected T load(String sql , Object... params){
List<T> list = executeQuery(sql,params);
return list!=null && list.size()>0 ? list.get(0) : null ;
}
//执行复杂查询,返回数组
//select count(*) , avg(age) as avg_age from t_person -> 得到两个数据,返回一个数组
protected Object[] executeComplexQuery(String sql , Object... params){
conn = ConnUtil.getconn();
try {
psmt = conn.prepareStatement(sql);
setParams(psmt,params);
rs = psmt.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
Object[] values = new Object[rsmd.getColumnCount()];
if(rs.next()){
for(int i = 0 ; i<values.length;i++){
values[i] = rs.getObject(i+1);
}
return values ;
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
}
return null ;
}
}
第四步:前置工作四,创建事务管理类TransactionManager
package com.ignorance.cglib.trans;
import java.sql.SQLException;
public class TransactionManager {
//开启事务
public static void begin() throws SQLException {
ConnUtil.getconn().setAutoCommit(false);
}
//提交事务
public static void commit() throws SQLException {
ConnUtil.getconn().commit();
ConnUtil.close();
}
//回滚事务
public static void rollback() throws SQLException {
System.out.println(444444);
ConnUtil.getconn().rollback();
ConnUtil.close();
}
}
第五步:创建订单实体类
package com.ignorance.cglib.trans;
import java.math.BigDecimal;
import java.util.List;
public class Order {
private String orderId;
private BigDecimal balance;
private Integer count;
private List<OrderItem> orderItemList;
public Order(String orderId, BigDecimal balance, Integer count) {
this.orderId = orderId;
this.balance = balance;
this.count = count;
}
public String getOrderId() {
return orderId;
}
public List<OrderItem> getOrderItemList() {
return orderItemList;
}
public void setOrderItemList(List<OrderItem> orderItemList) {
this.orderItemList = orderItemList;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}
第六步:创建订单明细类
package com.ignorance.cglib.trans;
import java.math.BigDecimal;
public class OrderItem {
private String id;
private Long productId;
private String productName;
private BigDecimal balance;
private Integer count;
private String orderId;
public OrderItem(String id, Long productId, String productName, BigDecimal balance, Integer count, String orderId) {
this.id = id;
this.productId = productId;
this.productName = productName;
this.balance = balance;
this.count = count;
this.orderId = orderId;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
}
这些字段相信不用多跟大家解释都能秒懂,重点说下具体的业务逻辑,模拟的逻辑是用户提交订单需要同时向订单表以及订单明细表插入数据,订单表是当前交易的总计,订单明细表则是当前订单的详情。订单表与明细表的关系为一对多,在订单明细表中,我门设置了orderId为逻辑外键,用于关联订单总表的订单Id。
数据库表结构如下图所示:
订单总表:t_order:
订单明细表:t_order_item
第七步:创建OrderDao继承BaseDao,用于操作t_order表
package com.ignorance.cglib.trans;
public class OrderDao extends BaseDao<Order> {
}
第八步:创建OrderItemDao继承BaseDao,用于操作t_order_item表
package com.ignorance.cglib.trans;
public class OrderItemDao extends BaseDao<OrderItem> {
}
第九步:创建业务接口OrderService类,并且定义保存订单方法
package com.ignorance.cglib.trans;
public interface OrderService {
public void saveOrder(Order order);
}
第十步:创建业务层实现类OrderServiceImpl
package com.ignorance.cglib.trans;
public class OrderServiceImpl implements OrderService {
private OrderDao orderDao = new OrderDao();
private OrderItemDao orderItemDao = new OrderItemDao();
@Override
public void saveOrder(Order order) {
String sql = "insert into t_order values (?,?,?)";
orderDao.executeUpdate(sql,order.getOrderId(),order.getBalance(),order.getCount());
order.getOrderItemList().forEach(o -> {
String sql1 = "insert into t_order_item values(?,?,?,?,?,?)";
orderItemDao.executeUpdate(sql1,o.getId(),o.getProductId(),o.getProductName(),o.getBalance(),o.getCount(),o.getOrderId());
});
}
}
最后呢,创建测试类模拟客户下单的过程:
package com.ignorance.cglib.trans;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class Client {
private static OrderService orderService = new OrderServiceImpl();
public static void main(String[] args) {
String orderId = UUID.randomUUID().toString().replaceAll("\\-","");
Order order = new Order(orderId,new BigDecimal("22000"),5);
List<OrderItem> orderItemList = new ArrayList<>();
String itemId1 = UUID.randomUUID().toString().replaceAll("\\-","");
OrderItem orderItem1 = new OrderItem(itemId1,1001L,"iphone14",new BigDecimal("5000"),2,orderId);
String itemId2 = UUID.randomUUID().toString().replaceAll("\\-","");
OrderItem orderItem2 = new OrderItem(itemId2,1002L,"小米13",new BigDecimal("4000"),3,orderId);
orderItemList.add(orderItem1);
orderItemList.add(orderItem2);
order.setOrderItemList(orderItemList);
orderService.saveOrder(order);
}
}
所谓下订单,其实数据库操作就是向订单表写入一条合计数据,然后再把详细数据插入到订单明细表,执行代码后数据库变化如下图所示:
t_order表数据变化:
t_order_item表数据变化:
此时通过数据库的变化,发现完全符合业务逻辑,执行代码后和预期结果一致。但是呢,这是在代码没有任何异常的情况下,知道订单表和订单明细表虽然是两个独立的表,但是在保存订单的过程,两者是不可分割的,要不同时成功要么同时失败,绝对不允许出现一个步骤成功另外一个步骤失败的结果。比如此时在第一个步骤执行成功后,然后手动产生一个异常。如下所示:
public void saveOrder(Order order) {
String sql = "insert into t_order values (?,?,?)";
orderDao.executeUpdate(sql,order.getOrderId(),order.getBalance(),order.getCount());
int a = 10 / 0;
order.getOrderItemList().forEach(o -> {
String sql1 = "insert into t_order_item values(?,?,?,?,?,?)";
orderItemDao.executeUpdate(sql1,o.getId(),o.getProductId(),o.getProductName(),o.getBalance(),o.getCount(),o.getOrderId());
});
}
在第4行代码中,人为制造了一个算数异常,此时重置数据库,重新运行一下代码,结果如下:
此时程序的运行结果我相信大家都不会意外,主要观测一下数据库的数据变化情况:
对订单表t_order来说,它的执行是在异常之前,所以它成功的完成了数据插入肯定是没什么问题的。
接下来再观察一下t_order_item表,通过代码可以发现,明细表的逻辑是在异常之后,所以它没有机会执行,通过数据库来论证结论。
可以看出此时明细表没有一条记录,但是呢订单表却插入成功了,这肯定是有极大的安全问题的,前面说订单表和明细表在下单时应该保持强一致性,此时却出现了一个成功一个失败的情况,这显然是绝对不允许的。所以呢,需要引入事务来解决这个问题,将这两个步骤看作一个不可分割的整体,要么同时成功要么同时失败,接下来使用事务来修改代码:
public void saveOrder(Order order) {
try {
TransactionManager.begin();
String sql = "insert into t_order values (?,?,?)";
orderDao.executeUpdate(sql,order.getOrderId(),order.getBalance(),order.getCount());
int a = 10 / 0;
order.getOrderItemList().forEach(o -> {
String sql1 = "insert into t_order_item values(?,?,?,?,?,?)";
orderItemDao.executeUpdate(sql1,o.getId(),o.getProductId(),o.getProductName(),o.getBalance(),o.getCount(),o.getOrderId());
});
TransactionManager.commit();
}catch (Exception e){
try {
TransactionManager.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
在代码的开始使用try...catch异常机制加上事务机制来完善安全隐患,在第一行开启事务,如果所有代码或者说一个事务中的所有子步骤都没有异常,则正常提交,否则则回滚事务。我相信这些基本操作大家都很熟悉,所以也就不一一赘述了。
接下来重新执行程序,并重置数据库数据,结果如下:
对订单表t_order来说,数据变化如下:
对订单明细表t_order_item来说,数据变化如下;
可以看出加了事务以后,这个时候我订单表以及订单明细表的写操作已经成为了一个无法分割的整体,它们在数据上保持着强一致性,要不全部成功,要不全部失败,安全性问题得以解决。
而这还没开始重点,这个案例的目的是引用代理模式,前面的一堆问题都是为了代理模式做准备的,那么事务跟代理模式有什么联系呢?其实一点关系都没有,只是在程序开发中,并不仅仅只有这个业务会用到事务,在一个程序应用中,存在着大量使用事务的情况。而像这样直接硬编码将事务的代码硬编码在业务类中显然是很不明智的,一是代码耦合性太严重了,其次是每个业务都要写事务开启、提交以及回滚的代码,这显然是不合适的。
都说事务跟代理模式无关,只是加事务这个步骤正好完美的符合了代理模式的思想,还是拿订单案例来讲,核心代码应该就是订单表以及明细表的添加逻辑,而完成这个功能又不得不添加非逻辑性代码,也就是事务机制,不然程序是有很大安全问题的。
所以添加事务的这个步骤可以使用代理模式的思想进行改造,可以根据业务场景构建出代理模式的模型,即将业务类OrderServiceImp看作目标类,将事务添加看作代理类,需要对业务类动态拓展事务的功能。因为业务类实现了对应的接口,所以可以考虑使用JDK动态代理的方法改造程序。
1.创建TransactionProxyFactory类,用于帮助构建事务代理类:
package com.ignorance.cglib.trans;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class TransactionProxyFactory<T> {
//目标对象
private T targetObj;
//调用构造器时对目标对象进行初始化
public TransactionProxyFactory(T targetObj) {
this.targetObj = targetObj;
}
//返回targetObj目标对象的一个代理对象
public Object getProxyInstance() {
//目标对象所对应的类加载器
ClassLoader classLoader = targetObj.getClass().getClassLoader();
//目标对象大所对应的接口
Class<?>[] interfaces = targetObj.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
TransactionManager.begin();
Object returObj = method.invoke(targetObj, args);
TransactionManager.commit();
}catch (Exception e){
TransactionManager.rollback();
}
return null;
}
});
}
}
2.删除目标方法上的事务代码
public void saveOrder(Order order){
String sql = "insert into t_order values (?,?,?)";
orderDao.executeUpdate(sql,order.getOrderId(),order.getBalance(),order.getCount());
int a = 10 / 0;
order.getOrderItemList().forEach(o -> {
String sql1 = "insert into t_order_item values(?,?,?,?,?,?)";
orderItemDao.executeUpdate(sql1,o.getId(),o.getProductId(),o.getProductName(),o.getBalance(),o.getCount(),o.getOrderId());
});
}
3.进行测试:
package com.ignorance.cglib.trans;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class Client {
public static void main(String[] args) throws SQLException {
TransactionProxyFactory<OrderService> transactionProxyFactory = new TransactionProxyFactory<>(new OrderServiceImpl());
OrderService proxyInstance = (OrderService)transactionProxyFactory.getProxyInstance();
String orderId = UUID.randomUUID().toString().replaceAll("\\-","");
Order order = new Order(orderId,new BigDecimal("22000"),5);
List<OrderItem> orderItemList = new ArrayList<>();
String itemId1 = UUID.randomUUID().toString().replaceAll("\\-","");
OrderItem orderItem1 = new OrderItem(itemId1,1001L,"iphone14",new BigDecimal("5000"),2,orderId);
String itemId2 = UUID.randomUUID().toString().replaceAll("\\-","");
OrderItem orderItem2 = new OrderItem(itemId2,1002L,"小米13",new BigDecimal("4000"),3,orderId);
orderItemList.add(orderItem1);
orderItemList.add(orderItem2);
order.setOrderItemList(orderItemList);
proxyInstance.saveOrder(order);
}
}
至于运行结果,同样和上面一样。会发现这种写法比直接耦合在业务类中优雅太多了,一是业务类只需要专注业务,不需要编写业务无关的代码,对来说还是提高了很多效率的。其次是解耦,让我项目维护起来更加轻松松容易。
总结
在本篇文章中,我们讲解了一种全新的设计模式,也就是代理模式。不管是在面试还是在开发中,代理模式都毋庸置疑是一种极其重要的设计模式,所以针对代理模式花了很长的篇幅进行讲解。
在文章刚开始呢,通过一些生活的案例引入了代理模式,并对代理模式的三种思想进行了详细的讲解。即静态代理、JDK动态代理以及通过继承机制实现的Cglib代理,大家一定要重点去理解和掌握,不管是面试,还是提高我们的设计思想都是极其重要有意义的。
在最后呢,讲解了两种涉及到使用代理模式的案例,第一种是日志问题,第二种则是事务机制。之所以会给大家举这个例子,一是这两种场景大家比较常见,其次就是向通过案例的方式让大家进一步的领悟代理模式的魅力。学习嘛,肯定要学会举一反三的能力,这样我们的技术才会更上一个台阶!