JSK动态代理
内容导航
Javaweb — 深化一下反射,之后会再看一下线程和并发
java设计模式 — 代理模式: 动态代理jdk实现
JQuery虽然被新的技术取代了,但是通过实例就发现相比原生的JS,JQuery简化了很多;接下来再来看看动态代理— 其实就是之前的反射机制,J2EE中使用过多次了,这里正式来康康
动态代理 — 基于反射机制的;之前使用过多次反射,尤其是几个工具类,第一个就是DBUtil使用了简单的forname方式进行类加载;之后再JSONUtil中使用了Class和Field;和方法一样设置可以access变可获取私有的属性,正常的情况下是获取不到的
代理模式 proxy pattern
proxy
代理就比如中介一样,帮助去实现某些操作,也就是允许客户端通过这个服务与另外一个end system进行非直接的服务,所以说路由器等就相当于是一个代理
从中介来分析: 比如房产中介,中介和代理所做的事情都是一致的,那就是招揽客人; 中介是房源的代理,房源是目标;这个过程就是 【客户 -----> 中介 ------> 房源】,中介是代理,会收费的;尽管如此,还是有很多中介存在;因为中介是专业的、非常的方便;其次就是客户不能独自找到房源,或者找房源十分困难
在实际开发中,也就有了代理的情况,也就是对客户----代理 ---- 目标
链式关系的抽象,比如有一个类A,还有一个类C,如果A类的功能需要调用C类,但是C类不允许A调用,所以这个时候可以找一个代理类B,B访问C;A再访问B即可,完成功能 【比如项目的短信验证码的功能,项目是不可能直接发送短信的,短信只有电信运营商才能发送,所以找一个中介关联公司 项目 ------------- 关联公司、中介 ------ 中国移动、电信、联通】
proxy pattern
为其他对象提供一种代理可以控制对这个对象的访问,在某些情况下,一个对象不舍和或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。使用代理模式,是为了在不修改目标对象的基础上,增强业务逻辑,客户类对目标对象的访问时通过访问代理对象来实现
proxy role
上面介绍了代理模式,那么其作用是什么呢?
- 功能增强、易于扩展 --------- 原来比如new Dao(),那么如果使用代理类来执行这个操作,在代理类中所写的其他的代码就是属于功能增强;但是这并没有修改原来的代码【符合开闭原则】
- 控制访问,保护对象 ------ 目标类不让客户类直接访问,这样可以对目标类起到保护的作用
- 使职责更加明确 ----- 比如在service中应该全部都是业务逻辑,而创建对象不属于业务逻辑,使用代理可以明确分工
代理模式分为静态代理和动态代理,二者的不同?
静态代理 static proxy
- 代理类是自己手工实现,自己创建一个java类,来表示代理类
- 代理的目标类是确定的
- 实现很简单,容易理解
这里来做一个例子来模拟一下代理,比如现在抽象一个关系链条 : 用户到商家位置买雪糕; 商家的雪糕从厂家进的;现在用户想要购买雪糕,实现功能【不能直接从厂家拿雪糕】,那么实现这个功能: 首先需要创建一个接口,定义卖雪糕的方法,展示商家和厂家的做的事情;其次创建厂家和商家类都要实现接口,最后创建客户类,调用商家的方法买一个雪糕
通过代理模式才能进行功能增强,也就是扩展功能;对扩展开发,对修改关闭
package cfeng.proxy;
//表示功能,厂家和商家都要实现这个方法
public interface IcecreamSell {
//定义方法,售卖雪糕param表示单价
float sell(int amount);
//可以定义其他的方法
}
这里就是接口,商家和厂家都会实现这个接口
package cfeng.proxy;
public class CfengFactory implements IcecreamSell {
//厂家不支持用户的单独购买
@Override
public float sell(int amount) {
//简单return即可
return 85.0f;
}
}
厂家继承了接口,对接口进行了继承,完成了雪糕的销售
package cfeng.proxy;
public class Merchant implements IcecreamSell {
//声明商家所代理的生产雪糕厂家
private IcecreamSell factory = new CfengFactory();
@Override
public float sell(int amount) {
//向厂家调用订单,告诉厂家,让其发货
float pri = factory.sell(amount);
//商家作为中间商需要加价
pri += 25; //这部分代码就是属于功能增强,因为原来只会有上面的代码,这里都是额外的,易于扩展
//返回给用户价格
return pri;
}
}
商家也实现了接口,这里商家完成了两件事情,第一件事情就是调用厂家的方法,第二件事就是进行加价;加价这部分【除了原来的方法的调用之外】,都是属于功能的增强的部分
package cfeng.proxy;
public class Client {
//从商家购买雪糕;不能直接从
public static void main(String[] args) {
Merchant mercha = new Merchant();
System.out.println(mercha.sell(1));
}
}
最后就是客户类,客户通过调用商家【代理】的【增强之后的】方法,完成了业务;这里的代理可以有多个,因为可以有多个商家,它们所做的处理不同;所以不同的代理可以完成不同的扩展
因此: 代理的最主要的功能就是 : 目标类的方法的调用【代理是没有直接的目标的功能的】, 功能的增强
静态代理的缺点
-
静态代理的目标固定,所以如果有很多目标,那就需要很多个代理,这样代码冗杂了
-
当接口中功能增加了,或者修改了,因为众多的代理实现类,那么修改的工作量大,这样就容易出现问题
dynamic proxy 动态代理
在程序的执行过程中,创建代理对象,动态指定代理目标类;动态代理就是一种创建对象能力,不用创建类,就可以获得对象 ------ 就是反射机制,获得类的字节码,再invoke即可; 动态代理是指代理类对象在程序运行时有JVM根据根舍机制动态生成的,动态代理不需要定义代理类的java源文件,动态获取类,(比如通过对象,通过完整的类名) 动态创建class字节码加载到JVM
动态代理的方式:
- JDK 动态代理: 使用java反射包中的类和接口实现动态代理的功能 反射包java.lang.reflect : 其中的三个类Method,InvocationHandler、Proxy
- cglib动态代理 : cglib时第三方工具库,创建代理对象,cglib的原理就是继承,通过继承目标类,创建其子类,在子类中重写超类的方法,实现功能的修改 — 所以父类不能是final的;cglib在很多框架中使用【比如mybatis、springAOP就会使用
反射 – Method
之前分享反射的时候着重分享了几个类,首先就是Class类,承载的是类的字节码;之后还有Method类表示的是类中的方法【公私均可】,Field类表示的是类的属性【公私均可】; 但是私有的一定要设置可访问,setAccess;这是之前最经常使用的
这里可以用反射的方式实现之前的客户类
public class Client {
//从商家购买雪糕;不能直接从
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
/*
Merchant mercha = new Merchant();
System.out.println(mercha.sell(1));
*/
//这里不通过创建对象的方式,如何得到上面的结果?反射即可 --- 使用Method
//获取类的字节码
try {
Class cls = Class.forName("cfeng.proxy.Merchant");
//通过字节码创建一个对象;通过构造方法创建
Object obj = cls.getDeclaredConstructor().newInstance();
//通过类的字节码获取到所有的方法
Method method = cls.getDeclaredMethod("sell",int.class);
//执行该方法通过method的invoke方法;第一个参数是对象,第二个是方法执行时的参数值 方法执行后的返回值
Object o = method.invoke(obj, 1);//o就是函数的返回值
System.out.println(o);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
- 这里通过类的字节码创建对象,不能直接使用newInstance了,since version 9就过时了,需要先获得构造器,之后再创建对象;
- 获得类的方法可以通过Class的getDeclaredMethod方法,第一个参数时方法的名称,第二个参数是方法的参数的字节码;
- 执行方法通过Method的invoke方法,这个方法的第一个参数为该类的一个具体的对象,第二个参数是该方法传入的具体的参数的值;返回值为方法的返回值【invoke 调用】
动态代理步骤
动态代理的目标不是固定的,根据代理的对象,动态创建代理类,这样就避免了静态代理中的类过多的问题,动态代理的实现方式---- 反射; 可以直接使用java.lang.reflect.Proxy
; 动态代理的实现步骤如下
jdk动态代理要求目标对象必须要实现接口,没有接口就实现不了动态代理
- 编写一个委托类、目标类的接口【定义目标类要完成的功能】
- 实现一个真正的委托类【编写目标类】
- 创建InvocationHandler接口的实现类,重写invoke方法,完成代理类的功能—调用目标方法、增强功能
- 使用Proxy类的newProxyInstance()静态方法生成动态代理的对象 ,并将返回值转为接口类型
动态代理主要依托的reflect包中的三个类: invocationHandler、Method、Proxy
- invocationHandler【调用处理器】 接口 ------- 就是表明代理要做什么: 这是一个函数式接口,其中就一个方法invoke;invoke方法表示代理对象要执行的功能代码;代理类要完成的功能就卸载invoke代码中【目标方法的调用、功能增强】
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
Object proxy : 这是jdk创建的代理对象,无需赋值
Method method : 目标类中的方法,jdk提供method ---->这里的method代指的就是目标方法
Object[] args : 目标类中方法的参数 -----> 所以说如果有多个参数,就写成一个集合即可 --->目标方法的参数 {int.class,String.class,……}
-
Method类: 表示方法的,也就是通过Method可以执行某个目标类的方法,通过invoke方法执行目标的方法
-
Proxy类: 核心的对象,用于创建代理对象,之前创建对象都是new 类构造方法;现在可以直接使用Proxy类的方法,代替new的使用
静态方法 ---- newProxyInstance()方法
作用就是创建代理对象,等同于静态代理的new ……
这里可以看一下源码
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
ClassLoader loader : 类加载器,负责向内存中加载对象 --- 使用反射机制就可以获得对象的classLoader ----比如对于类String ---》String.class.getClassLoader() 也就是获得目标的类
Class<?>[] interfaces 接口,目标对象实现的接口,也是通过反射获取的
InvocationHandler h :也就是之前实现的InvocationHandler接口,完成代理类的功能
通过Proxy类的这个newProxyInstance方法就可以生成一个目标对象的代理对象
动态代理实例
这里就不另外编写例子了,就以刚刚静态代理的买雪糕的例子 ---- 用户不能直接从从某一个具体的工厂买雪糕,那么就需要创建一个代理类商家来进行间接购买,同时商家增强了功能【抬价】 所以这里的核心应该是用户和目标;商家是为了处理这个业务应运而生的类型
按照动态代理的固定步骤,首先要规范目标类【服务类】的行为,面向接口编程,定义一个接口 ----- 定义接口的目的就是因为,客户类想使用的其实就是该接口定义的方法,所以真正要代理的是方法,而不是某一个具体实现类;多态的核心就是抽象,这样就便于扩展而少修改
package cfeng.proxy;
//表示功能,厂家和商家都要实现这个方法
public interface IcecreamSell {
//定义方法,售卖雪糕param表示单价
float sell(int amount);
//可以定义其他的方法
}
这个接口也就是为了完成客户类想要实现的功能;之后定义实现类来实现这个功能接口
package cfeng.proxy;
public class CfengFactory implements IcecreamSell {
//厂家不支持用户的单独购买
@Override
public float sell(int amount) {
//简单return即可
System.out.println("执行服务接口的具体实现类的实现方法");
return 85.0f;
}
}
本来按照静态代理,接下来就要手动去创建针对这个服务类的代理类;但是动态代理接下来就是创建InvocationHandler接口的实现类
这里的参数中,method就代表的是目标方法,args代表的就是目标方法的参数,所以为了执行方法,这里需要执行Method的invoke方法,需要传入目标类型的对象,这里就使用带有参数的构造方法实现传入目标对象
package cfeng.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 完成代理类要执行的功能,这里需要注意的是后两个对象就是目标参数,其实就是接口中的方法---代理方法而不是代理具体的实现类
* 通过构造器获得方法执行的具体的对象【多态】
*/
public class SellHandler implements InvocationHandler {// sell功能的处理器【代理功能】
private Object obj;
public SellHandler(Object obj) {
super();
this.obj = obj;
}
// 完成代理类执行的功能
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null; //目标方法的返回值
// 执行目标方法
result = method.invoke(obj, args);
//进行功能增强
if(result != null) {
float pri = (float)result;
pri += 25;
result = pri;
}
System.out.println("你获得2元优惠");
return result;
}
}
第四步就是利用Proxy类的静态方法生成代理对象;代理对象的目的也是执行接口中的sell方法,所以类型是接口的类型
package cfeng.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
//从商家购买雪糕;不能直接从
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//创建需要代理的目标对象
IcecreamSell fact = new CfengFactory();
//创建调用处理器对象
InvocationHandler handler = new SellHandler(fact);
//创建代理对象 ---- obj.getClass 类.class Class.forName()
IcecreamSell proxy = (IcecreamSell)Proxy.newProxyInstance(fact.getClass().getClassLoader(), fact.getClass().getInterfaces() , handler);
//通过代理对象执行方法
Object o = proxy.sell(1);
System.out.println(o);
}
}
单从这里来看,这里的动态代理就是类似于一种别样的重载,比如sell;给出的代理类还是有接口中的方法;JDK的动态代理必须有接口,Proxy的第二个参数就一定是接口
执行服务接口的具体实现类的实现方法 执行目标接口实现类的功能方法
你获得2元优惠 执行代理对象的功能增强代码
110.0 执行代理对象的功能增强代码
只能说动态代理和JDBC一样固化,非常明确的几个步骤
首先就是编写功能接口,【多态】实现接口;
之后就是实现invocationHandler接口来封装代理的功能 — 调用原方法、功能增强 ---- 最后就是使用Proxy的静态方法生成代理对象;这个代理对象就是最开始定义的接口类型的;它的目的还是有这个sell功能 ----- 代理可以在不改变原有的功能的前提下,增加新的功能; 代理类和接口实现类都是接口类型的🎄