切面:那些重复的,公共的,通用的功能被称为切面,例如,日志,事务,权限等功能
连接点:实际就是目标方法,因为在目标方法中要实现业务功能和切面功能的整合
切入点(Pointcut):用来指定切入的位置,切入点可以是一个目标方法,可以是一个类中的所有方法,还可以是某个包下的所有类中的方法等
目标对象:操作谁,谁就是目标对象,往往是业务接口的实现类对象
通知(Advice):来指定切入的时机是在目标方法执行前,执行后,出错时,还是环绕目标方法来切入切面功能
关键字:切入点表达式由execution关键字引出,后面括号内跟需要切入切面功能的业务方法的定位信息
规范的公式:execution( 访问权限 方法返回值 方法声明(参数) 异常类型 )
简化后的公式:execution( 方法返回值 方法声明(参数) )
1. execution(public * *(..) ):任意的公共方法2. execution(* set*(..) ):任何以set开始的方法3. execution(* com.xyz.service.impl.*.*(..)):com.xyz.service.impl包下的任意类的任意方法4. execution(* com.xyz.service..*.*(..)):com.xyz.service包及其子包下的任意类的任意方法5. execution(* *..service.*.*(..)):service包下的任意类的任意方法,注意service包前可以有任意包的子包6. execution(* *.service.*.*(..)):service包下的任意类的任意方法,注意:service包前只能有一个任意的包
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>ch08-spring-aspectj</artifactId> <version>1.0.0</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!-- junit测试依赖 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <!-- 添加spring-context依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.22</version> </dependency> <!-- 添加spring-aspects --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.22</version> </dependency> </dependencies> <build> <!-- 添加资源文件的指定--> <resources> <resource> <!-- 目标目录1 --> <directory>src/main/java</directory> <includes> <!-- 被包括的文件类型 --> <include>**/*.xml</include> <include>**/*.properties</include> </includes> <filtering>false</filtering> </resource> <resource> <!-- 目标目录2 --> <directory>src/main/resources</directory> <includes> <!-- 被包括的文件类型 --> <include>**/*.xml</include> <include>**/*.properties</include> </includes> <filtering>false</filtering> </resource> </resources> </build></project>package com.example.s01;/** * 定义业务接口 */public interface SomeService { //定义业务功能 default String doSome(int orderNums){return null;}}package com.example.s01;/** * 业务功能实现类 */public class SomeServiceImpl implements SomeService{ @Override public String doSome(int orderNums) { System.out.println("---- 业务功能 ----"); System.out.println("预定图书: " + orderNums + " 册"); return "预定成功!"; }}package com.example.s01;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;/** * 切面类 *///添加@Aspect注释,表明切面类交给AspectJ这一面向切面编程框架管理@Aspectpublic class SomeServiceAspect { /** * a. 切面功能由切面类中的切面方法负责完成 * * b. 前置通知的切面方法规范 * 1.访问权限是public * 2.方法的返回值是void * 3.方法名称自定义 * 4.方法没有参数,如果有参数也只能是JoinPoint类型 * 5.必须使用注解:@Before,来声明切入的时机是前切和切入点的信息 * 参数:value,用来指定切入点表达式 * * c.前切示例 * 目标方法(即业务实现类中的方法):public String doSome(int orderNums) */ @Before(value = "execution(public String com.example.s01.SomeServiceImpl.doSome(int))") public void myBefore(){ System.out.println("前置通知: 查询图书是否有剩余"); }}<?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"> <!-- 创建业务功能对象 --> <bean id="someServiceImpl" /> <!-- 创建切面功能对象 --> <bean id="someServiceAspect" /> <!-- 绑定业务功能和切面功能--> <aop:aspectj-autoproxy/> </beans>package com.example.test;import com.example.s01.SomeService;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestBeforeAspect { //测试前置切面功能 @Test public void testBeforeAspect(){ //创建Spring容器 ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml"); //实际获取的是业务实现类对象的jdk动态代理对象 SomeService agent = (SomeService) ac.getBean("someServiceImpl"); //测试agent类型 System.out.println("agent类型: " + agent.getClass()); //代理对象调用业务功能(切面功能 + 被代理对象的传统业务功能) String res = agent.doSome(10); //接住目标对象目标业务方法的返回值 System.out.println("业务执行结果: " + res); }}agent类型: class com.sun.proxy.$Proxy10前置通知: 查询图书是否有剩余---- 业务功能 ----预定图书: 10 册业务执行结果: 预定成功!Process finished with exit code 0execution(public String com.example.s01.SomeServiceImpl.doSome(int)) //目标方法的限定范围太小 //新增一个业务功能 default void show(){} @Override public void show() { System.out.println("新增的show()方法被调用....."); } //测试切入点表达式对新增的业务方法是否起作用 @Test public void testBeforeAspect02(){ //创建Spring容器 ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml"); //获取业务实现类的jdk动态代理对象 SomeService agent = (SomeService) ac.getBean("someServiceImpl"); //代理对象调用业务功能 agent.show(); }新增的show()方法被调用.....Process finished with exit code 0execution(* com.example.s01.*.*(..)) //限定的范围不大不小,开发中常用前置通知: 查询图书是否有剩余新增的show()方法被调用.....Process finished with exit code 0execution(* com.example.s01..*(..)) //不常用,了解即可execution(* *(..)) //限定范围太大,不常用,了解即可 <!-- 绑定业务功能和切面功能--> <aop:aspectj-autoproxy/> //使用业务接口的类型去接住动态代理对象 SomeService agent = (SomeService) ac.getBean("someServiceImpl");
<!-- 绑定业务功能和切面功能--> <aop:aspectj-autoproxy proxy-target-/> //测试代理对象的类型 @Test public void testBeforeAspect03(){ //创建Spring容器 ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml"); //CGLib子类代理,用实现类(父类)的类型来接 SomeServiceImpl agent = (SomeServiceImpl) ac.getBean("someServiceImpl"); //调用业务功能 agent.show(); }前置通知: 查询图书是否有剩余新增的show()方法被调用.....Process finished with exit code 0/** * 业务功能实现类 */@Servicepublic class SomeServiceImpl implements SomeService{ //......}//切面类交给Aspectj框架管理@Aspect@Componentpublic class SomeServiceAspect { //......} <!-- 添加包扫描 --> <context:component-scan base-package="com.example.s01"/> //测试代理对象的类型 @Test public void testBeforeAspect03(){ //创建Spring容器 ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml"); //CGLib子类代理,用实现类(父类)的类型来接 SomeServiceImpl agent = (SomeServiceImpl) ac.getBean("someServiceImpl"); //调用业务功能 agent.show(); }前置通知: 查询图书是否有剩余新增的show()方法被调用.....Process finished with exit code 0 @Before(value = "execution(* com.example.s01.*.*(..))") public void myBefore(JoinPoint joinPoint){ System.out.println("目标方法签名: " + joinPoint.getSignature()); System.out.println("目标方法参数: " + Arrays.toString(joinPoint.getArgs())); System.out.println("前置通知: 查询图书是否有剩余"); } //测试前置切面功能 @Test public void testBeforeAspect(){ //创建Spring容器 ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml"); //获取业务实现类的CGLib动态代理对象 SomeService agent = (SomeService) ac.getBean("someServiceImpl"); //测试agent类型 System.out.println("agent类型: " + agent.getClass()); //代理对象调用业务功能 String res = agent.doSome(10); //接住目标对象目标业务方法的返回值 System.out.println("业务执行结果: " + res); }agent类型: class com.example.s01.SomeServiceImpl$$EnhancerBySpringCGLIB$$36b23096目标方法签名: String com.example.s01.SomeServiceImpl.doSome(int)目标方法参数: [10]前置通知: 查询图书是否有剩余---- 业务功能 ----预定图书: 10 册业务执行结果: 预定成功!