Spring 之 AOP 原理详解

        Spring 是一个流行的 Java 企业应用程序开发框架。其中的 AOP(面向切面编程)是 Spring 框架中的一个核心概念。本文将介绍 Spring AOP 的底层实现原理,并通过源代码解析来详细阐述其实现过程。

什么是AOP?

         AOP是一种编程范式,用于在不修改原始代码的情况下向现有应用程序添加新功能。这种编程方式将应用程序分成许多独立的部分,称为切面。这些切面可以在应用程序的不同位置进行编写和维护,从而提高了应用程序的可重用性和可维护性

         AOP主要用于实现横切关注点(Cross-Cutting Concerns),例如日志记录、性能监测、事务管理等。通过AOP,我们可以将这些关注点与应用程序的其他部分分离开来,从而使应用程序更加模块化和易于维护。

实现原理

Spring AOP 的实现原理是基于动态代理字节码操作的。

        在编译时, Spring 会使用 AspectJ 编译器将切面代码编译成字节码文件。在运行时, Spring 会使用 Java 动态代理或 CGLIB 代理生成代理类,这些代理类会在目标对象方法执行前后插入切面代码,从而实现AOP的功能。

         Spring AOP 可以使用两种代理方式:JDK动态代理和 CGLIB 代理。如果目标对象实现了至少一个接口,则使用JDK动态代理;否则,使用 CGLIB 代理。下面分别介绍这两种代理方式的实现原理。

JDK动态代理

        JDK 动态代理是 Java 自带的动态代理实现方式。使用JDK动态代理时,需要目标对象实现至少一个接口。JDK 动态代理会在运行时生成一个实现了目标对象接口的代理类,该代理类会在目标对象方法执行前后插入切面代码。

下面是JDK动态代理的实现代码:

public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {

    private final AdvisedSupport advised;

    public JdkDynamicAopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy() {
        return Proxy.newProxyInstance(
                getClass().getClassLoader(),
                advised.getTargetSource().getInterfaces(),
                this
        );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
        MethodInvocation methodInvocation = new ReflectiveMethodInvocation(
                advised.getTargetSource().getTarget(),
                method,
                args,
                methodInterceptor,
                advised.getTargetSource().getTargetClass()
        );
        return methodInvocation.proceed();
    }
}
复制代码

        在该代码中,JdkDynamicAopProxy 类实现了 AopProxy 和 InvocationHandler 接口。getProxy 方法返回一个代理对象,该代理对象实现了目标对象实现的所有接口。invoke 方法用于执行代理方法,该方法会在目标对象方法执行前后插入切面代码。

CGLIB 代理

        CGLIB 代理是一个基于字节码操作的代理方式,它可以为没有实现接口的类创建代理对象。CGLIB 代理会在运行时生成一个目标对象的子类,并覆盖其中的方法,以实现AOP的功能。

下面是 CGLIB 代理的实现代码:

public class CglibAopProxy implements AopProxy {

    private final AdvisedSupport advised;

    public CglibAopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(advised.getTargetSource().getTargetClass());
        enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
        return enhancer.create();
    }

    private static class DynamicAdvisedInterceptor implements MethodInterceptor {

        private final AdvisedSupport advised;

        public DynamicAdvisedInterceptor(AdvisedSupport advised) {
            this.advised = advised;
        }

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            MethodInvocation methodInvocation = new CglibMethodInvocation(
                    advised.getTargetSource().getTarget(),
                    method,
                    args,
                    proxy,
                    advised.getMethodInterceptor(),
                    advised.getTargetSource().getTargetClass()
            );
            return methodInvocation.proceed();
        }
    }
}

复制代码

        在该代码中,CglibAopProxy 类实现了 AopProxy 接口。getProxy 方法返回一个代理对象,该代理对象是目标对象的子类,并覆盖了其中的方法。DynamicAdvisedInterceptor 类实现了 MethodInterceptor 接口,用于在目标对象方法执行前后插入切面代码。

AOP使用示例

        在了解了 Spring AOP的实现原理后,我们来看一下 Spring AOP的源码实现。Spring AOP的源码位于org.Springframework.aop包下,其中涉及到的类有:

Advised:一个包含切面信息的接口,用于描述切面的配置信息。

AdvisedSupport:Advised接口的默认实现类,包含了切面的配置信息。

AopProxy:AOP代理的接口,用于获取代理对象。

CglibAopProxy: CGLIB 代理的实现类。

JdkDynamicAopProxy:JDK动态代理的实现类。

MethodInvocation:方法调用的接口,用于封装目标对象方法的调用过程。

ReflectiveMethodInvocation:MethodInvocation接口的默认实现类,用于调用目标对象方法。

MethodInterceptor:方法拦截器的接口,用于实现切面的具体逻辑。

ProxyFactory:代理工厂,用于创建代理对象。

下面是一个使用 Spring AOP的示例代码:

@Service
public class UserServiceImpl implements UserService {

    @Override
    @LogAnnotation
    public void addUser(String username, String password) {
        System.out.println("addUser");
    }
}

复制代码

在该代码中,UserServiceImpl 类实现了 UserService 接口,并在 addUser 方法上添加了@LogAnnotation 注解。@LogAnnotation 注解是一个自定义注解,用于标记需要记录日志的方法。

下面是 @LogAnnotation 注解的定义:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
}
复制代码

在使用 Spring AOP时,我们需要定义一个切面类,用于实现@LogAnnotation注解的具体逻辑。下面是一个定义了@LogAnnotation注解的切面类:

@Aspect
@Component
public class LogAspect {

    @Pointcut("@annotation(com.example.demo.annotation.LogAnnotation)")
    public void logPointcut() {}

    @Before("logPointcut()")
    public void before(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getName();
        System.out.println("before " + methodName);
    }

    @After("logPointcut()")
    public void after(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getName();
        System.out.println("after " + methodName);
    }
}
复制代码

        在该代码中,LogAspect 类使用 @Aspect 注解标记为切面类,并使用 @Component 注解将其注册为 Spring 的 Bean。

logPointcut 方法使用 @Pointcut 注解定义了切点,该切点匹配所有标记了@LogAnnotation 注解的方法。

before 方法和 after 方法分别使用 @Before 和 @After 注解定义了前置通知和后置通知。在 before 方法和 after 方法中,我们可以编写具体的日志记录逻辑。

下面是创建代理对象的代码:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl userService = applicationContext.getBean(UserServiceImpl.class);
userService.addUser("test", "test");
复制代码

在该代码中,我们使用 ApplicationContext 从配置文件中获取了 UserServiceImpl 的 Bean,并调用了 addUser 方法。由于 addUser 方法标记了 @LogAnnotation 注解,因此 Spring 会自动将 LogAspect 类中定义的切面逻辑插入到该方法中。

总结

本文介绍了 Spring AOP 的实现原理,并通过源代码解析详细阐述了其实现过程。在使用 Spring AOP时,我们需要定义切面类,并为需要实现AOP的方法添加注解。 Spring 会自动将切面逻辑插入到这些方法中,从而实现AOP的功能。

 

你可能感兴趣的:(spring,java,spring,boot)