Spring 使用AspectJ进行AOP开发(基于注解)
在 Spring 中,虽然我们可以使用 XML 配置文件可以实现 AOP 开发,但如果所有的配置都集中在 XML 配置文件中,就势必会造成 XML 配置文件过于臃肿,从而给维护和升级带来一定困难。 (这个XML配置就是逊呐)
AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义,Spring 框架可以根据这些注解生成 AOP 代理。
注解介绍
名称 | 说明 |
---|---|
@Aspect | 用于定义一个切面。 |
@Pointcut | 用于定义一个切入点。 |
@Before | 用于定义前置通知,相当于 BeforeAdvice。 |
@AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 |
@Around | 用于定义环绕通知,相当于 MethodInterceptor。 |
@AfterThrowing | 用于定义抛出通知,相当于 ThrowAdvice。 |
@After | 用于定义最终通知,不管是否异常,该通知都会执行。 |
@DeclareParents | 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。 |
切入点表达式
学习使用Aspect进行AOP开发之前,要先了解一下切入点表达式。 AspectJ最强大的地方就在于他的切入点表达式:
execution() ,用于描述方法 ,语法格式为
execution([权限修饰符] [返回值类型] [类的完全限定名] [方法名称]([参数列表])
其中:
-
返回值类型、方法名、参数列表是必须配置的选项,权限修饰符、类的完全限定名则为可选配选项。
-
返回值类型:
*
表示任意返回值; 如果返回值为对象,则需指定全路径的类名。 -
类的完全限定名:包名+类名
-
方法名:
*
代表所有方法,set*
代表以set开头的所有方法,*do
代表以do结尾的所有有方法,addUser
代表固定方法 -
参数列表:
(..)
代表所有参数;(*)
代表只有一个参数,且参数类型为任意类型;( *,String)
代表有两个参数,第一个参数可以为任何值,第二个为 String 类型的值。
举例1:对org.example包下的UserDao类中的add()方法进行增强,配置如下
execution(* org.example.UserDao.add(..))
举例2: 对org.example 包下 UserDao 类中的所有方法进行增强
execution(* org.example.UserDao.*(..))
举例 3:对 org.example 包下所有类中的所有方法进行增强
execution(* org.example.*.*(..))
实现步骤
1.启用@AspectJ注解支持
在使用 @AspectJ 注解进行 AOP 开发前,首先我们要先启用 @AspectJ 注解支持。我们可以通过以下 2 种方式来启用 @AspectJ 注解。
1)使用Java配置类启用
我们可以在 Java 配置类(标注了 @Configuration 注解的类)中,使用 @EnableAspectJAutoProxy 和 @ComponentScan 注解启用 @AspectJ 注解支持。
@Configuration
@ComponentScan(basePackages = "org.example") //注解扫描
@EnableAspectJAutoProxy //开启 AspectJ 的自动代理
public class AppConfig {
}
2)基于XML配置启用
在Spring的XML配置文件中,添加以下内容启用@AspectJ注解支持。
<!-- 开启注解扫描 -->
<context:component-scan base-package="org.example"/>
<!--开启AspectJ 自动代理-->
<aop:aspectj-autoproxy/>
2.定义切面@Aspect
可以通过 @Aspect 注解将一个 Bean 定义为切面。
在启用了 @AspectJ 注解支持的情况下,Spring 会自动将 IoC 容器(ApplicationContext)中的所有使用了 @Aspect 注解的 Bean 识别为一个切面。
1)只需要在定义好的Bean对应的Java类使用一个@Aspect注解 ,将这个 Bean 定义为一个切面
<bean id = "myAspect" class = "org.example.MyAspect">
...
</bean>
package org.example;
import org.aspectj.lang.annotation.*;
@Aspect //定义为切面
public class MyAspect {
}
2)全注解方式定义切面
package org.example;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component //Spring自动装配 将类的对象定义成 Bean
@Aspect //将Bean定义为切面
public class MyAspect {
}
3.定义切点@Pointcut
使用 @Pointcut 注解用来定义一个切点。需要注意的是,定义为切点的方法,它的返回值类型必须为 void。
// 要求:方法必须是private,返回值类型为 void,名称自定义,没有参数
@Pointcut("execution(*org.example..*.*(..))")
private void myPointCut() {
}
@Pointcut 注解中有一个 value 属性,这个属性的值就是切入点表达式。
除了可以通过切入点表达式(execution)直接对切点进行定义外,还可以通过切入点方法的名称来引用其他的切入点。在使用方法名引用其他切入点时,还可以使用“&&”、“||”和“!”等表示“与”、“或”、“非”的含义。
/**
* 将 org.example.dao包下 UserDao 类中的 get() 方法定义为一个切点
*/
@Pointcut(value ="execution(* org.example.dao.UserDao.get(..))")
public void pointCut1(){
}
/**
* 将 org.example.dao包下 UserDao 类中的 delete() 方法定义为一个切点
*/
@Pointcut(value ="execution(* org.example.dao.UserDao.delete(..))")
public void pointCut2(){
}
/**
* 除了 org.example.dao包下 UserDao 类中 get() 方法和 delete() 方法外,其他方法都定义为切点
*
* ! 表示 非
* && 表示 与
* || 表示 或
*/
@Pointcut(value ="!pointCut1() && !pointCut2()")
public void pointCut3(){
}
4.定义通知(Advice)
AspectJ 为我们提供了以下 6 个注解,来定义 6 种不同类型的通知(Advice),如下表。
注解 | 说明 |
---|---|
@Before | 用于定义前置通知,相当于 BeforeAdvice。 |
@AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 |
@Around | 用于定义环绕通知,相当于 MethodInterceptor。 |
@AfterThrowing | 用于定义抛出通知,相当于 ThrowAdvice。 |
@After | 用于定义最终通知,不管是否异常,该通知都会执行。 |
@DeclareParents | 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。 |
以上这些通知注解中都有一个 value 属性,这个 value 属性的取值就是这些通知(Advice)作用的切点(PointCut),它既可以是切入点表达式,也可以是切入点的引用(切入点对应的方法名称)。
@Pointcut(value ="execution(* org.example.dao.UserDao.get(..))")
public void pointCut1(){
}
@Pointcut(value ="execution(* org.example.dao.UserDao.delete(..))")
public void pointCut2(){
}
@Pointcut(value ="!pointCut1() && !pointCut2()")
public void pointCut3(){
}
//使用切入点引用
@Before("MyAspect.pointCut3()")
public void around() throws Throwable {
System.out.println("环绕增强……");
}
//使用切入点表达式
@AfterReturning(value = "execution(* org.example.dao.UserDao.get(..))" ,returning = "returnValue")
public void afterReturning(Object returnValue){
System.out.println("方法返回值为:"+returnValue);
}
代码示例
-
新建Maven项目,在pom.xml文件中导入maven依赖
//在父级dependencies定义spring版本 <spring.version>5.3.15</spring.version> <dependencies> <!--日志--> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <!-- java ee --> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> </dependency> <!-- 单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>5.3.15</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.15</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <!--Aspectj--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency> </dependencies>
-
在org.example.dao包下,创建一个名为UserDao的接口
package org.example.dao; /** * 数据访问层 接口 */ public interface UserDao { public void add(); public void delete(); public Integer modfiy(); public void get(); }
-
在org.example.dao.imp1包下,创建UserDao的实现类UserDaoImp1
package org.example.dao.imp1; import org.example.dao.UserDao; import org.springframework.stereotype.Component; @Component("userDao") public class UserDaoImp1 implements UserDao { @Override public void add() { System.out.println("正在执行UserDao的add()方法"); } @Override public void delete() { System.out.println("正在执行UserDao的delete()方法"); } @Override public Integer modfiy() { System.out.println("正在执行UserDao的modfiy()方法"); return 1; } @Override public void get() { System.out.println("正在执行UserDao的get()方法"); } }
-
在org.example.dao.config包下,新建名为AppConfig的配置类
package org.example.dao.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @ComponentScan(basePackages = "org.example") //注解扫描 @EnableAspectJAutoProxy //开启AspectJ自动代理 public class AppConfig { }
-
在org.example包下,创建名为MyAspect的切面类
package org.example; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component //定义成Bean @Aspect //定义为切面 public class MyAspect { @Before("execution(* org.example.dao.UserDao.add(..))") public void before(JoinPoint joinPoint) { System.out.println("前置增强..." + joinPoint); } @After("execution(* org.example.dao.UserDao.get(..))") public void after(JoinPoint joinPoint){ System.out.println("最终增强..."+joinPoint); } //将org.example.dao下的UserDao类的get()方法定义为一个切点 @Pointcut(value = "execution(* org.example.dao.UserDao.get(..))") public void pointCut1(){ } //将org.example.dao下的UserDao类的delete(()方法定义为一个切点 @Pointcut(value = "execution(* org.example.dao.UserDao.delete(..))") public void pointCut2(){ } //使用切入点引用 @Around("MyAspect.pointCut2()") public void around(ProceedingJoinPoint point) throws Throwable{ System.out.println("环绕增强前..."); point.proceed(); System.out.println("环绕增强后..."); } //使用切入点表达式 @AfterReturning(value = "execution(* org.example.dao.UserDao.modfiy(..))",returning = "returnValue") public void afterReturning(Object returnValue){ System.out.println("后置返回增强...返回值为:"+returnValue); } }
-
MainApp类
package org.example; import org.example.dao.UserDao; import org.example.dao.config.AppConfig; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main( String[] args ){ ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class); UserDao userDao=(UserDao)context.getBean("userDao"); userDao.add(); userDao.delete(); userDao.modfiy(); userDao.get(); } }
-
运行结果
前置增强...execution(void org.example.dao.UserDao.add()) 正在执行UserDao的add()方法 环绕增强前... 正在执行UserDao的delete()方法 环绕增强后... 正在执行UserDao的modfiy()方法 后置返回增强...返回值为:1 正在执行UserDao的get()方法 最终增强...execution(void org.example.dao.UserDao.get())
- 点赞
- 收藏
- 关注作者
评论(0)