本章当中使用到的名词是:
1.目标类:实现了功能接口的实现类对象
2.代理类:作为调用类和目标类之间的桥接
3.调用类:需要调用目标类方法来完成值的获取
4.OCP原则:程序设计的一个考虑,类设计的时候尽量避免方法当中代码的二次修改,但是欢迎类设计者扩展一个类的功能(方法)
5.耦合度:一个类过于依赖于另一个类,就会产生耦合性
为什么要使用到代理?
我们在之前的学习当中,我们经常使用到一个类去调用另一个类的方法去获取到需要的值
例:1.创建了一个实现接口的目标类对象(它的主要作用是执行某些特定的功能)
//目标类实现接口
public class Test1 implements TestInterface{
@Override
public void printHello() {
System.out.println("Hello World!");
}
@Override
public int add(int a, int b) {
return a + b;
}
2.创建一个调用类对象,这个对象当中会调用目标类的方法去执行相应的功能
//当前是一个调用类
public class MainTest1 {
public static void main(String[] args) {
//创建两个类对象
Test1 test1 = new Test1();
//调用了对象去调用目标类对象的方法
test1.printHello();
int result = test1.add(10,20);
}
}
两个类之间的关系是 : 调用类对象 —— 执行 ——> 目标类对象的方法 ——获得值——>调用类
但是在后续的开发功能加入的时候,我们有可能会经常性地修改到目标类的代码,这样会导致我们
在实现每一个功能增强的时候经常性地改动目标类对象的方法
这样会造成一个问题:不符合OCP原则(对修改关闭,对扩展开放)
我们在类设计的时候要经常性地遇到需要改动的方法代码,但是如果在实际开发当中,一个项目
有很多的目标类,我们不能每一次都修改目标类对象的代码,动一发而牵动全身,我们除了要改
动当前类的对象,我们还需要改动其他类调用该对象的方法代码,这样会造成一个耦合度过高
一个调用类对象过分依赖于目标类对象,目标类对象的CRUD操作会影响到调用类对象的代码
这不是我们想要的结果,我们需要的是每一个类都是一个独立体。
需要解决的需求:调用类对象和目标类对象解耦合,实现目标类方法功能增强
1.解决耦合性过高的问题:在两个类之间再增加一个类,调用类对象调用新增的类
新增的类调用目标类对象,将获取到的值返回给新增的类,新增的类返回该调用类对象
实现了两个类之间非直接性关联的问题(这样的话就实现了两个类之间的解耦合)、
2.实现目标类方法功能增强的问题:在实际开发当中,我们会经常性地需要增强目标类方法的功能
实现,但是为了符合OCP原则,我们又不能直接性地改动目标类对象的方法,我们在解决耦合性
过高的新增类的基础之上对返回的值进行一个再次的修改再返回给调用类对象
这个时候解决两个问题所提出的概念叫做:代理模式(创建代理类对象作为目标类对象和调用类
对象的桥接)
代理模式
1.代理:两个类之间有一个类完成两个类之间的桥接功能,之间这一个类就是代理类
例子:现实生活当中,我们在买房子的时候,我们会习惯性地寻找中介来进行租房或购房操作
这个时候,消费者就类似于调用类,房东就是目标类,中介就是代理类
(消费者可以访问到中介,房东可以访问到中介,但是消费者没有权利直接访问到房东)
2.代理机制特性:1.目标类和调用类之间非直接性关联 2.功能增强 3.控制访问
(1)非直接性关联:中间有代理类对象
(2)功能增强:使用代理类对象实现功能增强
(3)控制访问:调用类对象不能访问到目标类对象,必须通过代理类对象
3.代理的分类:静态代理,动态代理
(1)静态代理:程序员手动编写一个代理类对象完成代理机制的功能。
(2)动态代理:使用JDK当中提供的java.util.reflect当中三个类:InvocationHandler,Method,Proxy
4.动态代理的分类:
(1)JDK动态代理:JDK使用反射机制创建代理类对象,JDK具有创建对象的能力(本章)
(2)CGLIB动态代理:利用面向对象三大特性继承的关系,完成代理操作
5.动态代理和静态代理的区别:
(1)创建代理类对象的操作者:静态代理:程序员自身 动态代理:JDK
(2)创建代理类对象的时机:静态代理:在编译阶段(源代码级别) 动态代理:运行阶段
(静态代理的代理类在源代码的时候有程序员手动编写,而动态代理是在程序运行到哪个代码
块动态判断是否需要代理类对象来完成代理)
代理模式代码实现:
1.静态代理代码实现:例:工厂,零售商,消费者为例
(1)创建功能接口,接口实现类(工厂)(没有跟调用者直接关联)
package com.amour.StaticProxy;
public interface FactoryInterface {
//批量购买操作
double buyGoods(int amount);
}
package com.amour.StaticProxy;
//实现工厂接口的实现类
public class FactoryClass implements FactoryInterface {
//工厂原材货物
private static int goods = 500;
//工厂单价
private static double price = 75;
/**
* /
* 购买货物行为
* @param amount 货物数量
* @return 获取货物的单价
*/
@Override
public double buyGoods(int amount) {
//满十个会优惠操作
if (amount > 10){
goods -= amount;
return price * 0.95;
}
else {
goods -= amount;
return price;
}
}
}
(2)创建静态代理类(零售商)(实现了功能增强和控制访问)
package com.amour.StaticProxy;
//工厂与消费者的静态代理类
public class StaticProxyFactory {
//创建工厂类对象
private FactoryInterface factory = new FactoryClass();
public double buyFoods(int amount){
//从工厂那边获取单价
double price = factory.buyGoods(100);
//实现功能增强操作,完成涨价操作
price += 20;
return price;
}
}
(3)创建目标类对象(消费者)
package com.amour.StaticProxy;
//调用类对象
public class Customer {
public static void main(String[] args) {
//从零售商那边获取到价格
StaticProxyFactory staticProxy = new StaticProxyFactory();
double currentPrice = staticProxy.buyFoods(10);
System.out.println("获取的价格为:"+currentPrice);
}
}
以上代码发现:调用类的main方法当中根本就没有直接出现过目标类对象
所以目标类对象的代码进行修改,只需要修改代理类对象就可以了,实现了解耦合
在使用的时候,我们可以使用静态代理的方式完成一些小型项目的代理操作(类比较少)
但是在大型项目当中我们不推荐使用静态代理完成对目标类的代理实现,推荐使用动态代理
2.JDK动态代理代码实现:以消费者,零售商,工厂为例
注意:这个时候我们需要使用到的是反射机制当中的InvocationHandler,Proxy,Method类
在使用之前,复习反射机制
(1)反射机制:具有操作字节码文件的能力
【1】java.lang.Class<T> :保留一个类的全部信息
【2】java.util.reflect.Constructor:保存了一个类当中的构造方法的所有信息
【3】java.util.reflect.Method:保存了一个类当中的方法的所有信息
【4】java.util.reflect.Field:保存了一个类当中的成员属性的所有信息
信息包括了:访问权限控制符,是否使用了final或static关键字,返回值类型,名称,参数类型
可以使用反射机制来完成类对象的实例,类方法的执行,类属性的调用等
但是在实际开发当中,我们很少使用到反射机制来执行方法和调用属性,原因在于反射机制不适用
于编写应用程序,也不适用于查找类的错误信息。因为我们在实际开发当中经常是通过运行,异常
栈堆信息来获取错误信息的
(2)JDK动态代理代码实现:
package com.amour.StaticProxy;
public interface FactoryInterface {
//批量购买操作
double buyGoods(int amount);
}
【1】创建目标类对象(工厂)
package com.amour.StaticProxy;
//实现工厂接口的实现类
public class FactoryClass implements FactoryInterface {
//工厂原材货物
private static int goods = 500;
//工厂单价
private static double price = 75;
/**
* /
* 购买货物行为
* @param amount 货物数量
* @return 获取货物的单价
*/
@Override
public double buyGoods(int amount) {
//满十个会优惠操作
if (amount > 10){
goods -= amount;
return price * 0.95;
}
else {
goods -= amount;
return price;
}
}
}
【2】创建动态代理对象(使用到的是InvocationHandler接口和method的invoke方法)
package com.amour.StaticProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//实现InvocationHandler接口的实现类
public class RetailerInvocationHandler implements InvocationHandler {
//创建目标类对象
/**
* 创建的目标对象的数据类型一定是Object的,不能是其他类型的
*/
private Object target;
//创建构造方法
public RetailerInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//使用反射机制执行方法
Object price = method.invoke(target,args);
//实现功能增强
if (price != null){
double currentPrice = (double) price;
currentPrice += 20;
price = currentPrice;
}
return price;
}
}
【3】创建代理类对象【一般这一步是交给系统执行的,但是为了体现我们使用的类】
package com.amour.StaticProxy;
import com.amour.dynamicProxy.MyInvoctionHandler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
//调用类对象
public class Customer {
public static void main(String[] args) {
//创建目标类对象,传入到代理对象的参数当中
FactoryInterface factory = new FactoryClass();
InvocationHandler handler = new RetailerInvocationHandler(factory);
//创建目标类对象的代理类对象
FactoryInterface proxy = (FactoryInterface) Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(),handler);
double price = proxy.buyGoods(10);
System.out.println("获得的价格是:"+price);
}
}
使用JDK动态代理的时候一定要对反射机制有一个初步的了解,我们经常使用动态代理创建对象
并不直接使用new关键字在一个类当中实例化对象。【这样的话可以防止写死对象,我们可以在
后续对对象的类型进行修改】
3.建议
(1)我们自己手动使用动态代理的知识点很少,一般动态代理都已经在Spring,SpringMVC
Mybatis,Tomcat服务器当中已经集成实现了
(2)我们需要做的只是了解动态代理的机制和实现原理,如果是将来就业方向是服务器端软件
的软件开发的话,就需要比较深入地了解动态代理的机制