一.什么是代理模式
代理模式:为其他对象提供一种代理以便控制对这个对象的访问。
可以详细控制访问某个类(对象)的方法,在调用这个方法前作的前置处理(统一的流程代码放到代理中处理),调用这个方法后做后置处理。
例如:明星的经纪人,租房的中介等等都是代理
二.代理模式的分类
三.静态代理
静态代理的代理对象和被代理对象在代理之前就已经确定,它们都实现相同的接口或继承相同的抽象类。静态代理模式一般由业务实现类和业务代理类组成,业务实现类里面实现主要的业务逻辑,业务代理类负责在业务方法调用的前后作一些你需要的处理,如日志记录、权限拦截等功能…实现业务逻辑与业务方法外的功能解耦,减少了对业务方法的入侵。静态代理又可细分为:基于继承的方式和基于聚合的方式实现。
1.基于继承方式进行代理
我们首先创建一个接口,里面有say方法:
public interface UserService {
void say(String name);
}
接着我们写一个实现类实现这个接口:
/**
* @author Gjf
* @date 2019/6/25
* @content 基于继承的方式实现代理
*/
public class UserServiceImpl implements UserService{
@Override
public void say(String name) {
System.out.println(name);
}
}
然后写一个代理类继承我们的实现类:
public class UserServiceProxy extends UserServiceImpl{
@Override
public void say(String name) {
System.out.println("Hi");
super.say(name);
System.out.println("ByeBye");
}
}
我们测试一下上述代码:
@Test
public void userServiceProxyTest() {
UserService userService = new UserServiceProxy();
userService.say("张三");
}
查看控制台输出:
发现我们已经在原来的方法上加入了新功能。
2.基于聚合的方式进行代理
接下来我们采用聚合的方式进行静态代理,让我们的代理类直接实现userService:
public class UserServiceProxy2 implements UserService{
private UserServiceImpl userServiceImpl;
public UserServiceProxy2(UserServiceImpl userServiceImpl) {
this.userServiceImpl = userServiceImpl;
}
@Override
public void say(String name) {
System.out.println("Hi2");
userServiceImpl.say(name);
System.out.println("ByeBye2");
}
}
我们测试一下上述代码:
@Test
public void userServiceProxy2Test() {
UserService userService = new UserServiceProxy2(new UserServiceImpl());
userService.say("张三");
}
控制台输出:
和上述继承的方式一样实现了新功能。
3.继承和聚合的优劣
如果现在我们需要再添加一些新功能,按照继承的方式需要不断的继承上一个子类,如果我们利用聚合的方式只需要新的子类实现父类接口,再将需要增强的方法所属对象引进来就可以了。
四.动态代理
看完静态代理,大家会发现我们只要想增加一个功能就需要新增加一个类,而且只是针对于一个接口。如果是多个接口需要增加大量的类去实现我们需要的功能,这个时候我们就需要用到动态代理了。动态代理就可以动态的生成代理类,实现对不同类下的不同方法的代理。
1.jdk动态代理
jdk动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用业务方法前调用InvocationHandler处理。代理类必须实现InvocationHandler接口,并且,JDK动态代理只能代理实现了接口的类,没有实现接口的类是不能实现JDK动态代理。
package cn.keking.project.elastictest.dynamicproxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author Gjf
* @date 2019/6/25
*/
public class JdkDynamicUserLogProxy implements InvocationHandler{
private Object targetObject;
public JdkDynamicUserLogProxy(Object targetObject) {
this.targetObject = targetObject;
}
/**
*
* @param proxy 代理对象
* @param method 被代理对象的方法
* @param args 被代理对象的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("见面开始打招呼");
Object invoke = method.invoke(targetObject, args);
System.out.println("见面结束打招呼");
return invoke;
}
}
上面我们用实现了 InvocationHandler接口,接下来我们要真正的得到代理类:
@Test
public void dynamaicProxyTest() {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");//该设置用于输出jdk动态代理产生的类
UserService userService = new UserServiceImpl();
Class<?> clazz = userService.getClass();
JdkDynamicUserLogProxy jdkDynamicUserLogProxy = new JdkDynamicUserLogProxy(userService);
userService = (UserService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), jdkDynamicUserLogProxy);
userService.say("李四");
}
输出结果:
我们可以总结一下jdk动态代理的流程:
1、编写需要被代理的类和接口(我这里就是UserServiceImpl、UserService);
2、编写代理类(例如我这里的JdkDynamicUserLogProxy ),需要实现InvocationHandler接口,重写invoke方法;
3、使用Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)动态创建代理类对象,通过代理类对象调用业务方法。
2.cglib动态代理
cglib是针对类来实现代理的,它会对目标类产生一个代理子类,通过方法拦截技术对过滤父类的方法调用。代理子类需要实现MethodInterceptor接口。下面我们写一个cglib的拦截类:
/**
* @author Gjf
* @date 2019/6/25
*/
public class CglibDynamicUserLogProxy implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
public Object getProxyObj(Class clazz) {
//设置父类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
enhancer.setUseCache(false);
return enhancer.create();
}
/**
*
* @param o 代理对象
* @param method 目标对象方法
* @param objects 目标对象方法参数
* @param methodProxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("见面开始打招呼");
Object invoke = methodProxy.invokeSuper(o, objects);
System.out.println("见面结束打招呼");
return invoke;
}
}
着重注意的是,intercept方法中的o为代理对象,很多地方写的o为目标对象,如果o为目标对象,我们只是简单的o.invoke调用目标对象的方法就可以了。但是如果我们那么做,就会造成一个死循环,无限次的调用代理对象的方法。
下面我们测试一下我的我们的代码:
public class CglibDynamaicProxyTest {
@Test
public void dynamaicProxyTest() {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\aaa");
UserService userService = new UserServiceImpl();
CglibDynamicUserLogProxy cglibDynamicUserLogProxy = new CglibDynamicUserLogProxy();
userService = (UserService) cglibDynamicUserLogProxy.getProxyObj(userService.getClass());
userService.say("王五");
ValidationAutoConfiguration configuration;
}
}
输出:
实现了代理的效果。
3.两种动态代理的区别
1、JDK动态代理只能代理实现了接口的类,没有实现接口的类不能实现JDK的动态代理;
2、Cglib动态代理是针对类实现代理的,运行时动态生成被代理类的子类拦截父类方法调用,因此不能代理声明为final类型的类和方法;
4.Spring如何选择两种代理模式
1、如果目标对象实现了接口,则默认采用JDK动态代理;
2、如果目标对象没有实现接口,则使用Cglib代理;
3、如果目标对象实现了接口,但强制使用了Cglib,则使用Cglib进行代理
我们看一下源码中spring是如何进行选择的:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
return new JdkDynamicAopProxy(config);
} else {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
} else {
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}
源码中,config.isOptimize()和config.isProxyTargetClass() 默认是false,因此到底是选择jdk还是cglib是由hasNoUserSuppliedProxyInterfaces(config)决定的,这个方法判断目标对象是否有接口,所以在默认情况下,目标对象是否有接口是判断什么动态代理方式的关键。
如果你想显示声明使用cglib进行开发,在springboot环境下,你需要在application.properties文件中配置
spring.aop.proxy-target-class=true
网上有人说在启动类头部添加如下注解 :
@EnableAspectJAutoProxy(proxyTargetClass = true)
我试了不管你设置成true还是false并没有效果,都是cglib代理,具体的原因是由于spring的ValidationAutoConfiguration这个类导致的,他在获取配置文件的属性时,如果获取不到proxy-target-class的属性,会把这个值默认为true,即用cglib实现动态代理
@Bean
@ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,
@Lazy Validator validator) {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
processor.setProxyTargetClass(proxyTargetClass);
processor.setValidator(validator);
return processor;
}
由于笔者水平有限,文章中有错误的地方还请各位同学指正,谢谢^_^
参考博文:https://blog.csdn.net/fanrenxiang/article/details/81939357