代理模式(静态代理和动态代理)

一.什么是代理模式

代理模式:为其他对象提供一种代理以便控制对这个对象的访问。

可以详细控制访问某个类(对象)的方法,在调用这个方法前作的前置处理(统一的流程代码放到代理中处理),调用这个方法后做后置处理。

例如:明星的经纪人,租房的中介等等都是代理

 

二.代理模式的分类

 

三.静态代理

静态代理的代理对象和被代理对象在代理之前就已经确定,它们都实现相同的接口或继承相同的抽象类。静态代理模式一般由业务实现类和业务代理类组成,业务实现类里面实现主要的业务逻辑,业务代理类负责在业务方法调用的前后作一些你需要的处理,如日志记录、权限拦截等功能…实现业务逻辑与业务方法外的功能解耦,减少了对业务方法的入侵。静态代理又可细分为:基于继承的方式和基于聚合的方式实现。

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 
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值