0
点赞
收藏
分享

微信扫一扫

SpringAOP从入门到源码分析大全,学好AOP这一篇就够了(二)


文章目录

  • ​​系列文档索引​​
  • ​​四、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 编程思想》


举报

相关推荐

0 条评论