一、前言
-
参考视频:遇见狂神说-Spring5
-
参看博文::Java SDK动态代理类怎么调用到invoke()方法?
-
使用:
- IDEA 2019.3
- Maven
- Junit 4.12
为什么要学代理模式?因为这就是SpringAOP的底层!
代理模式的分类:
- 静态代理
- 动态代理
二、角色分析
- 抽象角色:一般会使用接口或抽象类
- 真实角色:被代理角色
- 代理角色:代理真实的角色,代理角色后,我们一般会做一些附属操作
- 客户:访问代理角色的人
三、准备代码
3.1 租户->房东
在租户直接面对房东的模式中:
路径
代码
Rent.java:
package demo01;
/**
* @Autord: HuangDekai
* @Date: 2020/10/1 20:29
* @Version: 1.0
* @since: jdk11
*/
public interface Rent {
void getRent();
}
Landlord.java:
package demo01;
/**
* @Autord: HuangDekai
* @Date: 2020/10/1 20:25
* @Version: 1.0
* @since: jdk11
*/
public class Landlord implements Rent{
@Override
public void getRent(){
System.out.println("房东获取租金。");
}
}
Client.java:
package demo01;
/**
* @Autord: HuangDekai
* @Date: 2020/10/1 20:27
* @Version: 1.0
* @since: jdk11
*/
public class Client {
public static void main(String[] args) {
Landlord landlord = new Landlord();
landlord.getRent();
}
}
3.2 租户->中介->房东
但是在实际情况中,租户往往是找不到房东的,而房东又是只想收租,不想做找租客、维护等的工作,那么这时候就要引入一个中介:
Proxy:
package demo01;
/**
* @Autord: HuangDekai
* @Date: 2020/10/1 20:47
* @Version: 1.0
* @since: jdk11
*/
public class Proxy implements Rent{
private Landlord landlord;
public Proxy() {
}
public Proxy(Landlord landlord) {
this.landlord = landlord;
}
@Override
public void getRent() {
landlord.getRent();
}
}
这时候Client.java也要修改:
package demo01;
/**
* @Autord: HuangDekai
* @Date: 2020/10/1 20:27
* @Version: 1.0
* @since: jdk11
*/
public class Client {
public static void main(String[] args) {
Landlord landlord = new Landlord();
Proxy proxy = new Proxy(landlord);
proxy.getRent();
}
}
运行可知,这依旧是让房东收房租,即,收租这个操作依旧是让房东运行的。
那么加这个代理人有什么作用呢?现在还没体现出来。
那么比如,要加入招租、收中介费、维护房子的操作,这些不是房东想要做的,房东只想收租。
而这些是代理人要做的,这些就是附属操作。
修改Proxy.java:
package demo01;
/**
* @Autord: HuangDekai
* @Date: 2020/10/1 20:47
* @Version: 1.0
* @since: jdk11
*/
public class Proxy implements Rent{
private Landlord landlord;
public Proxy() {
}
public Proxy(Landlord landlord) {
this.landlord = landlord;
}
public void forLease(){
System.out.println("中介招租");
}
public void maintain(){
System.out.println("维修房子");
}
public void agencyFee(){
System.out.println("中介获取中介费");
}
@Override
public void getRent() {
this.forLease();
this.agencyFee();
landlord.getRent();
this.maintain();
}
}
Client.java、Landlord.java的代码没变,但现在运行main就变成了:
四、静态代理小结
代码步骤:
- 接口
- 真实角色
- 代理角色
- 客户
代理模式的好处:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务;
- 公共业务也就交给代理角色,实现了业务的分工;
- 公共业务发生扩展的时候,方便集中管理。
缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率降低
五、加深理解
5.1 Service->ServiceImpl
路径
代码
Service:
package demo02;
/**
* @Autord: HuangDekai
* @Date: 2020/10/1 22:08
* @Version: 1.0
* @since: jdk11
*/
public interface Service {
void add();
void delete();
void update();
void query();
}
ServiceImp:
package demo02;
/**
* @Autord: HuangDekai
* @Date: 2020/10/1 22:09
* @Version: 1.0
* @since: jdk11
*/
public class ServiceImpl implements Service {
@Override
public void add() {
System.out.println("执行add()");
}
@Override
public void delete() {
System.out.println("执行delete()");
}
@Override
public void update() {
System.out.println("执行update()");
}
@Override
public void query() {
System.out.println("执行query()");
}
}
Controller:
package demo02;
/**
* @Autord: HuangDekai
* @Date: 2020/10/2 10:17
* @Version: 1.0
* @since: jdk11
*/
public class Controller {
public static void main(String[] args) {
Service service = new ServiceImpl();
service.add();
service.delete();
service.update();
service.query();
}
}
这个时候假设dao---->service---->controller---->前端的代码已经完成了,由于业务量的增大,要在业务层使用日志功能。
由于巨大的代码量(假设有),直接对ServiceImpl去进行修改是很不现实的操作,那么就要加入代理。
5.2 ServiceImplProxy
路径
代码
ServiceImplProxy:
package demo02;
/**
* @Autord: HuangDekai
* @Date: 2020/10/2 10:22
* @Version: 1.0
* @since: jdk11
*/
public class ServiceImplProxy implements Service {
private Service service;
public void setService(Service service) {
this.service = service;
}
private void log(String log){
System.out.println("[Debug]"+log);
}
@Override
public void add() {
log("add()");
service.add();
}
@Override
public void delete() {
log("delete()");
service.delete();
}
@Override
public void update() {
log("update()");
service.update();
}
@Override
public void query() {
log("query()");
service.query();
}
}
由于没有用Spring做控制反转,所以还需要修改Controller这个cilent:
package demo02;
/**
* @Autord: HuangDekai
* @Date: 2020/10/2 10:17
* @Version: 1.0
* @since: jdk11
*/
public class Controller {
public static void main(String[] args) {
Service service = new ServiceImpl();
ServiceImplProxy serviceProxy = new ServiceImplProxy();
serviceProxy.setService(service);
serviceProxy.add();
serviceProxy.delete();
serviceProxy.update();
serviceProxy.query();
}
}
运行结果:
六、AOP
七、动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生产的,不是我们直接写好的
- 动态代理:
- 基于接口的动态代理------如:JDK动态代理
- 基于类的动态代理---------如:cglib
- java字节码实现-------------如:JAVAssist
需要了解:Proxy、InvocationHandler。
InvocationHandler是一个接口类,
public interface InvocationHandler
InvocationHandler
是由代理实例的调用处理程序实现的接口 。每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的
invoke
方法。
它只有一个invoke
方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。
参数
proxy
- 调用该方法的代理实例
method
-所述Method
对应于调用代理实例上的接口方法的实例。Method
对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
args
-包含的方法调用传递代理实例的参数值的对象的阵列,或null
如果接口方法没有参数。 原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer
或java.lang.Boolean
。结果
从代理实例上的方法调用返回的值。 如果接口方法的声明返回类型是原始类型,则此方法返回的值必须是对应的基本包装类的实例; 否则,它必须是可声明返回类型的类型。 如果此方法返回的值是
null
和接口方法的返回类型是基本类型,那么NullPointerException
将由代理实例的方法调用抛出。 如上所述,如果此方法返回的值,否则不会与接口方法的声明的返回类型兼容,一个ClassCastException
将代理实例的方法调用将抛出。异常
Throwable
- 从代理实例上的方法调用抛出的异常。 异常类型必须可以分配给接口方法的throws
子句中声明的任何异常类型java.lang.RuntimeException
检查的异常类型java.lang.RuntimeException
或java.lang.Error
。 如果检查的异常是由这种方法是不分配给任何的中声明的异常类型throws
接口方法的子句,则一个UndeclaredThrowableException
包含有由该方法抛出的异常将通过在方法调用抛出代理实例。
而java.lang.reflect.Proxy只用到newProxyInstance
这个静态方法:
newProxyInstance
public static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
Proxy.newProxyInstance
因为与IllegalArgumentException
相同的原因而Proxy.getProxyClass
。
参数
loader
- 类加载器来定义代理类
interfaces
- 代理类实现的接口列表
h
- 调度方法调用的调用处理函数结果
具有由指定的类加载器定义并实现指定接口的代理类的指定调用处理程序的代理实例
异常
IllegalArgumentException
- 如果对可能传递给getProxyClass
有任何getProxyClass
被违反
SecurityException
-如果安全管理器,S存在任何下列条件得到满足:给定的loader
是null
,并且调用者的类加载器不是null
,并且调用s.checkPermission
与RuntimePermission("getClassLoader")
权限拒绝访问;对于每个代理接口,intf
,呼叫者的类加载器是不一样的或类加载器的祖先intf
和调用s.checkPackageAccess()
拒绝访问intf
;任何给定的代理接口的是非公和呼叫者类是不在同一runtime package作为非公共接口和调用s.checkPermission
与ReflectPermission("newProxyInPackage.{package name}")
权限拒绝访问。
NullPointerException
- 如果interfaces
数组参数或其任何元素是null
,或者如果调用处理程序h
是null
路径
代码
Rent:
package demo03;
/**
* @Autord: HuangDekai
* @Date: 2020/10/1 20:29
* @Version: 1.0
* @since: jdk11
*/
public interface Rent {
void getRent();
}
Landlord:
package demo03;
/**
* @Autord: HuangDekai
* @Date: 2020/10/1 20:25
* @Version: 1.0
* @since: jdk11
*/
public class Landlord implements Rent {
@Override
public void getRent(){
System.out.println("房东获取租金。");
}
}
ProxyInvocationHandler:
package demo03;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Autord: HuangDekai
* @Date: 2020/10/2 11:41
* @Version: 1.0
* @since: jdk11
*/
// 这个类用于自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
// 生成得到代理类
public Object getProxy(){
// 注意,这个Proxy是java.lang.reflect包下的
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
// 处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(rent,args);
return result;
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
Client:
package demo03;
/**
* @Autord: HuangDekai
* @Date: 2020/10/2 11:51
* @Version: 1.0
* @since: jdk11
*/
public class Client {
public static void main(String[] args) {
Rent rent = new Landlord();
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(rent);
Rent proxy = (Rent) pih.getProxy();
proxy.getRent();
}
}
运行结果:
八、加深理解
和我一样对于动态代理类怎么调用invoke()有疑问的可以看看:Java SDK动态代理类怎么调用到invoke()方法?
路径
代码
ProxyInvocationHandler:
package demo4;
import demo02.Service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Autord: HuangDekai
* @Date: 2020/10/2 13:07
* @Version: 1.0
* @since: jdk11
*/
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Service service;
public void setService(Service service) {
this.service = service;
}
//生成得到一个代理类的实例
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),service.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(service,args);
return result;
}
public void log(String msg){
System.out.println("[debug]"+msg);
}
}
Client:
package demo4;
import demo02.Service;
import demo02.ServiceImpl;
/**
* @Autord: HuangDekai
* @Date: 2020/10/2 13:25
* @Version: 1.0
* @since: jdk11
*/
public class Client {
public static void main(String[] args) {
//真实角色
Service service = new ServiceImpl();
//代理角色不存在
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setObject(service); //设置要代理的对象
Service proxy = (Service) pih.getProxy(); //动态生成代理类
proxy.add();
proxy.delete();
proxy.update();
proxy.query();
}
}
结果:
九、对于使用动态代理
其实就应用的角度来说,动态代理直接使用上面的代码修改一下就能有较高的复用性了:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Autord: HuangDekai
* @Date: 2020/10/2 13:07
* @Version: 1.0
* @since: jdk11
*/
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setObject(Object taget) {
this.target = taget;
}
//生成得到一个代理类的实例
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//添加某些附加方法.....
Object result = method.invoke(target,args);
//添加某些附加方法.....
return result;
}
}
参照上面的代码,把taget的类型换成要代理的类的接口,就基本可行。再在invoke里加入一些附加方法,就是了。
动态代理模式的好处:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务;
- 公共业务也就交给代理角色,实现了业务的分工;
- 公共业务发生扩展的时候,方便集中管理;
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务;
- 一个动态代理类可以代理多个类,只要是实现了同一个接口。