JDK 动态代理(AOP)
概述
- 什么是动态代理
1、使用 jdk 的反射机制,创建对象的能力, 创建的是代理类的对象。 而不用你创建类文件。不用写 java 文件。
2、动态:在程序执行时,调用 jdk 提供的方法才能创建代理类的对象。
3、jdk 动态代理,必须有接口,目标类必须实现接口,没有接口时,需要使用 cglib 动态代理。 - 动态代理能做什么
1、可以在不改变原来目标方法功能的前提下,可以在代理中增强自己的功能代码。
2、背景示例。
比如:你所在的项目中,有一个功能是其他人(公司的其它部门,其它小组的人)写好的,你可以使用(但是只有 Class 文件):Demo.class。
Demo dm = new Demo();
dm.print();
当这个功能缺少时, 不能完全满足我项目的需要。我需要在 dm.print() 执行后,需要自己在增加代码。
这时就会用代理实现 dm.print() 调用时, 增加自己代码, 而不用去改原来的 Demo 文件。
(在 mybatis,spring 中也有应用)
现实中的代理
代购,中介,换 ip,商家等等。
例1
比如有一家美国的大学, 可以对全世界招生。 这时就会有留学中介(代理)。
留学中介(代理): 帮助这家美国的学校招生,中介是学校的代理, 中介是代替学校完成招生功能。
- 代理特点:
1、中介和代理他们要做的事情是一致的:招生。
2、中介是学校代理, 学校是目标。
3、家长---中介(学校介绍,办入学手续)---美国学校。
4、中介是代理,不能白干活,需要收取费用(功能增强)。
5、代理不让你访问到目标。 - 为什么要找中介 ?
1、中介是专业的, 方便
2、家长现在不能自己去找学校。 家长没有能力访问学校。 或者美国学校不接收个人来访。
3、买东西都是商家卖, 商家是某个商品的代理, 你个人买东西, 肯定不会让你接触到厂家的。
4、在开发中也会有这样的情况, 你有a类, 本来是调用c类的方法, 完成某个功能。 但是c不让a调用。
a -----不能调用 c的方法。
在a 和 c 直接 创建一个 b 代理, c让b访问。
a --访问b---访问c
例2
登录,注册有验证码,验证码是手机短信。
中国移动,联通,电信都能发短信。他们都有子公司,或者关联公司,他们面向社会提供短信的发送功能。
张三的项目需要发送短信功能,他去找子公司,或者关联公司,然后这些公司再去找中国移动,联通,电信。
使用代理模式的作用
1、功能增强:在你原有的功能上,增加了额外的功能。新增加的功能,叫做功能增强。
2、控制访问:代理类不让你访问目标,例如商家不让用户访问厂家。
实现代理的方式
分为静态代理、动态代理。
静态代理:
1、代理类是自己手工实现的,自己创建一个java类,表示代理类。
2、同时你所要代理的目标类是确定的。
特点
1、实现简单。
2、容易理解。
缺点
当项目中目标类和代理类很多时候:
1、当目标类增加了,代理类可能也需要成倍的增加。代理类数量过多。
2、当你的接口中功能增加了,或者修改了,会影响众多的实现类,厂家类,代理都需要修改。影响比较多。
模拟一个用户购买u盘的行为
- 背景
用户是客户端类
商家:代理,代理某个品牌的u盘。
厂家:目标类。
三者的关系: 用户(客户端)---商家(代理)---厂家(目标)
商家和厂家都是卖u盘的,他们完成的功能是一致的,都是卖u盘。 - 实现步骤:
1、创建一个接口,定义卖u盘的方法, 表示你的厂家和商家做的事情。
2、创建厂家类,实现1步骤的接口。
3、创建商家,就是代理,也需要实现1步骤中的接口。
4、创建客户端类,调用商家的方法买一个u盘。 - 代理类完成的功能:
1、目标类中方法的调用
2、功能增强
动态代理
1、在程序执行过程中,使用jdk的反射机制,创建代理类对象, 并动态的指定要代理目标类。换句话说: 动态代理是一种创建java对象的能力,让你不用创建TaoBao类,就能创建代理类对象。
2、在静态代理中目标类很多时候,可以使用动态代理,避免静态代理的缺点。
3、即使动态代理中目标类很多,但是代理类数量可以很少;当修改了接口中的方法时,不会影响代理类。
4、动态代理有cglib 动态代理和 jdk 动态代理。
- 动态代理的实现:
1、jdk 动态代理(理解):使用 java 反射包中的类和接口实现动态代理的功能。反射包java.lang.reflect
,里面有三个类:
InvocationHandler
Method
Proxy
cglib 动态代理(了解)
1、cglib 是第三方的工具库,用于创建代理对象。
2、cglib 的原理是继承, cglib 通过继承目标类,创建它的子类,在子类中重写父类中同名的方法, 实现功能的修改。
3、因为 cglib 是继承,重写方法,所以要求目标类不能是 final 的, 方法也不能是 final 的。
4、cglib 的要求目标类比较宽松, 只要能继承就可以了。
5、cglib 在很多的框架中使用,比如 mybatis,spring 框架中都有使用。
jdk 动态代理
InvocationHandler 接口(调用处理器)
就一个方法 invoke()。
invoke():表示代理对象要执行的功能代码。你的代理类要完成的功能就写在 invoke() 方法中。
- 代理类完成的功能:
1、调用目标方法,执行目标方法的功能
2、功能增强,在目标方法调用时,增加功能。 - 方法原型:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
参数:
Object proxy:jdk创建的代理对象,无需赋值。
Method method:目标类中的方法,jdk提供method对象的
Object[] args:目标类中方法的参数, jdk提供的。
InvocationHandler 接口:表示你的代理要干什么、怎么用
1、创建类实现接口 InvocationHandler
2、重写invoke()方法, 把原来静态代理中代理类要完成的功能,写在这。
Method类:表示方法的, 确切的说就是目标类中的方法。
作用:通过Method可以执行某个目标类的方法,Method.invoke();
method.invoke(目标对象,方法的参数)
Object ret = method.invoke(service2, "李四");
说明:
method.invoke()就是用来执行目标方法的
等同于静态代理中的:
//向厂家发送订单,告诉厂家,我买了u盘,厂家发货
float price = factory.sell(amount); //厂家的价格。
3)Proxy类:核心的对象,创建代理对象。之前创建对象都是 new 类的构造方法()
现在我们是使用Proxy类的方法,代替new的使用。
方法: 静态方法 newProxyInstance()
作用是: 创建代理对象, 等同于静态代理中的TaoBao taoBao = new TaoBao();
参数:
1、ClassLoader loader 类加载器,负责向内存中加载对象的。 使用反射获取对象的ClassLoader类a,
a.getCalss().getClassLoader(),目标对象的类加载器。
2、Class[] interfaces:接口,目标对象实现的接口,也是反射获取的。
3、InvocationHandler h:我们自己写的,代理类要完成的功能。
返回值:就是代理对象
public static Object newProxyInstance(ClassLoader loader,
Class[] interfaces,
InvocationHandler h)
实现动态代理的步骤:
1、创建接口,定义目标类要完成的功能.
2、创建目标类实现接口。
3、创建 InvocationHandler 接口的实现类,在 invoke 方法中完成代理类的功能。
(1)、调用目标方法;
(2)、增强功能;
3、使用 Proxy 类的静态方法,创建代理对象。 并把返回值转为接口类型。
代码
UsbSell.java:
package com.proxy.sevice;
public interface UsbSell {
float sell(int amount);
}
UsbKingFactory.java:
package com.proxy.factory;
import com.proxy.sevice.UsbSell;
/**
* 目标类
*/
public class UsbKingFactory implements UsbSell {
@Override
public float sell(int amount) {
System.out.println("目标类中执行 sell 方法");
return 85.0f;
}
}
MySellHandler.java:
package com.proxy.handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MySellHandler implements InvocationHandler {
private Object target = null;
//动态代理对象目标是活动的,不是固定的,需要传进来
//传入的是谁,就给谁代理
public MySellHandler(Object target) {
//给目标对象赋值
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
/* float price = factory.sell(amount);
用以下代码替换 */
res = method.invoke(target, args);//执行目标方法
/*
* 代理商加价(增强功能)
* 在目标类的方法调用后,做的其他功能就是功能增强
* */
if (res != null){
float price = (float) res;
res = (price + 25) * Float.parseFloat(args[0].toString());
}
return res;
}
}
MainShop.java:
package com.proxy;
import com.proxy.factory.UsbKingFactory;
import com.proxy.handler.MySellHandler;
import com.proxy.sevice.UsbSell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class MainShop {
public static void main(String[] args) {
//创建代理类,使用 Proxy
//1、创建目标对象
UsbSell factory = new UsbKingFactory();
//2、创建 InvocationHandler 对象
InvocationHandler handler = new MySellHandler(factory);
//3、创建代理对象
UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),
handler);
//通过代理对象执行方法
float price = proxy.sell(3);
System.out.println("通过动态代理对象调用方法:"+price);
}
}