Bootstrap

代理模式与Spring AOP:从设计模式到手写源码

代理模式

给一个对象提供一种代理对象以控制对该对象的访问。代理对象在不改变原对象代码的基础上对原对象的功能进行扩展。

使用代理降低了系统的耦合度,扩展性也会更好,还可以起到保护目标对象的作用。代理模式在 Java 开发中是广泛应用的,特别是在框架中底层原理经常涉及到代理模式(尤其是动态代理)。

静态代理和动态代理,实际使用时还是动态代理使用的比较多。原因就是静态代理需要自行手写代码,维护、修改非常繁琐,会额外引入很多工作量。也不能很好的使用配置完成逻辑的指定,所以使用较少。

  • 静态代理维护复杂,一旦接口或父类发生改变,所有相关的类或接口就都得进行维护。
  • 动态代理无需手写代理类,也不会存在代码编译的过程。运用在内存中生产代理类的技术在 JVM 的运行区造一个代理对象,只需对需要修改的部分进行编辑。

基于 JDK 和基于 CGLIB,实际使用时两个都会用到。

在 Spring 中,默认情况下它就支持了两种动态代理方式。如果你指定的目标类实现了接口,Spring 就会自动选择 JDK 的动态代理。而如果目标类没有实现接口,则 Spring 会使用 CGLIB。而在 SpringBoot 中,默认使用的是 CGLIB。

我们在开发时,由于基于 JDK 的动态代理要求比较多,更不容易实现,所以很多人习惯于统一配置为使用 CGLIB 进行代理。也就是 CGLIB 更通用。

【静态代理】基于接口实现

让目标对象和代理对象都实现一个共同接口。那么这两个类就有了公共的方法,就可以在代理对象中实现对目标对象功能的扩展。

public class StaticProxyInterfaceDemo {

    /**
     * 公共接口
     */
    public interface Dao {
        void save();
    }

    /**
     * 原始对象
     */
    static class UserDao implements Dao {
        @Override
        public void save() {
            System.out.println("保存数据");
        }
    }

    /**
     * 代理对象
     */
    static class UserDaoProxy implements Dao {

        private UserDao userDao;

        @Override
        public void save() {
            System.out.println("开启事务");
            userDao.save();
            System.out.println("提交事务");
        }
    }
}

【静态代理】基于继承实现

让代理对象继承目标对象,那么代理对象就拥有目标对象的方法,同时又可以对其功能进行扩展。

public class StaticProxyExtendsDemo {

    /**
     * 原始对象
     */
    static class UserDao {
        public void save() {
            System.out.println("保存数据");
        }
    }

    /**
     * 代理对象
     */
    static class UserDaoProxy extends UserDao {

        @Override
        public void save() {
            System.out.println("开启事务");
            super.save();
            System.out.println("提交事务");
        }
    }
}

【动态代理】基于接口的 JDK 动态代理

JDK 动态代理基于接口实现,要求原始对象必须实现接口。

public class JdkProxyDemo {

    public static void main(String[] args) {
        // 创建目标对象(如果是 JDK 8 之前的版本,匿名内部类访问外面的变量需要声明 final)
        UserService userService = new UserServiceImpl();
        Class<? extends UserService> userServiceClass = userService.getClass();
        // 使用 JDK 动态代理生成代理对象
        UserService proxyObj = (UserService) Proxy.newProxyInstance(userServiceClass.getClassLoader(), userServiceClass.getInterfaces(), (proxy, method, args1) -> {
            // 在此处编写前置操作
            System.out.println("Before");
            Object result = null;
            try {
                result = method.invoke(userService, args1);
            } catch (Exception e) {
                // 在此处编写异常操作
                System.out.println("Exception");
            }
            // 在此处编写后置操作
            System.out.println("After");
            // 可以在此处编写新的返回值并返回
            return result;
        });
        proxyObj.login();
    }
}

【动态代理】基于继承的 CGLIB 动态代理

CGLIB 动态代理基于继承原始类实现,不需要原始类实现接口,但是原始类不能是 final。

public class CglibProxyDemo {

    public static void main(String[] args) {
        // 创建目标对象
        UserService userService = new UserServiceImpl();
        // 使用 CGlib 动态创建代理对象
        UserService proxyObj = (UserService) Enhancer.create(userService.getClass(), (InvocationHandler) (o, method, objects) -> {
            // 在此处编写前置操作
            System.out.println("Before");
            Object result = null;
            try {
                result = method.invoke(userService, args);
            } catch (Exception e) {
                // 在此处编写异常操作
                System.out.println("Exception");
            }
            // 在此处编写后置操作
            System.out.println("After");
            // 可以在此处编写新的返回值并返回
            return result;
        });
        proxyObj.login();
    }
}

Spring AOP

Spring 的 AOP(面向切面编程)是 Spring 框架中的一个核心特性,它允许开发者在不修改原有代码的情况下,通过添加额外的逻辑来实现横切关注点(cross-cutting concerns)的功能。

Spring 的 AOP 基于代理模式实现。它通过动态代理或者字节码生成技术,在运行时为目标对象生成一个代理对象,并将切面逻辑织入到代理对象的方法调用中。当应用程序调用代理对象的方法时,切面逻辑会在目标方法执行前、执行后或抛出异常时被触发执行。

在 Spring 中,默认情况下它就支持了两种动态代理方式。如果你指定的目标类实现了接口,Spring 就会自动选择 JDK 的动态代理。而如果目标类没有实现接口,则 Spring 会使用 CGLIB。而在 SpringBoot 中,默认使用的是 CGLIB。

关于 Spring AOP 的详细介绍,可以查看这篇文章:《Spring AOP:解锁切面编程的威力与实践》。

我们已经了解到,Spring 的 AOP 本质上就是动态代理,那么 Spring 是通过什么来应用这个动态代理技术的呢?回想之前学习的内容,我们知道 BeanPostProcesser 可以对创建的 Bean 进行加工,而 Spring 的 AOP 正好就是用 BeanPostProcesser 来实现的,它就是 AnnotationAwareAspectJAutoProxyCreator。

Spring 基于注解的 AOP 编程,我们需要在配置类中标记 @EnableAspectJAutoProxy 注解,在 SpringBoot 中我们不需要手动指定,因为 SpringBoot 底层已经进行了封装。如果我们要强制 Spring 每次都使用 CGLIB,那么需要在这个注解添加属性 proxyTargetClass = true

为什么我们指定了 @EnableAspectJAutoProxy 就可以了?我们知道 AOP 是通过 AnnotationAwareAspectJAutoProxyCreator 这个 BeanPostProcesser 来实现的,那么 @EnableAspectJAutoProxy 底层肯定也是通过 @Import 技术引入了这个类。这样就不需要我们自己引入,屏蔽了 Spring 的 AOP 实现细节,语义化的处理也更加的通俗。

@EnableAspectJAutoProxy 还有一个属性 exposeProxy,默认为 false,此参数控制是否将代理对象放入 ThreadLocal,以方便后续获取,解决代理对象的方法中调用本来其他方法代理失效的问题。

我们需要在代理类中配置:@EnableAspectJAutoProxy(exposeProxy=true),随后在代理的原始方法调用其他方法的时候,通过 AopContext.currentProxy() 获取本类的代理对象,然后调用方法。

@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
@ComponentScan("world.xuewei.aopproxy")
public class AopProxyConfig {

}
@Service
public class UserService implements IService {

    @Override
    public void m1() {
        System.out.println("UserService.m1");
        // 注意这里,实现接口默认是 JDK 动态代理,这里获取的代理对象的类型用接口类型接收
        IService service = (IService) AopContext.currentProxy();
        // 如果是 CGLIB 动态代理,则可以使用原始对象类型接收,同时需要指定 proxyTargetClass = true
        // UserService service = (UserService) AopContext.currentProxy();
        service.m2();
    }

    @Override
    public void m2() {
        System.out.println("UserService.m2");
    }
}

手写 Spring AOP

基于前面的了解,我们手写一个简单的实现原理,其实就是创建一个 BeanPostProcesser。这个 BeanPostProcesser 通常是在初始化完成后去执行的。当然也不一定,也有可能在处理循环引用的时候提前就执行了。

定义接口和原始类

public interface IStudentService {
    void test1();
}

@Service
public class StudentServiceImpl implements IStudentService {

    @Override
    public void test1() {
        System.out.println("StudentServiceImpl.test1");
    }
}

定义代理 BeanPostProcessor

public class MyProxyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 基于 CGLIB 创建代理对象
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(this.getClass().getClassLoader());
        enhancer.setSuperclass(bean.getClass());
        enhancer.setCallback((InvocationHandler) (o, method, objects) -> {
            // 在此处编写前置操作
            System.out.println("Before");
            Object result = null;
            try {
                result = method.invoke(bean, objects);
            } catch (Exception e) {
                // 在此处编写异常操作
                System.out.println("Exception");
            }
            // 在此处编写后置操作
            System.out.println("After");
            // 可以在此处编写新的返回值并返回
            return result;
        });
        return enhancer.create();
    }
}

定义 AOP 开启注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyProxyBeanPostProcessor.class)
public @interface EnableMyProxy {

}

定义配置类

@EnableMyProxy
@Configuration
@ComponentScan("world.xuewei.proxy")
public class ProxyAppConfig {
    
}

定义测试类

public class ProxyTest {

    private ApplicationContext context;

    @Before
    public void before() {
        // 指定配置类,创建 Spring 工厂
        context = new AnnotationConfigApplicationContext(ProxyAppConfig.class);
    }

    /**
     * 测试 AOP
     */
    @Test
    public void test1() {
        StudentServiceImpl service = context.getBean("studentServiceImpl", StudentServiceImpl.class);
        service.test1();
    }
}

[外链图片转存中…(img-hLfwxitB-1721658404553)]

;