学习代理模式内容:

★ 静态代理、

★ 动态代理(JDK动态代理、CGLIB动态代理)、

★ 拦截器的原理和日志记录

★ 代理总结


一、职责分离的例子---房屋租赁

1、重复

2、职责不分离

●【​​陪着看房、陪着谈价格、交钥匙​​​】----不应该交个房东来重复做,不是他关心的重点,作为房东他只需要关心【​​签合同、收房租​​】

----解决:把这部分重复非业务重点的代码重构抽离给​​第三者​​----中介


● 抽离之后,中介也会重复了呀,那怎么办呢?

----没事,这是人家的责任,人家就是靠这个赚钱的。


✿ ​​客户端Client----- 第三方(目的:增强Service的功能)-----服务端Service​


二、代理模式 [设计模式] ​​中介作用​​

✿ 1、代理模式:客户端直接使用的都是​​代理对象​​​,并不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到​​中介作用​​。


(1)代理对象完全包含真实对象,客户端使用的都是代理对象的方法,和真实对象没有直接关系;

(2)代理模式的职责:把不是真实对象该做的事情从真实对象上撇开——职责清晰


三、静态代理

1、概念/原理:

在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。


2、静态代理的实现过程:

■ 将功能封装成一个接口,​​代理类和真实类/委托类 都需要实现该接口​​:

真实类/委托类 需要实现该接口,很好理解,才具有特定的功能。
代理类 需要实现该接口,是因为这样子才知道需要为哪些功能做代理、做增强。

设计模式~代理模式_动态代理


3、静态代理的优缺点:

● 优点:职责分离、安全

1,业务类只需要关注业务逻辑本身,保证了业务类的重用性。

2,把真实对象隐藏起来了,保护真实对象。


● 缺点:代码臃肿(一个真实对象需要对应一个代理对象)、不符合开闭原则(不方便扩展和维护)。

----​一个代理类只能服务某一个业务接口​​。

1,代理对象的某个接口只服务于某一种类型的对象也就是说每一个真实对象都得创建一个代理对象

2,如果需要代理的方法很多,则要为每一种方法都进行代理处理。

3,如果接口增加一个方法,除了所有实现类需要实现这个方法外,代理类也需要实现此方法


4、总结静态代理:

1、静态代理需要实现某个接口(才知道要代理、增强什么功能)

2、静态代理要包含真实对象(真实对象是静态代理类的对象属性)----内部bean,不暴露给外界

3、测试,通过接口对象进行测试,直接调用的是静态代理对象(间接调用真实对象)

动态代理在测试时,是通过动态代理类,先获取到代理对象,直接调用的是动态代理对象(间接调用真实对象)】

  • 获取到代理对象:jdk提供的Proxy的方法newProxyInstance


● 详细的代码:

/* 静态代理类【需要实现接口,才知道需要为哪些功能做代理、做增强】*/
public class EmployeeServiceProxy implements IEmployeService{
@Setter
private IEmployeService target;//真实对象/委托对象
@Setter
private TransactionManager txManager;//事务管理器

@Override
public void save(Employee e) {
txManager.open();
try {
target.save(e);
txManager.commit();
} catch (Exception e2) {
txManager.rollback();
e2.printStackTrace();
}
}

@Override
public void update(Employee e) {
txManager.open();
try {
target.update(e);
txManager.commit();
} catch (Exception e2) {
txManager.rollback();
e2.printStackTrace();
}
}
}


/* 测试类 (先获取代理对象)*/
@SpringJUnitConfig
public class App {

@Autowired
private IEmployeService service;

@Test
void testSave() throws Exception {
Employee e = new Employee();
e.setName("shangke");
e.setAge(28);

service.save(e);//调用接口对象【根据bean配置,实际调用的是静态代理对象】
// System.out.println(service);//接口对象的真实类型【根据bean配置,实际调用的是静态代理对象】
// System.out.println(service.getClass());
}
}

● 静态代理bean对象的配置:

设计模式~代理模式_静态代理_02


四、动态代理

1、学习动态动态代理之前的准备工作:字节码的动态加载

(1) 先了解一下java的编译运行原理【java加载字节码原理】:

  • 编译:将源文件 .java 文件,通过编译器(javac 命令) 编译成 字节码文件 .class 文件。【​​编译得到字节码文件​​】
  • 运行:通过​​类加载器​​(以二进制流形式)把字节码加载进JVM,通过java解析器(java 命令) 进行运行程序。【​​jvm解析字节码文件​​】


(2) 如何动态创建一份字节码?(实现了在代码中动态创建一个类的能力):

  • 通过java的编译和运行原理,可以看到:在运行时期,是jvm通过字节码的二进制信息来加载类的

所以,当我们在运行时期,通过java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类


2、静态代理和动态代理的(原理)区别:

■ 静态代理:(经历了​​编译​​和运行)

​在程序运行前就已经存在代理类的字节码文件​​​(因为通过了编译阶段),​​代理对象和真实对象的关系在运行前就确定了​​(因为通过了编译阶段)。

■ 动态代理:(​​只经历了运行​​,咱通过某种手段得到的字节码【遵循字节码格式和结构】)

动态代理类是在程序运行期间由jvm通过反射等机制动态生成的,所以​​不存在代理类的字节码文件​​​(因为没有经历编译阶段),​​代理对象和真实对象的关系是在程序运行期间才确定的​​。


□ 两个原理相同点:

客户端直接使用的都是​​代理对象​​​,并不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到​​中介作用​​。


3、动态代理分类:JDK动态代理、CGLIB动态代理

(1)JDK动态代理:

① 使用JDK的 ​​Proxy的newProxyInstance​​ 创建动态代理对象
//动态代理---获取代理对象
@SuppressWarnings("unchecked")
public <T> T getProxyObject() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真实对象的类加载器
target.getClass().getInterfaces(), //真实对象实现的接口
this);//如何做事务增强的对象【增强器】
} //动态代理---获取代理对象
@SuppressWarnings("unchecked")
public <T> T getProxyObject() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真实对象的类加载器
target.getClass().getInterfaces(), //真实对象实现的接口
this);//如何做事务增强的对象【增强器】
}

● 详细的代码:

/* 动态代理:事务的增强操作 */
public class TransactionMangagerAdvice implements InvocationHandler{
@Setter
private Object target;//真实对象/委托对象
@Setter
private TransactionManager txManager;//事务增强器

//动态代理---获取代理对象
@SuppressWarnings("unchecked")
public <T> T getProxyObject() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真实对象的类加载器
target.getClass().getInterfaces(), //真实对象实现的接口
this);//如何做事务增强的对象【增强器】
}


//如何为真实对象的方法做增强具体操作
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = null;
txManager.open();
try {
//======================================================
ret = method.invoke(target, args);//调用真实对象的方法
//======================================================
txManager.commit();
} catch (Exception e) {
e.printStackTrace();
txManager.rollback();
}
return ret;
}
}


/* 测试类 (先获取代理对象)*/
@SpringJUnitConfig
public class App {

@Autowired
private TransactionMangagerAdvice advice;

@Test
void testSave() throws Exception {
Employee e = new Employee();
e.setName("shang");
e.setAge(10);
e.setId(2L);

//获取代理对象
IEmployeService proxy = advice.getProxyObject();
//调用代理对象的保存操作
proxy.save(e);

// System.out.println(proxy);//TransactionMangagerAdvice对象的真实类型是代理对象
// System.out.println(proxy.getClass());//对象的真实类型
}
}


☺(2) 动态代理原理:

  • 原理和静态代理差不多【客户端直接使用的都是​​代理对象​​,并不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到​​中介作用​通过代理对象间接的调用真实对象的方法
  • 只不过,​​动态代理的代理类​​,不是由我们所创建,是我们生成字节码对应格式和结构的二进制数据加载进虚拟机,​​动态生成的​​。

● 详细的代码[通过 DynamicProxyClassGenerator 生成动态代理的字节码,再通过反编译工具查看。]:

public class DynamicProxyClassGenerator {
public static void main(String[] args) throws Exception {
generateClassFile(EmployeeServiceImpl.class, "EmployeeServiceProxy");
}

public static void generateClassFile(Class targetClass, String proxyName)throws Exception {
//根据类信息和提供的代理类名称,生成字节码
byte[] classFile =
ProxyGenerator.generateProxyClass(proxyName, targetClass.getInterfaces());
String path = targetClass.getResource(".").getPath();
System.out.println(path);
FileOutputStream out = null;
//保留到硬盘中
out = new FileOutputStream(path + proxyName + ".class");
out.write(classFile);
out.close();
}
}


(3) CGLIB动态代理:

  • 第三方,需要拷贝jar包【spring-frame框架已经集成了cglib动态代理】

原理:继承


设计模式~代理模式_动态代理_03

● 详细的代码:

/* cglib动态代理:事务的增强操作 [和jdk的区别在创建代理对象方式上] */
//动态代理---获取代理对象
@SuppressWarnings("unchecked")
public <T> T getProxyObject() {
Enhancer enhancer = new Enhancer();
return (T)enhancer.create(target.getClass(), this);//创建代理对象
//enhancer.setSuperclass(target.getClass());//将继承哪一个类,去做增强
//enhancer.setCallback(this);//设置增强对象【增强器】
//return (T)enhancer.create();//创建代理对象
}


(4)JDK动态代理和CGLIB动态代理的区别:

■ JDK动态代理:​​要求 真实对象 必须要实现接口​​。
■ CGLIB动态代理:可以针对没有接口.

设计模式~代理模式_动态代理_04


☺ 五、代理的总结:

1、代理原理图:

设计模式~代理模式_设计模式~代理模式_05


☺ 2、​​代理的目标/作用:为了给目标对象(真实对象)的方法做功能的增强​​。


☺ 3、动态代理原理:

  • 原理和静态代理差不多【客户端直接使用的都是​​代理对象​​,并不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到​​中介作用​通过代理对象间接的调用真实对象的方法
  • 只不过,​​动态代理的代理类​​,不是由我们所创建,是我们生成字节码对应格式和结构的二进制数据加载进虚拟机,​​动态生成的​​。


4、JDK 动态代理 和 CGLIB 动态代理的总结:​​有接口-使用jdk,没有接口-使用cglib​

(1) JDK 动态代理:

① JAVA 动态代理是使用 ​​java.lang.reflect 包​​​中的 ​​Proxy 类与 InvocationHandler 接口​​这两个来完成的。

② 要使用 JDK 动态代理,​​委托类(真实类)必须要定义接口​​。

③ ​​JDK 动态代理将会拦截所有 pubic 的方法​​(因为只能调用接口中定义的方法),这样即使在接口中增加 了新的方法,不用修改代码也会被拦截。

④ 动态代理的最小单位是类(​​所有类中的方法都会被处理​​​),如果只想拦截一部分方法,可以在 invoke 方法 中对要执行的方法名​​进行判断​​ [判断内容可以放到配置文件,方便后续修改和维护~]


(2) CGLIB 动态代理:

​CGLIB 可以生成委托类的子类,并重写父类非 final 修饰符的方法​

② 要求类不能是 final 的,要拦截的方法要是非 final、非 static、非 private 的。

③ 动态代理的最小单位是类(所有类中的方法都会被处理);


5、性能和选择 [​​有接口-使用jdk,没有接口-使用cglib​​ + 性能要求有要求-Javassit]

  • JDK 动态代理是基于实现接口的,CGLIB 和 Javassit 是基于继承委托类的。
  • 从性能上考虑:Javassit > CGLIB > JDK Struts2 的拦截器和 Hibernate 延迟加载对象,采用的是 Javassit 的方式.
  • 对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,也更​​符合面向接口编程规范​​。
  • 若委托对象实现了干接口,优先选用 JDK 动态代理。 若委托对象没有实现任何接口,使用 Javassit 和 CGLIB 动态代


☺ 6、动态代理的应用:过滤器、拦截器、日志记录

1、过滤器Filter

2、拦截器Interceptor

  • 过滤器和拦截器差不多,只是过滤器是针对与web领域的概念,只能针对与请求和响应做增强,离不开servlet-api.jar;而拦截器是对于整个java领域的概念,不仅可以应用到web层,还可以应用到service层。

3、日志记录log



作者:一乐乐​