基于接口的动态代理和基于子类的动态代理
代理的分析
举个例子,以前我们购买电脑的时候,我们直接找到厂家购买电脑,厂家也会对我购买的电脑进行售后,在那个时候,购买与售后都由厂家负责.
一开始我们和厂家有直接的联系
但是后来,生产厂家随着时间的发展和推移,各种销量和生产的提升,生产厂家发现既要生产,又要销售和售后压力很大
这个时候生产厂家运营成本主要为两个功能 1 生产 2销售和售后,l
那么作为生产厂家来说,不希望销售和售后给他带来很大的生产压力,代理商就因应而生
我们再购买电脑时就不能直接与厂家联系了,而是跟代理商联系
当电脑出现了质量问题,我们也是去找经销商.经销商再去找厂家帮我们维修.
不管是销售和售后,我们都不需要再直接与生产厂家保持联系了,生产厂家的压力也被缓解了
我们使用代理机制,可以实现对业务流程的增强,同时代理商和生产厂家之间也会些要求,比如找谁作为代理商,或者说代理商找什么样的生产厂家
代理分析
我们如何再程序中体现刚刚分析的
基于接口的动态代理
代理者想要销售产品时,对选择生产厂家有必要的准则,1是提供产品 2.是提供售后
在Java程序中,我们需要接口来提供这个规范
创建一个生产者的接口,这个生产者有两个功能:1,销售商品 2,售后服务,我们为它写两个方法,saleProduct 和
afterService,参数是销售的金额
IProductor
package proxy;
public interface IProductor {
void saleProduct(Float money);
void afterService(Float money);
}
ProductorImpl
package proxy;
public class ProductorImpl implements IProductor {
public void saleProduct(Float money) {
System.out.println("销售产品,并拿到钱:"+money);
}
public void afterService(Float money) {
System.out.println("提供产品售后,并拿到钱:"+money);
}
}
作为一个消费者,就要去购买了,我们创建一个Client类,模拟一个消费者.
原来购买产品的时候,通过生产厂家直接买,直接找到生产厂家,付款.
package proxy;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
IProductor productor = new ProductorImpl();
productor.saleProduct(10000f);
}
}
而现在,我们需要代理来完成这个功能,不再需要直接与生产厂家接触了.
当代理出现后,我们如何去连接这个代理呢?
动态代理
- 特点:随用随创建,随用随加载
- 作用:不修改代码的基础上,对代码进行增强
动态代理分两类:
- 基于接口的动态代理
- 基于子类的动态代理
基于接口的动态代理
- 涉及的类:Proxy
- 提供者:JDK官方
- 如何创建代理对象?
使用Proxy类中的newProxyInstance()方法 - 创建代理对象的要求
被代理对象最少实现一个接口,否则不能使用 -
newProxyInstance方法的参数
- ClassLoader
用于加载代理对象字节码的,写的是被代理对象的类加载器,和被加载对象使用相同的类加载器
代理谁就写谁的ClassLoader,是固定写法 - Class [ ]
用于代理对象和被代理对象有相同的方法.
(怎样拥有相同的方法?只要两个都实现同一个接口,那么这两个方法就拥有了相同的方法),也是固定写法 -
InvoationHandler
用于提供增强代码,让我们写如何代理
我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的.
接口的实现类一般都是谁用谁写
newProxyInstance方法的参数
new InvoationHandler()
这个接口的实现类就是只有一个方法就是invoke,当我们实现了 InvoationHandler 的接口,代理对象就写完了,它返回的是Object类型,我们用IProductor接受,强转成Productor类型
InvoationHandler
- ClassLoader
invoke
* 作用:执行被代理对象的任何接口方法都会经过此方法(也就是该方法有一个拦截个功能)
* proxy:代理对象的引用
* method:当前执行的方法
* args:当前执行方法所需要的参数
* 和被代理的对象有相同的返回值
当我们把这些东西都分析完之后,其实只需要在return这个地方return method.invoke,第一个参数Object指的是谁的方法,这个方法就是被代理对象的方法,第二个args就是被代理对象的参数.
image.png
当匿名内部类访问外部成员方法时,外部成员方法要用final修饰
这里我们被代理对象时productor,当写上productor时,因为匿名内部类访问外部成员时要用final修饰,之前定义productor的地方用上final修饰
final IProdctor productor
写到这里其实只是写出了一个代理对象,并没有增强方法,要实现代码增强,还要写代码.
我们的思路就是消费者购买产品的时候给了10000,但是作为代理商,需要提取20%作为利润,生产厂家只能拿到8000块
那么如何去做这个操作呢?
第一步,获取方法执行的参数
第二步,判断当前方法是不是销售
增强的代码
提醒money * 0.8f的时候记得带f,否则会报错
Exception in thread "main" java.lang.IllegalArgumentException: argument type mismatch
这段代码写完以后,我们并没有对saleProduct进行改变,但我们已经实现了方法的增强,这就是基于接口的动态代理,但是它有一个问题如果我们的类不实现任何一个接口的时候,它是无法执行的
基于子类的动态代理
针对于我们的类不实现任何接口的情况,我们怎么解决我们如何代理一个普通的java类呢?
这时候就要用到基于子类的动态代理
之前有略微提到,基于子类的动态代理,提供者为第三方,叫cglib.
所以在pom.xml导入cglib的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>day03_eesy_02proxy</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>12</maven.compiler.source>
<maven.compiler.target>12</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>
</project>
就在基于接口的动态代理的工程中,新建一个名叫cglib的pacage,复制ProductorImpl和Client进去,并修改ProductorImpl文件名为Productor,Productor不再实现任何接口
,复制ProductorImpl和Client进去,并修改ProductorImpl文件名为Productor
Productor 类如下:
package cglib;
public class Productor{
public void saleProduct(Float money) {
System.out.println("销售产品,并拿到钱:"+money);
}
public void afterService(Float money) {
System.out.println("提供产品售后,并拿到钱:"+money);
}
}
-
基于子类的动态代理
-
如何创建代理对象:Enhancer
提供者:第三方 cglib库 -
如何创建代理对象
使用Enhancer的create方法 -
被创建代理对象的要求
被代理类不能是最终类 -
create方法的参数
Class:字节码
他是固定写法,被代理对象的字节码(只要有了被代理对象的字节码,
(不管是想用它的类加载器或者查看它有哪些内容,都可以得到,要想代理谁,就写谁的.class)
(Class[ ]:字节码数组 在基于子类的动态代理中,子类是可以不实现任何接口的,所以这个方法没有了)
Callback:用于提供增强的代码
我们一般写的都是该接口的子接口实现类,MethodInterceptor,new一个MethodInterceptor,它是Callback的子接口
Enhance.crate()方法,可以看到一个是Class类型,一个是Callback类型
我们一般写的都是该接口的子接口实现类,MethodInterceptor,new一个MethodInterceptor
-
如何创建代理对象:Enhancer
-
MethodInterceptor
执行被代理对象的任何方法都经过该方法
@param proxy
@param method
@param args
此时把参数名改成一样.以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
@param methodProxy 当前执行方法的代理对象(用不上)
@return
@throws Throwable
在Client中,还是和跟基于子类的动态代理一样,写上增强的代码,思路仍然一致 1.获取方法执行的参数,2.判断当前方法是不是销售,这里就直接copy基于子类的动态代理的增强代码拿来使用,然后Enhancer.crate()方法拿Productor类的对象CglibP来接收,然后代理对象执行saleProduct方法
完整代码如下
package cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import proxy.IProductor;
import java.lang.reflect.Method;
public class Client {
public static void main(String[] args) {
final Productor productor = new Productor();
Productor CglibP = (Productor) Enhancer.create(productor.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object invoke = null;
Float money = (Float) args[0];
if ("saleProduct".equals(method.getName())) {
invoke = method.invoke(productor, money * 0.8f);
}
return invoke;
}
});
CglibP.saleProduct(20000f);
}
}