前言介绍
附录:Spring源码学习专栏
在前面章节的学习中,我们对 Spring 框架的 IOC 实现源码有了一定的了解,接着本文继续学习 Springframework 一个核心的技术点 AOP 技术。
在学习 Spring AOP 源码之前,您是否对 AOP 有足够熟悉的理解?在对应用都不熟悉之前就去学习源码,肯定是很难理解的,所以本文先不描述源码的实现,先通过本篇博客了解熟悉 Spring AOP,然后再学习源码
1、什么是 AOP 技术?
引用Spring官网对 AOP 技术的概述:
Aspect-Oriented Programming (AOP) complements Object-Oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns such as transaction management that cut across multiple types and objects. (Such concerns are often termed crosscutting concerns in AOP literature.)
挑重点来说,所谓 AOP(Aspect-Oriented Programming)也即面向方面的编程,是指通过跨领域关注点的分离来实现模块化的一种面向对象技术。
2、AOP 的本质目的
AOP 本质:在不改变原有业务逻辑的情况下增强横切逻辑,这个横切逻辑可以是权限校验逻辑、日志监控、事务控制等等
AOP 相关知识详情可以参考:Spring AOP官方文档
3、AOP 的相关术语
看了前面这些理论,您可能不是很理解,所以引用国外网站的图例进行说明 AOP 概念,图来自链接
综上所述,其实所谓的目的其实只是要锁定在某个切入点(Pointcut)织入(Weaving)特定的增强逻辑(Advice)
4、Spring AOP 和 AspectJ
有了前面对 AOP 概念的概述之后,我们能够大致理解 AOP 了,不过本文还是要理理 Spring AOP 和 AspectJ 的关系
Spring官网给指出了 Spring AOP 和 AspectJ 的关系,官网明确指出其立场,表明 Spring AOP 不会和 AspectJ 项目竞争哪个项目能提供更成熟的 AOP 解决方案,Spring AOP 和 AspectJ 是一种互补的关系,Spring AOP 无缝结合了 IOC 和 AspectJ,从而使 Spring AOP 能够符合大部分的 AOP 需求,这是一种能提供代理对象的模式
从官网也可以知道了 Spring AOP 和 AspectJ 的大致关系,其实这是两种 AOP 的实现技术,Spring AOP 是集成了 AspectJ 部分功能,同时结合 IOC 实现的,AspectJ 则是一种比较完善成熟的 AOP 解决方案,接着本文做下简单对比
Spring AOP
Spring AOP 是 SpringFramework 的组件,属于 Springframework 的一个比较核心的功能,Spring AOP 是结合了 AspectJ 和 IOC 实现的,提供了 AspectJ 的功能
Spring AOP 致力于解决的是企业级开发中最普遍的 AOP 需求(方法织入),Spring AOP 不会和 AspectJ 竞争
Spring AOP 只能作用于 Spring 容器中的 Bean
性能方面,Spring AOP 是在运行时进行动态织入的,所以性能比不上 AspectJ
Spring AOP 使用动态代理的方式进行方法织入,有两种动态代理方法,一种是 CGLIB,另外一种是 JDK 提供的动态代理
引用https://www.baeldung.com/spring-aop-vs-aspectj的图进行说明
AspectJ
AspectJ 来自 Eclipse 的开源项目,链接:https://www.eclipse.org/aspectj
AspectJ 是一种比较成熟的 AOP 解决方案,能够提供比 Spring AOP 更多的 AOP 功能
AspectJ 的方法织入属于静态织入,它的织入时机可以是:compile-time(编译期)、post-compile(编译后)、load-time(JVM 类加载器加载时候)
AspectJ 在编译时进行方法织入,所以性能比 Spring AOP 好
ok,前面已经简单列举了 Spring AOP 和 AspectJ 的主要不同,现在可以用表格列举出不同点对比,表格参考自国外网站:
补充,AspectJ 静态织入时机:
compile-time weaving:编译期织入,在编译时候就直接进行方法织入,直接编译出包含织入代码的 .class 文件
post-compile weaving:编译后织入,也可以称之为二进制织入,它将 Advice 织入于编织后现有的类文件和 JAR 文件
Load-time weaving(LTW):指的是在加载类的时候进行织入,与以前的二进制编织完全一样,不同之处在于编织被推迟到类加载器将类文件加载到 JVM 的过程
5、Spring 中 AOP 代理选择
在前面知识,我们知道 Spring AOP 是使用动态代理技术实现 Spring AOP 中的代理选择,方法织入实现有两种方法,一种是 JDK 动态代理,一种是 CGLIB
6、实验环境准备参考
学习了前面的理论知识之后,现在可以通过例子进行实践,实践之前,您需要如下的环境准备,实验环境参考:
7、Spring AOP 实现方式
在 Spring AOP 中,主要提供了三种配置方式:
Spring1.2 基于接口的配置:Spring 最早的 AOP 实现是基于 Spring 提供的 AOP 接口实现的,通过实现接口,进行 Advice 逻辑代码编写等等
Spring2.0+ schema-based 配置 :Spring2.0 之后,提供了 schema-based 配置,也就是 xml 类型的配置,使用命名空间<aop>
Spring2.0+ @Aspect 配置:Spring2.0 之后,也提供了@Aspect
这种方法,@Aspect
是用 AspectJ 的 jar,但是实现是 Spring AOP 自己实现的
8、Spring AOP 例子参考
前面介绍了 Spring AOP 实现的三种方式,接着本文通过代码例子进行验证:
maven 配置
<properties>
<springframework.version>5.0.19.RELEASE</springframework.version>
<aspectj.version>1.9.4</aspectj.version>
</properties>
<dependencies>
<!-- Spring aop配置-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${springframework.version}</version>
</dependency>
<!-- 本文的测试类需要 ioc配置-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springframework.version}</version>
</dependency>
<!-- @Aspect才需要加上-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
复制代码
8.1、Spring1.2 基于接口的配置
8.1.1、基础类编写
User.java
package com.example.spring.aop.bean;
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
复制代码
UserService .java:
package com.example.spring.aop.service;
import com.example.spring.aop.bean.User;
/**
* <pre>
* UserService
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2020/11/20 18:02 修改内容:
* </pre>
*/
public interface UserService {
User addUser(User user);
User getUser();
}
复制代码
UserServiceImpl .java
package com.example.spring.aop.service.impl;
import com.example.spring.aop.bean.User;
import com.example.spring.aop.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
/**
* <pre>
* UserServiceImpl
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2020/11/20 17:57 修改内容:
* </pre>
*/
@Service
public class UserServiceImpl implements UserService {
private static User user = null;
@Override
public User addUser(User userDto) {
user = new User();
BeanUtils.copyProperties(userDto,user);
return user;
}
@Override
public User getUser() {
return user;
}
}
复制代码
8.1.2、使用 Advice 接口
LogMethodBeforeAdvice .java
package com.example.spring.aop.core.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* <pre>
* LogMethodBeforeAdvice
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2020/11/20 17:38 修改内容:
* </pre>
*/
public class LogMethodBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(String.format("执行方法:%s,参数列表:%s", method.getName(), Arrays.toString(args) ));
}
}
复制代码
LogAfterReturningAdvice .java
package com.example.spring.aop.core.advice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
/**
* <pre>
* LogAfterReturningAdvice
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2020/11/20 17:41 修改内容:
* </pre>
*/
public class LogAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(String.format("方法返回:%s", returnValue ));
}
}
复制代码
spring_interfaces_config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 具体业务实现类(target Object)-->
<bean id="userServiceTarget" class="com.example.spring.aop.service.impl.UserServiceImpl"></bean>
<!-- 实现MethodBeforeAdvice-->
<bean id="logMethodBeforeAdvice" class="com.example.spring.aop.core.advice.LogMethodBeforeAdvice"></bean>
<!-- 实现AfterReturningAdvice-->
<bean id = "logAfterReturningAdvice" class="com.example.spring.aop.core.advice.LogAfterReturningAdvice"></bean>
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 配置代理接口 proxy-->
<property name="proxyInterfaces">
<list>
<value>com.example.spring.aop.service.UserService</value>
</list>
</property>
<!-- 配置目标对象,也就是被代理类,具体业务实现类-->
<property name="target" ref="userServiceTarget"></property>
<!-- 配置拦截器,可以配置advice、advisor、interceptor -->
<property name="interceptorNames">
<list>
<value>logMethodBeforeAdvice</value>
<value>logAfterReturningAdvice</value>
</list>
</property>
</bean>
</beans>
复制代码
TestApplication.java:
package com.example.spring.aop;
import com.example.spring.aop.bean.User;
import com.example.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestApplication {
public static void testAopProxy() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("classpath:spring_interfaces_config.xml");
UserService userService = (UserService) ioc.getBean("userServiceProxy");
User userDto = new User();
userDto.setUsername("tom");
userDto.setPassword("11");
userService.addUser(userDto);
System.out.println(String.format("用户数据打印:%s",userService.getUser().toString()));
}
public static void main(String[] args) {
testAopProxy();
}
}
复制代码
执行方法:addUser,参数列表:[User{username='tom', password='11'}]
方法返回:User{username='tom', password='11'}
执行方法:getUser,参数列表:[]
方法返回:User{username='tom', password='11'}
用户数据打印:User{username='tom', password='11'}
8.1.3、使用 Advisor 接口
NameMatchMethodPointcutAdvisor 使用
定义一个只会拦截查询方法的 Advisor,修改配置:
<!-- 定义一个只会拦截查询方法的Advisor -->
<bean id="logOnlyObtainQueryAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<!-- 定义Advice类 -->
<property name="advice" ref="logMethodBeforeAdvice"></property>
<!-- 只有查询方法才会被拦截 -->
<property name="mappedNames" value="getUser"></property>
</bean>
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 配置代理接口 proxy-->
<property name="proxyInterfaces">
<list>
<value>com.example.spring.aop.service.UserService</value>
</list>
</property>
<!-- 配置目标对象,也就是被代理类,具体业务实现类-->
<property name="target" ref="userServiceTarget"></property>
<!-- 配置拦截器,可以配置advice、advisor、interceptor -->
<property name="interceptorNames">
<list>
<value>logOnlyObtainQueryAdvisor</value>
</list>
</property>
</bean>
复制代码
执行方法:getUser,参数列表:[]
用户数据打印:User{username='tom', password='11'}
RegexpMethodPointcutAdvisor 使用
前面介绍了NameMatchMethodPointcutAdvisor
,不过不够通用,所以 Spring aop 还提供了RegexpMethodPointcutAdvisor
,可以支持正则表达式
<!-- 定义支持正则匹配的Advisor,只拦截查询方法-->
<bean id="regexpMethodAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="logMethodBeforeAdvice"></property>
<property name="pattern" value="com.example.spring.aop.*.service.*.get.*"></property>
</bean>
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 配置代理接口 proxy-->
<property name="proxyInterfaces">
<list>
<value>com.example.spring.aop.service.UserService</value>
</list>
</property>
<!-- 配置目标对象,也就是被代理类,具体业务实现类-->
<property name="target" ref="userServiceTarget"></property>
<!-- 配置拦截器,可以配置advice、advisor、interceptor -->
<property name="interceptorNames">
<list>
<value>regexpMethodAdvisor</value>
</list>
</property>
</bean>
复制代码
执行方法:getUser,参数列表:[]
用户数据打印:User{username='tom', password='11'}
8.1.4、Interceptor 接口使用
TestMethodInterceptor .java:
package com.example.spring.aop.core.interceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* <pre>
* TestMethodInterceptor
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2020/11/23 10:28 修改内容:
* </pre>
*/
public class TestMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println(String.format("方法调用前(before method invoke) :%s",methodInvocation));
Object implObj = methodInvocation.proceed();
System.out.println(String.format("方法调用后(after method invoke) :%s",implObj));
return implObj;
}
}
复制代码
修改配置文件:
<!-- 定义MethodInterceptor -->
<bean id="methodInterceptor" class="com.example.spring.aop.core.interceptor.TestMethodInterceptor"></bean>
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 配置代理接口 proxy-->
<property name="proxyInterfaces">
<list>
<value>com.example.spring.aop.service.UserService</value>
</list>
</property>
<!-- 配置目标对象,也就是被代理类,具体业务实现类-->
<property name="target" ref="userServiceTarget"></property>
<!-- 配置拦截器,可以配置advice、advisor、interceptor -->
<property name="interceptorNames">
<list>
<value>logMethodBeforeAdvice</value>
<value>logAfterReturningAdvice</value>
<value>methodInterceptor</value>
</list>
</property>
</bean>
复制代码
挑 addUser 方法的日志信息:
方法调用前(before method invoke) :ReflectiveMethodInvocation: public abstract com.example.spring.aop.bean.User com.example.spring.aop.service.UserService.addUser(com.example.spring.aop.bean.User); target is of class [com.example.spring.aop.service.impl.UserServiceImpl]
方法调用后(after method invoke) :User{username='tom', password='11'}
8.1.5、beanNameAutoProxy 使用
前面介绍了ProxyFactoryBean
配置对应的业务代理进行调用,不过不够灵活,所以 Spring 中还提供了beanNameAutoProxy
,这种方式是自动匹配 beanName id,不需要每个业务都配对应的 proxy 进行代理
新建一个新的配置文件,spring_beanNameAutoProxy_config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 具体业务实现类(target Object)-->
<bean id="userServiceTarget" class="com.example.spring.aop.service.impl.UserServiceImpl"></bean>
<!-- 实现MethodBeforeAdvice-->
<bean id="logMethodBeforeAdvice" class="com.example.spring.aop.core.advice.LogMethodBeforeAdvice"></bean>
<!-- 实现AfterReturningAdvice-->
<bean id = "logAfterReturningAdvice" class="com.example.spring.aop.core.advice.LogAfterReturningAdvice"></bean>
<!-- 定义MethodInterceptor -->
<bean id="methodInterceptor" class="com.example.spring.aop.core.interceptor.TestMethodInterceptor"></bean>
<!-- 定义BeanNameAutoProxyCreator -->
<bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<!-- interceptorNames可以配置advice、advisor、interceptor-->
<property name="interceptorNames">
<list>
<value>logMethodBeforeAdvice</value>
<value>logAfterReturningAdvice</value>
<value>methodInterceptor</value>
</list>
</property>
<!-- 注意:beanNames这里是拦截对应的实现类bean id,eg:userServiceTarget-->
<property name="beanNames" value="*ServiceTarget"></property>
</bean>
</beans>
复制代码
TestApplication.java:
package com.example.spring.aop;
import com.example.spring.aop.bean.User;
import com.example.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestApplication {
public static void testBeanNameAutoProxy() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("classpath:spring_beanNameAutoProxy_config.xml");
UserService userService = ioc.getBean(UserService.class);
User userDto = new User();
userDto.setUsername("tom");
userDto.setPassword("11");
userService.addUser(userDto);
System.out.println(String.format("用户数据打印:%s",userService.getUser().toString()));
}
public static void main(String[] args) {
// BeanNameAutoProxyCreator
testBeanNameAutoProxy();
}
}
复制代码
8.2、Spring2.0+ @Aspect 配置
@Aspect
这种方式是比较常用的,pom 需要加上aspectjweaver
配置,spring aop 引用了 aspectJ 的 api,但是实现是 spring 自己进行实现拓展的
8.2.1、启用 @AspectJ 支持
注解方式,使用@EnableAspectJAutoProxy
开启
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
复制代码
xml 方式,可以使用<aop:aspectj-autoproxy/>
开启
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
</beans>
复制代码
8.2.2、声明方面
xml 方式:
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of aspect here as normal -->
</bean>
复制代码
java 方式,使用注解@Aspect
:
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
复制代码
8.2.3、声明切入点
切入点(Pointcut)的类型,Spring官网给出了比较详情的介绍:
在官网的建议是声明一个通用的SystemArchitecture
:
package com.xyz.someapp;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemArchitecture {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.someapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.someapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() {}
/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.someapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() {}
/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then
* the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
* could be used instead.
*
* Alternatively, you can write the expression using the 'bean'
* PCD, like so "bean(*Service)". (This assumes that you have
* named your Spring service beans in a consistent fashion.)
*/
@Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
public void businessService() {}
/**
* A data access operation is the execution of any method defined on a
* dao interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
复制代码
因为官网的介绍比较详细,所以本博客只挑部分比较重要的进行介绍:
execution
execution:执行,这是最基本的切入点类型:
// 匹配UserService里的任何方法
@Pointcut("execution(* com.example.spring.aop.service.UserService.*(..))")
public void regexpExecution(){}
复制代码
ps:第一个*
表示匹配任何返回值,第二个*
表示匹配任何方法,(..)
表示匹配任何数量的方法参数
eg:execution(* *..find*(Long,..))
用于匹配方法名为 find...,而第一个参数是 long 类型的
@Pointcut("within(com.example.spring.aop..*) && execution(* *..find*(Long,..))")
public void regexpExecutionByMethodName(){}
复制代码
within
within:表示服务包中的任何连接点(仅在 Spring AOP 中执行方法)
@Pointcut("within(com.example.spring.aop..*)")
复制代码
this 和 target
前者在 Spring AOP 创建基于 CGLIB 的代理时起作用,而后者在创建基于 JDK 的代理时使用
如下实例代码:
public class UserServiceImpl implements UserService {
//...
}
复制代码
对于 UserService,使用target
,这种情况是基于 CGLIB 的代理
@Pointcut("target(com.example.spring.aop.service.UserService)")
复制代码
对于 UserService,使用this
,这种情况是基于 JDK 的代理
@Pointcut("this(com.example.spring.aop.service.impl.UserServiceImpl)")
复制代码
args
限制匹配点(参数是给定类型的实例)的连接点(使用 Spring AOP 时方法的执行)
@target
@target
不要和 target 混淆了,其中类的执行的对象的具有给定类型的注释
@Pointcut("@target(org.springframework.stereotype.Repository)")
复制代码
@args
@args
其中传递的实际参数的运行时类型具有给定类型的注释
package com.example.spring.aop.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
// 作用于类
@Target(ElementType.TYPE)
public @interface Entity {
}
复制代码
@Pointcut("within(com.example.spring.aop..*) && @args(com.example.spring.aop.annotation.Entity)")
public void argsMethod(){}
复制代码
@within
将匹配限制为具有给定注释的类型内的连接点
Pointcut("@within(org.springframework.stereotype.Repository)")
复制代码
等效于:
@Pointcut("within(@org.springframework.stereotype.Repository *)")
复制代码
@annotation
这个@annotation
是用于匹配注解的,比较常用,我们可以自己写个注解类:
package com.example.spring.aop.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <pre>
* EnableLog
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2020/11/23 18:27 修改内容:
* </pre>
*/
@Retention(RetentionPolicy.RUNTIME)
// 作用于方法
@Target(ElementType.METHOD)
public @interface EnableLog {
}
复制代码
然后,在对应方法加上@EnableLog
的方法都能被拦截到:
@Pointcut("within(com.example.spring.aop..*) && @annotation(com.example.spring.aop.annotation.EnableLog)")
public void annotationMethod(){}
复制代码
组合表达式
组合表达式可以使用 &&,||和!进行组合
@Pointcut("within(com.example.spring.aop..*) && execution(public String com.example.spring.aop.service.UserService.*(..))")
复制代码
8.2.4、声明 Advice 类型
Advice 类型,Spring官网也有比较详细的介绍:
归纳一下通知类型:
前置通知(@Before
):在方法执行之前,使用 @Before 注释声明
后置通知(@AfterReturning
):当匹配的方法执行正常返回时运行建议。它使用@AfterReturning
注释声明
异常通知(@AfterThrowing
):程序抛出异常后执行,不抛异常不会调用,使用@AfterThrowing
注释声明
最后通知(@After
):匹配的方法执行退出,如果是有 try...catch,一般是在 finally 执行完成后,使用@After
注释声明
环绕通知(@Around
):围绕建议在匹配的方法执行过程中“围绕”运行。它有机会在该方法执行之前和之后进行工作,并确定该方法何时,如何以及什至完全可以执行,使用@Around
注释声明
package com.example.spring.aop.config;
import com.example.spring.aop.service.UserService;
import com.example.spring.aop.service.impl.UserServiceImpl;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
* <pre>
* SpringAspectJConfiguration
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2020/11/24 10:52 修改内容:
* </pre>
*/
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@Aspect
public class SpringAspectJConfiguration {
@Bean
public UserService userService(){
return new UserServiceImpl();
}
private ThreadLocal<SimpleDateFormat> simpleDateFormat =new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue() {
//return super.initialValue();
return new SimpleDateFormat("[yyyy-mm-dd hh:mm:ss:SSS]");
}
};
//---------------------------------------------------------------------
// Types of pointcut
//---------------------------------------------------------------------
@Pointcut("within(com.example.spring.aop..*) && execution(public String com.example.spring.aop.service.UserService.*(..))")
public void regexpExecution(){}
@Pointcut("within(com.example.spring.aop..*) && execution(* *..find*(Long,..))")
public void regexpExecutionByMethodName(){}
@Pointcut("within(com.example.spring.aop..*) && target(com.example.spring.aop.service.UserService)")
public void targetInterface(){}
@Pointcut("within(com.example.spring.aop..*) && @args(com.example.spring.aop.annotation.Entity)")
public void argsMethod(){}
@Pointcut("within(com.example.spring.aop..*) && @annotation(com.example.spring.aop.annotation.EnableLog)")
public void annotationMethod(){}
//---------------------------------------------------------------------
// Types of advice
//---------------------------------------------------------------------
@Before(value = "regexpExecution()")
public void beforeAdvice(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println(String.format("前置通知:beforeAdvice,参数是:%s", Arrays.toString(args)));
System.out.println(simpleDateFormat.get().format(new Date()) + methodName);
}
@AfterReturning(value = "regexpExecution()", returning = "returnVal")
public void afterReturningAdvice(Object returnVal){
System.out.println(String.format("后置通知:afterReturningAdvice,返回参数是:%s", returnVal));
}
@AfterThrowing(value = "regexpExecution()", throwing = "e")
public void afterThrowingAdvice(Throwable e) {
System.out.println(String.format("异常通知:afterThrowingAdvice,异常信息:%s", e));
}
@After(value = "regexpExecution()")
public void afterAdvice() {
System.out.println(String.format("最后通知:afterAdvice"));
}
@Around(value = "regexpExecution()")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
Object rtValue = null;
try {
System.out.println("aroundAdvice前置通知!");
// 获取参数
Object[] args = proceedingJoinPoint.getArgs();
// 执行切入点方法
rtValue = proceedingJoinPoint.proceed(args);
System.out.println("aroundAdvice后置通知!");
} catch (Throwable e) {
System.out.println("aroundAdvice异常通知!");
e.printStackTrace();
} finally {
System.out.println("aroundAdvice最后通知!");
}
return rtValue;
}
}
复制代码
aroundAdvice 前置通知!
前置通知:beforeAdvice,参数是:[1]
[2020-13-24 02:13:48:509]findUserNameById
aroundAdvice 后置通知!
aroundAdvice 最后通知!
最后通知:afterAdvice
后置通知:afterReturningAdvice,返回参数是:tom
8.2.5、例子:实现日志监控
Service 加个方法:
AopConfiguration.java:
package com.example.spring.aop.config;
import com.example.spring.aop.core.interceptor.TestMonitoringInterceptor;
import com.example.spring.aop.service.UserService;
import com.example.spring.aop.service.impl.UserServiceImpl;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* <pre>
* AOP日志监控配置类
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: V1.0.0 修改人:mazq 修改日期: 2020/11/23 14:30 修改内容: 新增配置类
* </pre>
*/
@Configuration
@Aspect
@EnableAspectJAutoProxy
public class AopLogMonitorConfiguration {
@Pointcut("within(com.example.spring.aop..*) && execution(public String com.example.spring.aop.service.UserService.findUserNameById(Long, ..))")
public void monitor(){ }
@Bean
public TestMonitoringInterceptor monitoringInterceptor() {
return new TestMonitoringInterceptor(true);
}
@Bean
public Advisor monitoringAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("com.example.spring.aop.config.AopConfiguration.monitor()");
return new DefaultPointcutAdvisor(pointcut, monitoringInterceptor());
}
@Bean
public UserService userService(){
return new UserServiceImpl();
}
}
复制代码
TestMonitoringInterceptor.java
package com.example.spring.aop.core.interceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.springframework.aop.interceptor.AbstractMonitoringInterceptor;
/**
* <pre>
* TestMonitoringInterceptor
* </pre>
*
* <pre>
* 修改记录
* 修改后版本: 修改人: 修改日期: 2020/11/23 16:39 修改内容:
* </pre>
*/
public class TestMonitoringInterceptor extends AbstractMonitoringInterceptor {
public TestMonitoringInterceptor(){}
public TestMonitoringInterceptor (boolean useDynamicLogger) {
setUseDynamicLogger(useDynamicLogger);
}
@Override
protected Object invokeUnderTrace(MethodInvocation methodInvocation, Log log) throws Throwable {
String name = createInvocationTraceName(methodInvocation);
long start = System.currentTimeMillis();
try {
return methodInvocation.proceed();
} finally {
long end = System.currentTimeMillis();
long time = end - start;
log.info(String.format("方法名:%s,执行时间:%s ms",name,time));
if (time > 10) {
log.warn(String.format("方法名:%s,执行时间超过10 ms! ",name));
}
}
}
}
复制代码
pom.xml 加上 logback 配置:
<properties>
<slf4j.version>1.7.25</slf4j.version>
<logback.version>1.2.3</logback.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
</dependencies>
复制代码
logback.xml,copy @https://github.com/eugenp/tutorials/blob/master/spring-aop/src/main/resources/logback.xml,进行一点改写:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- copy @https://github.com/eugenp/tutorials/blob/master/spring-aop/src/main/resources/logback.xml-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<logger name="org.springframework" level="WARN" />
<logger name="org.springframework.transaction" level="WARN" />
<!-- in order to debug some marshalling issues, this needs to be TRACE -->
<logger name="org.springframework.web.servlet.mvc" level="WARN" />
<logger name="com.example.spring.aop.core.interceptor.TestMonitoringInterceptor" level="INFO" />
<logger name="org.springframework.aop.interceptor.PerformanceMonitorInterceptor" level="TRACE" />
<root level="TRACE">
<appender-ref ref="STDOUT" />
</root>
</configuration>
复制代码
package com.example.spring.aop;
import com.example.spring.aop.bean.User;
import com.example.spring.aop.config.AopConfiguration;
import com.example.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestApplication {
public static void testLogMonitoring() {
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext();
// 注册配置类
ioc.register(AopConfiguration.class);
// 启动IOC容器
ioc.refresh();
UserService userService = (UserService) ioc.getBean("userService");
System.out.println(userService.findUserNameById(1L));
}
public static void main(String[] args) {
// logging monitoring
testLogMonitoring();
}
}
复制代码
17:54:05.553 [main] INFO c.e.s.a.service.impl.UserServiceImpl - 方法名:com.example.spring.aop.service.UserService.findUserNameById,执行时间:2531 ms
17:54:05.559 [main] WARN c.e.s.a.service.impl.UserServiceImpl - 方法名:com.example.spring.aop.service.UserService.findUserNameById,执行时间超过 10 ms!
8.3、Spring2.0+ schema-based 配置
Spring2.0 之后提供了基于 <aop />
命名空间的 XML 配置,这也就是本文介绍的 schema-based 配置
SchemaBasedAspect .java:
package com.example.spring.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class SchemaBasedAspect {
private ThreadLocal<SimpleDateFormat> simpleDateFormat =new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue() {
//return super.initialValue();
return new SimpleDateFormat("[yyyy-mm-dd hh:mm:ss:SSS]");
}
};
public void beforeAdvice(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println(String.format("前置通知:beforeAdvice,参数是:%s", Arrays.toString(args)));
System.out.println(simpleDateFormat.get().format(new Date()) + methodName);
}
public void afterReturningAdvice(Object returnVal){
System.out.println(String.format("后置通知:afterReturningAdvice,返回参数是:%s", returnVal));
}
public void afterThrowingAdvice(Throwable e) {
System.out.println(String.format("异常通知:afterThrowingAdvice,异常信息:%s", e));
}
public void afterAdvice() {
System.out.println(String.format("最后通知:afterAdvice"));
}
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
Object rtValue = null;
try {
System.out.println("aroundAdvice前置通知!");
// 获取参数
Object[] args = proceedingJoinPoint.getArgs();
// 执行切入点方法
rtValue = proceedingJoinPoint.proceed(args);
System.out.println("aroundAdvice后置通知!");
} catch (Throwable e) {
System.out.println("aroundAdvice异常通知!");
e.printStackTrace();
} finally {
System.out.println("aroundAdvice最后通知!");
}
return rtValue;
}
}
复制代码
spring_schemaBased_config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 具体业务实现类(target Object)-->
<bean id="userService" class="com.example.spring.aop.service.impl.UserServiceImpl"></bean>
<!-- SchemaBased Aspect-->
<bean id="LoggingAspect" class="com.example.spring.aop.aspect.SchemaBasedAspect"></bean>
<aop:aspectj-autoproxy/>
<!--开始aop的配置-->
<aop:config>
<aop:pointcut id="executionPointcut" expression="execution(* com.example.spring.aop.service.UserService.*(..))" />
<!--配置切⾯-->
<aop:aspect id="logAspect" ref="LoggingAspect">
<!--配置前置通知-->
<aop:before method="beforeAdvice"
pointcut-ref="executionPointcut"></aop:before>
<!--配置后置通知-->
<aop:after-returning method="afterReturningAdvice"
pointcut-ref="executionPointcut" returning="returnVal"></aop:after-returning>
<!-- 配置异常通知-->
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="executionPointcut" throwing="e"
></aop:after-throwing>
<!-- 配置最后通知-->
<aop:after method="afterAdvice" pointcut-ref="executionPointcut"></aop:after>
<!-- 配置环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="executionPointcut"></aop:around>
</aop:aspect>
</aop:config>
</beans>
复制代码
TestApplication.java:
package com.example.spring.aop;
import com.example.spring.aop.bean.User;
import com.example.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestApplication {
public static void testSchemaBasedAop(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("classpath:spring_schemaBased_config.xml");
UserService userService = (UserService) ioc.getBean("userService");
User userDto = new User();
userDto.setUsername("tom");
userDto.setPassword("11");
userService.addUser(userDto);
System.out.println(String.format("用户数据打印:%s",userService.getUser().toString()));
}
public static void main(String[] args) {
// schema Based config
testSchemaBasedAop();
}
}
复制代码
本文的代码例子可以在 github 找到下载链接:链接
看完三件事❤️
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
关注公众号 『 java 烂猪皮 』,不定期分享原创知识。
同时可以期待后续文章 ing🚀
.关注后回复【666】扫码即可获取学习资料包
作者:SmileNicky
出处:https://www.cnblogs.com/mzq123/p/14041943.html
评论