Java 中的代理
Java 中的代理,整体上可以分为两种:
- 静态代理,23 种设计模式,里边有一个代理模式,那个就是静态代理。
- 动态代理:
a. 编译时增强
ⅰ. AspectJ
b. 运行时增强
ⅰ. JDK 动态代理
ⅱ. CGLIB 动态代理
例子:买电脑
背景:在早年间,假如你要买台电脑你得去联系电脑生产厂商购买,随着业务规模的增加,生产的电脑越来越多,当然买的人也多,这时候对于厂家来说相关的运营成本(仓储成本、物流成本、售后服务…)也随之增加了,为了剥离这块不相关的业务使之能专注生产电脑,把卖电脑的业务及相关的后期维护打包卖给代理商,由代理商来卖电脑。
代理:基于此发展而来的中间代理商必须要取得生产厂家的授权,在代码中即要实现相同的接口,一套标准
spring 会根据目标对象是否实现接口来选择哪种动态代理
静态代理
上述例子使用静态代理怎么做呢?首先需要定义一个接口,
public interface IProduer {
void saleProduct(float money);
}
接着生产厂家和代理厂商都实现这个接口
/**
* 生产厂商实现
*/
public class Producer implements IProduer {
@Override
public void saleProduct(float money) {
System.out.println("生产厂家得到的钱:" + money);
}
}
/**
* 中介作为中间代理,实现了接口,也代理了生产厂商
*/
public class ProducerAgent implements IProduer{
private Producer producer;
public ProducerAgent(Producer producer) {
this.producer = producer;
}
@Override
public void saleProduct(float money) {
publicAd();
producer.saleProduct(money);
agentFee();
}
private void agentFee() {
System.out.println("收取中介费...");
}
private void publicAd() {
System.out.println("代理厂商发广告,找卖家..");
}
}
来看看效果:
public class App {
public static void main(String[] args) {
Producer produer = new Producer();
ProducerAgent producerAgent = new ProducerAgent(produer);
producerAgent.saleProduct(1500);
}
}
输出:
代理厂商发广告,找卖家..
生产厂家得到的钱:1500.0
收取中介费...
可以看到,静态代理需要写很多的代理类,实际开发过程中很少去用
AspectJ
首先,在 IDEA 中想要运行 AspectJ,需要先安装 AspectJ 插件,就是下面这个:
安装好之后,我们需要在 IDEA 中配置一下,使用 ajc 编译器代替 javac(这个是针对当前项目的设置,所以可以放心修改):
有如下几个需要修改的点:
- 首先修改编译器为 ajc。
- 将使用的 Java 版本改为 8,这个一共有两个地方需要修改。
- 设置 aspectjtools.jar 的位置,这个 jar 包需要自己提前准备好,可以从 Maven 官网下载,然后在这里配置 jar 的路径,配置完成之后,点
击 test 按钮进行测试,测试成功就会弹出来图中的弹框。
对于第 3 步所需要的 jar,也可以在项目的 Maven 中添加如下依赖,自动下载,下载到本地仓库之后,再删除掉 pom.xml 中的配置即可:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.7.M3</version>
</dependency>
这样,开发环境就准备好了。接下来,假设我有一个银行转帐的方法:
public class MoneyService {
public void transferMoney() {
System.out.println("转账操作");
}
}
我想给这个方法添加事务,那么我就新建一个 Aspect,如下:
public aspect TxAspect {
void around():call(void MoneyService.transferMoney()){
System.out.println("开启事务");
try {
proceed();
System.out.println("提交事务事务");
} catch (Exception e) {
System.out.println("回滚事务");
}
}
}
这就是 AspectJ 的语法,跟 Java 有点像,但是不太一样。需要注意的是,这个 TxAspect 不是一个 Java 类,它的后缀是 .aj ,proceed 表示继续执行目标方法,
最后,我们去运行转账服务:
public class Demo01 {
public static void main(String[] args) {
MoneyService moneyService = new MoneyService();
moneyService.transferMoney();
}
}
运行结果:
开启事务
转账操作
提交事务
这就是一个静态代理,为什么这么说呢?我们通过 IDEA 来查看一下 TxAspect 编译之后的结果:
@Aspect
public class TxAspect {
static {
try {
ajc$postClinit();
} catch (Throwable var1) {
ajc$initFailureCause = var1;
}
}
public TxAspect() {
}
@Around(
value = "call(void MoneyService.transferMoney())",
argNames = "ajc$aroundClosure"
)
public void ajc$around$org_javaboy_demo_p2_TxAspect$1$3b99afea(AroundClosure ajc$aroundClosure) {
System.out.println("开启事务");
try {
ajc$around$org_javaboy_demo_p2_TxAspect$1$3b99afeaproceed(ajc$aroundClosure);
System.out.println("提交事务事务");
} catch (Exception var2) {
System.out.println("回滚事务");
}
}
public static TxAspect aspectOf() {
if (ajc$perSingletonInstance == null) {
throw new NoAspectBoundException("org_javaboy_demo_p2_TxAspect", ajc$initFailureCause);
} else {
return ajc$perSingletonInstance;
}
}
public static boolean hasAspect() {
return ajc$perSingletonInstance != null;
}
}
再看一下编译之后的启动类:
public class Demo01 {
public Demo01() {
}
public static void main(String[] args) {
MoneyService moneyService = new MoneyService();
transferMoney_aroundBody1$advice(moneyService, TxAspect.aspectOf(), (AroundClosure)null);
}
}
可以看到,都是修改后的内容了。
所以说 AspectJ 的作用就有点类似于 Lombok,直接在编译时期将我们的代码改了,这就是编译时增强。
基于接口的动态代理(JDK自带)
-
统一的接口规范
public interface IProduer { void saleProduct(float money); }
-
生产厂家的实现
public class Producer implements IProduer { @Override public void saleProduct(float money) { System.out.println("生产厂家得到的钱:"+money); } }
-
代理售卖电脑
public class Client { public static void main(String[] args) { Producer producer = new Producer(); IProduer proxyProducer = (IProduer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 作用:执行被代理对象的任何接口方法都会经过该方法 * @param proxy 代理对象的引用 * @param method 当前执行的方法 * @param args 当前执行方法的参数 * @return 和被代理对象有相同的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //提供增强的业务代码 Object res = null; float money = (float) args[0]; if ("saleProduct".equals(method.getName())){ res = method.invoke(producer,money*0.85f); } return res; } }); proxyProducer.saleProduct(10000); } }
缺点:如果没有实现任何接口 Proxy 这种方式就无法使用了
基于子类的动态代理(cgLib)
-
引入第三方 jar 包
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_3</version> </dependency> </dependencies>
-
生产厂家
public class Producer{ public void saleProduct(float money) { System.out.println("生产厂家得到的钱:"+money); } }
-
经销商
public class Client { public static void main(String[] args) { Producer producer = new Producer(); Producer producerProxy = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() { /** * 作用:执行被代理对象的任何接口方法都会经过该方法 * @param proxy 代理对象的引用 * @param method 当前执行的方法 * @param args 当前执行方法的参数 * @param methodProxy 当前执行方法的代理对象 * @return 和被代理对象有相同的返回值 * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //提供增强的业务代码 Object res = null; float money = (float) args[0]; if ("saleProduct".equals(method.getName())){ res = method.invoke(producer,money*0.8f); } return res; } }); producerProxy.saleProduct(20000); } }