重点标识
Java中的代理,动态代理与静态代理
代理
动态代理
JDK动态代理,前提是一定要有接口,不然实现不了,看看那下面的例子就知道了。
Jdk动态代理
这里用一个简单的计算方法来做一下动态代理演示,首先,定义一个加法接口,如下
public interface ICalculator {
int add(int a,int b);
}
然后,我们定义一个实现类,实现这个接口,
public class ICalculatorImpl implements ICalculator{
@Override
public int add(int a, int b) {
return a+b;
}
}
最后,则是进行动态代理,注释已经写的很清楚了,这里我就不啰嗦了。
public static void main(String[] args) {
ICalculatorImpl iCalculatorimpl = new ICalculatorImpl();
/**
*
* 1,类加载器
* 2,代理对象要实现的接口
* 3,真正的代理对象生成逻辑
*
*/
ICalculator iCalculator = (ICalculator)Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{ICalculator.class}, new InvocationHandler() {
/**
*
* @param proxy 生成的代理对象
*
* @param method 被代理的方法,这里就是add方法
*
* @param args 传入的参数
*
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long l = System.currentTimeMillis();
long endMillis = System.currentTimeMillis();
//args是传入的参数,而invoke则是计算的方法,那在这里,我们可以new Object[]{2,5} 这样,不管你传什么,这里都给你改成2和5了
Object invoke = method.invoke(iCalculatorimpl, args);
System.out.println("代理方法耗时:" + (endMillis - l) + "毫秒!");
//这里,相当于被代理方法的返回值,等于说,如果invoke被改了值,那不管传什么,都会是那个写死的值,如给他个999
return invoke;
}
});
int add = iCalculator.add(2, 4);
System.out.println(add);
}
想了下,这里还是稍微解释一下,为什么要强转成ICalculator,而不是他的实现类ICalculatorImpl,答案实际上很简单,这是一个动态代理,看似是采用接口的实现类ICalculatorImpl来进行的逻辑运算,但实际上,Java会动态生成一个代理类,可以理解为ICalculatorImpl#,然后去调用它里面的add方法,那么这样的话,如果我们强转成ICalculatorImpl,就会报一个类型不匹配的错误,有兴趣的同学可以试一下。
那如果没有借口,我们该如何进行动态代理呢,当然是Cglib动态代理。
Cglib动态代理
通过拦截器,把你的目标方法拦截下来,进行处理。
没有接口,没有实现类,生成一个子类。
准备一个需要增强的类
public class UserService {
public void cat(){
System.out.println("喵喵喵!");
}
}
写一个增强类,这里有个点,Cglib是springframework里面的,所以要引入Spring依赖,注释很详细,就不多说了。
public class MyMethodInterceptor implements MethodInterceptor {
/**
*
* @param obj 代理类
* @param method 方法
* @param args 参数
* @param proxy 代理方法
* @return 返回值
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//这里要注意的是,Cglib动态代理,本质上就是生成一个子类来动态增强父类的方法
long startMillis = System.currentTimeMillis();
Object o = proxy.invokeSuper(obj, args);
long endMillis = System.currentTimeMillis();
System.out.println(proxy.getSuperName()+"方法执行耗时为:"+(endMillis - startMillis)+"毫秒!");
return o;
}
}
最后进行调用:
public static void main(String[] args) {
//这个实际上就是字节增强器
Enhancer enhancer = new Enhancer();
//先设置父类
enhancer.setSuperclass(UserService.class);
//设置拦截器
enhancer.setCallback(new MyMethodInterceptor());
//如果有接口,可以使用下面的
//enhancer.setInterfaces();
//调用,生成代理对象,就是UserService的子类所产生的对象
UserService userService =(UserService) enhancer.create();
userService.cat();
}
AOP
AOP的底层就是动态代理,默认有接口就是用jdk动态代理,没有接口就是用Cglib,但是如果是Spring Boot,版本2之前和Spring一样,从Spring Boot版本2以后,默认情况下都是Cglib。
还是和之前一样,用计算器来演示这个,注意,要使用AOP,我们除了Spring-Context依赖以外,还需要spring-aspects切面依赖,如下
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.1.4</version>
</dependency>
计算器接口和实现类这里就不写了,还是上面那部分代码,直接看调用的:
public static void main(String[] args) {
//new 大力工厂,这个是Spring提供的,底层就是cglib和jdk动态代理
ProxyFactory proxyFactory = new ProxyFactory();
//设置被代理的对象
proxyFactory.setTarget(new ICalculatorImpl());
//代理对象要实现的接口
proxyFactory.addInterface(ICalculator.class);
//设置为ture,则直走Cglib,设置为false则根据情况,
// 有接口则使用jdk动态代理,没有接口则使用Cglib动态代理
proxyFactory.setProxyTargetClass(true);
//通知,增强
proxyFactory.addAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
long startMillis = System.currentTimeMillis();
Method method = invocation.getMethod();
Object proceed = invocation.proceed();
long endMillis = System.currentTimeMillis();
System.out.println(method.getName()+ "方法耗时: " +(endMillis - startMillis)+" 毫秒!");
return proceed;
}
});
ICalculator iCalculator = (ICalculator)proxyFactory.getProxy();
System.out.println(iCalculator.add(
2, 3
));
}
这里我们可以debug看一下,按照我现在设置的,必须走Cglib动态代理,在代理接口打个断点就可以看到:
确实是使用了Cglib动态代理。
如果不设置setProxyTargetClass这个,默认为true,我们再看一下,
就是$Proxy,jdk动态代理了。
结语
放弃很容易,坚持却很难!