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的功能。