1、重复
2、职责不分离
●【陪着看房、陪着谈价格、交钥匙
】----不应该交个房东来重复做,不是他关心的重点,作为房东他只需要关心【签合同、收房租
】
----解决:把这部分重复非业务重点的代码重构抽离给第三者
----中介
----没事,这是人家的责任,人家就是靠这个赚钱的。
客户端Client----- 第三方(目的:增强Service的功能)-----服务端Service
中介作用
代理对象
,并不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介作用
。(1)代理对象完全包含真实对象,客户端使用的都是代理对象的方法,和真实对象没有直接关系;
(2)代理模式的职责:把不是真实对象该做的事情从真实对象上撇开——职责清晰;
在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。
代理类和真实类/委托类 都需要实现该接口
:● 优点:职责分离、安全
1,业务类只需要关注业务逻辑本身,保证了业务类的重用性。
2,把真实对象隐藏起来了,保护真实对象。
● 缺点:代码臃肿(一个真实对象需要对应一个代理对象)、不符合开闭原则(不方便扩展和维护)。
----一个代理类只能服务某一个业务接口
。
1,代理对象的某个接口只服务于某一种类型的对象,也就是说每一个真实对象都得创建一个代理对象。
2,如果需要代理的方法很多,则要为每一种方法都进行代理处理。
3,如果接口增加一个方法,除了所有实现类需要实现这个方法外,代理类也需要实现此方法
1、静态代理需要实现某个接口(才知道要代理、增强什么功能)
2、静态代理要包含真实对象(真实对象是静态代理类的对象属性)----内部bean,不暴露给外界
3、测试,通过接口对象进行测试,直接调用的是静态代理对象(间接调用真实对象)
【动态代理在测试时,是通过动态代理类,先获取到代理对象,直接调用的是动态代理对象(间接调用真实对象)】
● 详细的代码:
/* 静态代理类【需要实现接口,才知道需要为哪些功能做代理、做增强】*/ 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对象的配置:
编译:将源文件 .java 文件,通过编译器(javac 命令) 编译成 字节码文件 .class 文件。【编译得到字节码文件
】
运行:通过类加载器
(以二进制流形式)把字节码加载进JVM,通过java解析器(java 命令) 进行运行程序。【jvm解析字节码文件
】
所以,当我们在运行时期,通过java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类。
编译
和运行)在程序运行前就已经存在代理类的字节码文件
(因为通过了编译阶段),代理对象和真实对象的关系在运行前就确定了
(因为通过了编译阶段)。只经历了运行
,咱通过某种手段得到的字节码【遵循字节码格式和结构】)不存在代理类的字节码文件
(因为没有经历编译阶段),代理对象和真实对象的关系是在程序运行期间才确定的
。代理对象
,并不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介作用
。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());//对象的真实类型 } }
原理和静态代理差不多【客户端直接使用的都是代理对象
,并不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介作用
。通过代理对象间接的调用真实对象的方法】
只不过,动态代理的代理类
,不是由我们所创建,是我们生成字节码对应格式和结构的二进制数据加载进虚拟机,动态生成的
。
● 详细的代码[通过 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(); } }
第三方,需要拷贝jar包【spring-frame框架已经集成了cglib动态代理】
● 详细的代码:
/* 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();//创建代理对象 }
要求 真实对象 必须要实现接口
。代理的目标/作用:为了给目标对象(真实对象)的方法做功能的增强
。原理和静态代理差不多【客户端直接使用的都是代理对象
,并不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介作用
。通过代理对象间接的调用真实对象的方法】
只不过,动态代理的代理类
,不是由我们所创建,是我们生成字节码对应格式和结构的二进制数据加载进虚拟机,动态生成的
。
有接口-使用jdk,没有接口-使用cglib
① JAVA 动态代理是使用 java.lang.reflect 包
中的 Proxy 类与 InvocationHandler 接口
这两个来完成的。
② 要使用 JDK 动态代理,委托类(真实类)必须要定义接口
。
③ JDK 动态代理将会拦截所有 pubic 的方法
(因为只能调用接口中定义的方法),这样即使在接口中增加 了新的方法,不用修改代码也会被拦截。
④ 动态代理的最小单位是类(所有类中的方法都会被处理
),如果只想拦截一部分方法,可以在 invoke 方法 中对要执行的方法名进行判断
[判断内容可以放到配置文件,方便后续修改和维护~]
① CGLIB 可以生成委托类的子类,并重写父类非 final 修饰符的方法
。
② 要求类不能是 final 的,要拦截的方法要是非 final、非 static、非 private 的。
③ 动态代理的最小单位是类(所有类中的方法都会被处理);
有接口-使用jdk,没有接口-使用cglib
+ 性能要求有要求-Javassit]JDK 动态代理是基于实现接口的,CGLIB 和 Javassit 是基于继承委托类的。
从性能上考虑:Javassit > CGLIB > JDK Struts2 的拦截器和 Hibernate 延迟加载对象,采用的是 Javassit 的方式.
对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,也更符合面向接口编程规范
。
若委托对象实现了干接口,优先选用 JDK 动态代理。 若委托对象没有实现任何接口,使用 Javassit 和 CGLIB 动态代