代理模式


代理模式

1.基础知识

定义∶

为其他对象提供一种代理,以控制对这个对象的访问

代理对象在客户端和目标对象之间起到中介的作用


适用场景

保护目标对象

增强目标对象


优点

代理模式能将代理对象与真实被调用的目标对象分离

一定程度上降低了系统的耦合度,扩展性好

保护目标对象

增强目标对象


缺点

代理模式会造成系统设计中类的数目增加

在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢

增加系统的复杂度


扩展

静态代理

动态代理

CGLib代理


Spring代理选择

当Bean有实现接口时,Spring就会用JDK的动态代理

当Bean没有实现接口时,Spring使用CGlib

可以强制使用Cglib

在spring配置中加入


2.实战

在常用的保存用户信息的接口上,统计接口耗时,分别用静态代理和动态代理实现

静态代理方式:

/**
 * 用户类
 * @Author LYS
 * @Date 2022/1/16 15:21
 * @Version 1.0
 */
@Data
public class User {
    private String name;
    private String sex;
}
/**
 * @Author LYS
 * @Date 2022/1/16 15:21
 * @Version 1.0
 */
public interface UserService {
    int saveUserInfo(User user) throws InterruptedException;
}
/**
 * @Author LYS
 * @Date 2022/1/16 15:22
 * @Version 1.0
 */
public class UserServiceImpl implements UserService{
    @Override
    public int saveUserInfo(User user) throws InterruptedException {
        System.out.println("保存成功!");
        Thread.sleep(100);
        return 1;
    }
}
/**
 * 静态代理
 *
 * @Author LYS
 * @Date 2022/1/16 15:23
 * @Version 1.0
 */
public class UserServiceStaticProxy {
    private UserService userService;
    long start;

    public int saveUser(User user) throws InterruptedException {
        userService = new UserServiceImpl();
        beforeMethod();
        int result = userService.saveUserInfo(user);
        afterMethod();
        return result;
    }

    public void print() {
        System.out.println("打印数据:");
    }

    private void beforeMethod() {
        System.out.println("静态代理 before code");
        start = System.currentTimeMillis();

    }

    private void afterMethod() {
        System.out.println("静态代理 after code");
        long time = System.currentTimeMillis() - start;
        System.out.println("调用此接口花费了【" + time + "】毫秒");
    }
}
/**
 * @Author LYS
 * @Date 2022/1/16 15:28
 * @Version 1.0
 */
public class StaticTest {
    public static void main(String[] args) throws InterruptedException {
        User user = new User();
        UserServiceStaticProxy userServiceStaticProxy = new UserServiceStaticProxy();
        userServiceStaticProxy.saveUser(user);
    }
}

控制台输出:

image-20220116154031025


动态代理方式:使用反射invoke调用真实方法

/**
 * @Author LYS
 * @Date 2022/1/16 15:41
 * @Version 1.0
 */
public class UserServiceDynamicProxy implements InvocationHandler {
    private Object target;
    long start;

    public UserServiceDynamicProxy(Object target) {
        this.target = target;
    }

    /**
     * 返回动态代理类
     *
     * @return
     */
    public Object bind() {
        Class cls = target.getClass();
        return Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), this);
    }


    /**
     * 动态代理类实际执行的方法
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object argObject = args[0];
        beforeMethod(argObject);
        Object object = method.invoke(target, args);
        afterMethod();
        return object;
    }

    private void beforeMethod(Object obj) {
        System.out.println("动态代理 before code");
        start = System.currentTimeMillis();

    }

    private void afterMethod() {
        System.out.println("动态代理 after code");
        long time = System.currentTimeMillis() - start;
        System.out.println("调用此接口花费了【" + time + "】毫秒");
    }
}
public class DynamicTest {
    public static void main(String[] args) throws InterruptedException {
        User user = new User();
        user.setName("小李");
        UserService userServiceStaticProxy = (UserService) new UserServiceDynamicProxy(new UserServiceImpl()).bind();
        userServiceStaticProxy.saveUserInfo(user);
    }
}

控制台输出

image-20220116154614098


3.spring源码分析

spring 动态代理实现AOP

我们在使用 Spring AOP 时,需要先配置好 ProxyFactoryBean,然后通过 ac.getBean(bean id) 来获取 ProxyFactoryBean 代理的对象。而 ProxyFactoryBean 类使用 ProxyFactoryBean.getObject() 方法获取返回的对象,即代理对象。

下面先看 ProxyFactoryBean 类中的核心方法 getObject(),源码如下:

public Object getObject() throws BeansException {
   initializeAdvisorChain();
   if (isSingleton()) {
      return getSingletonInstance();
   }
   else {
      if (this.targetName == null) {
         logger.info("Using non-singleton proxies with singleton targets is often undesirable. " +
               "Enable prototype proxies by setting the 'targetName' property.");
      }
      return newPrototypeInstance();
   }
}

在 getObject() 方法中,主要调用 getSingletonInstance() 和 newPrototypeInstance() 方法。

在 Spring 的配置中,如果不做任何设置,则 Spring 代理生成的 Bean 都是单例对象。如果修改 scope,则每次都创建一个新的原型对象。newPrototypeInstance() 里的逻辑比较复杂,教程后面会详细讲解,这里简单了解即可。

Spring 使用动态代理实现 AOP 时有两个非常重要的类,即 JdkDynamicAopProxy 类和 CglibAopProxy 类,其类图如下:

img

Spring 中的代理选择如下:

  • 当 Bean 有实现接口时,Spring 会用 JDK 动态代理方式
  • 当 Bean 没有实现接口时,Spring 会选择 CGLib 动态代理方式

Spring 可以通过配置强制使用 CGLib 动态代理,只需在 Spring 的配置文件中加入如下代码即可。

相关