学习代理模式内容:
★ 静态代理、
★ 动态代理(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对象的配置:
四、动态代理
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 getProxyObject() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真实对象的类加载器
target.getClass().getInterfaces(), //真实对象实现的接口
this);//如何做事务增强的对象【增强器】
} //动态代理---获取代理对象
@SuppressWarnings("unchecked")
public 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 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动态代理】
-
原理:继承
● 详细的代码:
/* cglib动态代理:事务的增强操作 [和jdk的区别在创建代理对象方式上] */
//动态代理---获取代理对象
@SuppressWarnings("unchecked")
public 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动态代理:可以针对没有接口.
☺ 五、代理的总结:
1、代理原理图:
☺ 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层。