文章目录
- 系列文档索引
- 四、Spring AOP的使用入门
- 1、激活AspectJ模块
- (1)注解激活
- (2)XML激活
- 2、创建 @AspectJ 代理(了解)
- (1)编程方式创建 @AspectJ 代理实例
- (2)XML 配置创建 AOP 代理
- (3)标准代理工厂 API
- 3、Pointcut 使用
- (1)Pointcut 指令与表达式
- (2)XML 配置 Pointcut
- (3)API 实现 Pointcut
- 4、拦截动作
- (1)注解方式实现
- (2)XML配置方式实现
- (3)API方式实现
- (4)同一个@Before执行顺序
- 未完待续
- 参考资料
系列文档索引
SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(一)SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(二)
SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(三)
SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(四)
四、Spring AOP的使用入门
1、激活AspectJ模块
激活AspectJ模块分两种方式,一种是使用注解方式,一种使用xml方式。
• 注解激活 - @EnableAspectJAutoProxy
• XML 配置 - <aop:aspectj-autoproxy/>
(1)注解激活
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Aspect // 声明为 Aspect 切面
@Configuration // Configuration class
@EnableAspectJAutoProxy // 激活 Aspect 注解自动代理
public class AspectJAnnotationDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AspectJAnnotationDemo.class);
context.refresh();
AspectJAnnotationDemo aspectJAnnotationDemo = context.getBean(AspectJAnnotationDemo.class);
System.out.println(aspectJAnnotationDemo.getClass());
context.close();
}
}
以上是使用注解激活的实例,使用@EnableAspectJAutoProxy即可激活Aspect注解自动代理。
该AspectJAnnotationDemo类使用@Configuration标注代表是一个配置类,Spring中的配置类都会被代理,默认是使用CGLIB代理。
@Aspect表示该类是一个切面类。
(2)XML激活
在xml文件中使用该标签,即可激活Aspect自动代理:
<aop:aspectj-autoproxy/>
该标签与注解的@EnableAspectJAutoProxy效果相同。
2、创建 @AspectJ 代理(了解)
(1)编程方式创建 @AspectJ 代理实例
实现类: org.springframework.aop.aspectj.annotation.AspectJProxyFactory
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import org.springframework.aop.framework.AopContext;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class AspectJAnnotationUsingAPIDemo {
public static void main(String[] args) {
// 通过创建一个 HashMap 缓存,作为被代理对象
Map<String, Object> cache = new HashMap<>();
// 创建 Proxy 工厂(AspectJ)
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(cache);
// 增加 Aspect 配置类,暂时不需要
// proxyFactory.addAspect(AspectConfiguration.class);
// 设置暴露代理对象到 AopContext
proxyFactory.setExposeProxy(true);
proxyFactory.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
if ("put".equals(method.getName()) && args.length == 2) {
Object proxy = AopContext.currentProxy();
System.out.printf("[MethodBeforeAdvice] 当前存放是 Key: %s , Value : %s ," +
"代理对象:%s\n", args[0], args[1], proxy);
}
}
});
// 添加 AfterReturningAdvice
proxyFactory.addAdvice(new AfterReturningAdvice() {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target)
throws Throwable {
if ("put".equals(method.getName()) && args.length == 2) {
System.out.printf("[AfterReturningAdvice] 当前存放是 Key: %s , 新存放的 Value : %s , 之前关联的 Value : %s\n ",
args[0], // key
args[1], // new value
returnValue // old value
);
}
}
});
// 通过代理对象存储数据
Map<String, Object> proxy = proxyFactory.getProxy();
proxy.put("1", "A");
proxy.put("1", "B");
System.out.println(cache.get("1"));
}
}
(2)XML 配置创建 AOP 代理
<aop:aspectj-autoproxy/>
<bean id="echoService" class="com.demo.DefaultEchoService"></bean>
<!--拦截器-->
<bean id="echoServiceMethodInterceptor"
class="com.demo.interceptor.EchoServiceMethodInterceptor"/>
<bean id="echoServiceProxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetName" value="echoService"/> <!--指定targetName为需要代理的bean的Id-->
<property name="interceptorNames"> <!--指定拦截器-->
<value>echoServiceMethodInterceptor</value>
</property>
</bean>
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
public class EchoServiceMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
System.out.println("拦截 EchoService 的方法:" + method);
return invocation.proceed();
}
}
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@Aspect // 声明为 Aspect 切面
@Configuration // Configuration class
public class ProxyFactoryBeanDemo {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:/META-INF/spring-aop-context.xml");
EchoService echoService = context.getBean("echoServiceProxyFactoryBean", EchoService.class);
System.out.println(echoService.echo("Hello,World"));
System.out.println(echoService.getClass());
context.close();
}
}
我们发现,这种方式与编程的方式创建代理是差不多的,只不过是对象的创建交给Spring来处理了。
(3)标准代理工厂 API
实现类 - org.springframework.aop.framework.ProxyFactory
上面XML配置的方式创建ProxyFactoryBean,更多用于Spring中的应用,而ProxyFactory更偏底层。
import org.springframework.aop.framework.ProxyFactory;
/**
* ProxyFactory使用示例
*/
public class ProxyFactoryDemo {
public static void main(String[] args) {
DefaultEchoService defaultEchoService = new DefaultEchoService();
// 注入目标对象(被代理)
ProxyFactory proxyFactory = new ProxyFactory(defaultEchoService);
//proxyFactory.setTargetClass(DefaultEchoService.class);
// 添加 Advice 实现 MethodInterceptor < Interceptor < Advice
proxyFactory.addAdvice(new EchoServiceMethodInterceptor());
// 获取代理对象
EchoService echoService = (EchoService) proxyFactory.getProxy();
System.out.println(echoService.echo("Hello,World"));
}
}
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
public class EchoServiceMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
System.out.println("拦截 EchoService 的方法:" + method);
return invocation.proceed();
}
}
3、Pointcut 使用
Spring中的Pointcut只支持方法级别的定义,也就是说只支持Bean的方法拦截。
Pointcut 只是一个筛选,并没有具体的动作。
Advice才是具体的动作,一个Pointcut 可以对应多个Advice。
(1)Pointcut 指令与表达式
Spring AOP 之 aspect表达式详解
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* Pointcut 示例
*/
@Configuration // Configuration class
@EnableAspectJAutoProxy // 激活 Aspect 注解自动代理
public class AspectJAnnotatedPointcutDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AspectJAnnotatedPointcutDemo.class,
AspectConfiguration.class);
context.refresh();
AspectJAnnotatedPointcutDemo aspectJAnnotationDemo = context.getBean(AspectJAnnotatedPointcutDemo.class);
aspectJAnnotationDemo.execute();
context.close();
}
public void execute() {
System.out.println("execute()...");
}
}
/**
* Aspect 配置类
*/
@Aspect
public class AspectConfiguration {
@Pointcut("execution(public * *(..))") // 匹配 Join Point
private void anyPublicMethod() { // 方法名即 Pointcut 名
System.out.println("@Pointcut at any public method."); // 方法通常设置为空的,不会有具体的动作
}
@Before("anyPublicMethod()") // Join Point 拦截动作
public void beforeAnyPublicMethod() {
System.out.println("@Before any public method.");
}
}
Pointcut定义在切面类的私有方法上,该方法没有任何的动作,只是筛选要切入的方法。该方法名即为Pointcut名。
(2)XML 配置 Pointcut
<!--定义配置类-->
<bean id="aspectXmlConfig" class="com.demo.AspectXmlConfig"/>
<aop:config>
<aop:aspect id="AspectXmlConfig" ref="aspectXmlConfig"><!--引用配置类-->
<aop:pointcut id="anyPublicMethod" expression="execution(public * *(..))"/><!--Pointcut-->
<aop:before method="beforeAnyPublicMethod" pointcut-ref="anyPublicMethod"/><!--配置类的方法-->
</aop:aspect>
</aop:config>
public class AspectXmlConfig {
public void beforeAnyPublicMethod() {
System.out.println("@Before any public method.");
}
}
(3)API 实现 Pointcut
核心 API - org.springframework.aop.Pointcut
• org.springframework.aop.ClassFilter
• org.springframework.aop.MethodMatcher
适配实现 - DefaultPointcutAdvisor
public static void main(String[] args) {
EchoServicePointcut echoServicePointcut = new EchoServicePointcut("echo", EchoService.class);
// 将 Pointcut 适配成 Advisor
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(echoServicePointcut, new EchoServiceMethodInterceptor());
DefaultEchoService defaultEchoService = new DefaultEchoService();
ProxyFactory proxyFactory = new ProxyFactory(defaultEchoService);
// 添加 Advisor
proxyFactory.addAdvisor(advisor);
// 获取代理对象
EchoService echoService = (EchoService) proxyFactory.getProxy();
System.out.println(echoService.echo("Hello,World"));
}
public class EchoServicePointcut extends StaticMethodMatcherPointcut {
private String methodName;
private Class targetClass;
public EchoServicePointcut(String methodName, Class targetClass) {
this.methodName = methodName;
this.targetClass = targetClass;
}
@Override
public boolean matches(Method method, Class<?> targetClass) {
return Objects.equals(methodName, method.getName())
&& this.targetClass.isAssignableFrom(targetClass);
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class getTargetClass() {
return targetClass;
}
public void setTargetClass(Class targetClass) {
this.targetClass = targetClass;
}
}
public class EchoServiceMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
System.out.println("拦截 EchoService 的方法:" + method);
return invocation.proceed();
}
}
4、拦截动作
Around环绕动作。
Before前置动作。
After后置动作:
• 方法返回后:AfterReturning
• 异常发生后:AfterThrowing
• finally 执行:After
(1)注解方式实现
@Aspect
@Order
public class AspectConfiguration {
@Pointcut("execution(public * *(..))") // 匹配 Join Point
private void anyPublicMethod() { // 方法名即 Pointcut 名
System.out.println("@Pointcut at any public method.");
}
@Around("anyPublicMethod()") // Join Point 拦截动作
public Object aroundAnyPublicMethod(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("@Around any public method.");
return pjp.proceed();
}
@Before("anyPublicMethod()") // Join Point 拦截动作
public void beforeAnyPublicMethod() {
Random random = new Random();
if (random.nextBoolean()) {
throw new RuntimeException("For Purpose.");
}
System.out.println("@Before any public method.");
}
@After("anyPublicMethod()")
public void finalizeAnyPublicMethod() {
System.out.println("@After any public method.");
}
@AfterReturning("anyPublicMethod()")
// AspectJAfterReturningAdvice is AfterReturningAdvice
// 一个 AfterReturningAdviceInterceptor 关联一个 AfterReturningAdvice
// Spring 封装 AfterReturningAdvice -> AfterReturningAdviceInterceptor
// AfterReturningAdviceInterceptor is MethodInterceptor
// AfterReturningAdviceInterceptor
// -> AspectJAfterReturningAdvice
// -> AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs
public void afterAnyPublicMethod() {
System.out.println("@AfterReturning any public method.");
}
@AfterThrowing("anyPublicMethod()")
public void afterThrowingAnyPublicMethod() {
System.out.println("@AfterThrowing any public method");
}
public String toString() {
return "AspectConfiguration";
}
private int getValue() {
return 0;
}
}
(2)XML配置方式实现
<aop:config>
<!-- <aop:pointcut id="allPointcut" expression="execution(* * *(..))"/>-->
<aop:aspect id="AspectXmlConfig" ref="aspectXmlConfig">
<aop:pointcut id="anyPublicMethod" expression="execution(public * *(..))"/>
<aop:around method="aroundAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
<aop:around method="aroundAnyPublicMethod" pointcut="execution(public * *(..))"/>
<aop:before method="beforeAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
<aop:before method="beforeAnyPublicMethod" pointcut="execution(public * *(..))"/>
<aop:after method="finalizeAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
<aop:after-returning method="afterAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
<aop:after-throwing method="afterThrowingAnyPublicMethod" pointcut-ref="anyPublicMethod"/>
</aop:aspect>
</aop:config>
public class AspectXmlConfig {
public Object aroundAnyPublicMethod(ProceedingJoinPoint pjp) throws Throwable {
Random random = new Random();
if (random.nextBoolean()) {
throw new RuntimeException("For Purpose from XML configuration.");
}
System.out.println("@Around any public method : " + pjp.getSignature());
return pjp.proceed();
}
public void beforeAnyPublicMethod() {
System.out.println("@Before any public method.");
}
public void finalizeAnyPublicMethod() {
System.out.println("@After any public method.");
}
public void afterAnyPublicMethod() {
System.out.println("@AfterReturning any public method.");
}
public void afterThrowingAnyPublicMethod() {
System.out.println("@AfterThrowing any public method.");
}
}
(3)API方式实现
为什么 Spring AOP 不需要设计 Around Advice
• AspectJ @Around 与 org.aspectj.lang.ProceedingJoinPoint 配合执行被代理方法
• ProceedingJoinPoint#proceed() 方法类似于 Java Method#invoke(Object,Object…)
• Spring AOP 底层 API ProxyFactory 可通过 addAdvice 方法与 Advice 实现关联
• 接口 Advice 是 Interceptor 的父亲接口,而接口 MethodInterceptor 又扩展了 Interceptor
• MethodInterceptor 的invoke 方法参数 MethodInvocation 与 ProceedingJoinPoint 类似
API 实现 Before Advice
核心接口 - org.springframework.aop.BeforeAdvice
类型:标记接口,与 org.aopalliance.aop.Advice 类似
方法 JoinPoint 扩展 - org.springframework.aop.MethodBeforeAdvice
接受对象 - org.springframework.aop.framework.AdvisedSupport
• 基础实现类 - org.springframework.aop.framework.ProxyCreatorSupport
• 常见实现类:org.springframework.aop.framework.ProxyFactory;org.springframework.aop.framework.ProxyFactoryBean;org.springframework.aop.aspectj.annotation.AspectJProxyFactory
API 实现三种 After Advice
核心接口 - org.springframework.aop.AfterAdvice
类型:标记接口,与 org.aopalliance.aop.Advice 类似
扩展
• org.springframework.aop.AfterReturningAdvice
• org.springframework.aop.ThrowsAdvice
接受对象 - org.springframework.aop.framework.AdvisedSupport
• 基础实现类 - org.springframework.aop.framework.ProxyCreatorSupport
• 常见实现类:org.springframework.aop.framework.ProxyFactory;org.springframework.aop.framework.ProxyFactoryBean;org.springframework.aop.aspectj.annotation.AspectJProxyFactory
// 通过创建一个 HashMap 缓存,作为被代理对象
Map<String, Object> cache = new HashMap<>();
// 创建 Proxy 工厂(AspectJ)
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(cache);
// 增加 Aspect 配置类,此处不需要
// proxyFactory.addAspect(AspectConfiguration.class);
// 设置暴露代理对象到 AopContext
proxyFactory.setExposeProxy(true);
proxyFactory.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
if ("put".equals(method.getName()) && args.length == 2) {
Object proxy = AopContext.currentProxy();
System.out.printf("[MethodBeforeAdvice] 当前存放是 Key: %s , Value : %s ," +
"代理对象:%s\n", args[0], args[1], proxy);
}
}
});
// 添加 AfterReturningAdvice
proxyFactory.addAdvice(new AfterReturningAdvice() {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target)
throws Throwable {
if ("put".equals(method.getName()) && args.length == 2) {
System.out.printf("[AfterReturningAdvice] 当前存放是 Key: %s , 新存放的 Value : %s , 之前关联的 Value : %s\n ",
args[0], // key
args[1], // new value
returnValue // old value
);
}
}
});
// 通过代理对象存储数据
Map<String, Object> proxy = proxyFactory.getProxy();
proxy.put("1", "A");
proxy.put("1", "B");
System.out.println(cache.get("1"));
(4)同一个@Before执行顺序
注解驱动:使用@Order注解标注切面类,同一类中的顺序无法保证。
XML驱动:按照先后顺序执行。
未完待续
参考资料
极客时间《小马哥讲 Spring AOP 编程思想》