一、AOP 配置
1、导入 jar 包
① 导入 Spring 基础包
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</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-expression</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-test</artifactId>
<version>${spring-version}</version>
</dependency>
② 导入基础的 AOP 包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
③ 导入加强版的面向切面编程(即使目标对象没有实现任何接口也能创建动态代理)
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>com.springsource.org.aspectj.weaver</artifactId>
<version>1.6.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aopalliance</groupId>
<artifactId>com.springsource.org.aopalliance</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>net.sourceforge.cglib</groupId>
<artifactId>com.springsource.net.sf.cglib</artifactId>
<version>2.1.3</version>
</dependency>
2、写配置
①将目标类和切面类(封装通知方法(在目标方法执行前后的方法))加入到IOC容器中
②告诉Spring哪个是切面类@Aspect
③告诉Spring,切面类里面的每一个方法,都是何时何地运行
将业务类添加到容器中:
@Service
public class MyMathCalculator implements Calculator{}
将切面类(日志类)加到容器中
/**
* 如果将这个类(切面类)中的这些方法(通知方法)动态的在目标方法运行的各个位置切入
*/
@Aspect
@Component
public class LogUtils {
/**
* 告诉 Spring 每个方法都什么时候运行
*
* try {
* @Before
* method.invoke(obj, args);
* @AfterReturning
* }catch(e) {
* @AfterThrowing
* }finally {
* @After
* }
*
*
* @Before:在目标方法运行之前 前置通知
* @After:在目标方法运行结束之后 后置通知
* @AfterReturning:在目标方法正常放回之后 返回通知
* @AfterThrowing:在目标方法抛异常之后 异常通知
* @Around:环绕 环绕通知
*/
//想在执行目标方法之前
//execution(访问权限符 返回值类 方法签名)
@Before("execution(public int com.njf.aop.calc.MyMathCalculator.add(int, int))")
public static void logStart(){
System.out.println("【XXX】方法执行了,参数为【XXX】");
}
//想在目标方法正执行完毕之后
@AfterReturning("execution(public int com.njf.aop.calc.MyMathCalculator.add(int, int))")
public static void logReturn(){
System.out.println("【XXX】方法执行完成,他的结果为是:");
}
@AfterThrowing("execution(public int com.njf.aop.calc.MyMathCalculator.add(int, int))")
//想在目标方法出现异常时执行
public static void logException(){
System.out.println("【XXX】方法出现了异常,异常为: ");
}
//想在目标方法结束时执行
@After("execution(public int com.njf.aop.calc.MyMathCalculator.add(int, int))")
public static void logEnd(){
System.out.println("【XXX】方法执行最终完成");
}
}
开启包扫描:
<context:component-scan base-package="com.njf.aop"></context:component-scan>
3、在配置文件中 开启基于注解的AOP模式
<!-- 开启基于注解的AOP功能:aop 名称空间 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4、测试
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
@Test
public void test1() {
//1、从ioc容器中拿到目标对象
//注意:如果想要用类型获取,一定要用他的接口类型,不要用他的本类
Calculator calculator = ioc.getBean(Calculator.class);
calculator.add(1, 2);
}
二、AspectJ
1、简介
AspectJ:Java社区里最完整最流行的AOP框架。
在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
2、在 Spring 中启用 AspectJ 注解支持
① 导入jar包依赖
aopalliance.jar
aspectj.weaver.jar
spring-aspects.jar
② 开启基于注解的 AOP
(1)引入AOP名称空间
(2)配置文件
在配置文件中开启自动生成代理。
<!-- 开启 aspectJ 的自动代理功能 -->
<aop:aspectj-autoproxy />
作用:当 Spring IOC 容器侦测到 bean 配置文件中的 <aop:aspectj-autoproxy> 元素时,会自动为与 AspectJ 切面匹配的 bean 创建代理。
(3)开启 Spring IOC 的扫描组件
<!-- 让 IOC 容器扫描添加了注解的组件 -->
<context:component-scan base-package="com.spring.aop"></context:component-scan>
3、用AspectJ注解声明切面
(1)要在 Spring 中声明 AspectJ 切面,只需要在 IOC 容器中将切面声明为 bean 实例;
(2)当在 Spring IOC 容器中初始化 AspectJ 切面之后,Spring IOC 容器就会为那些与 AspectJ 切面想匹配的 bean 创建代理;
(3)在 AspectJ 注解中,切面只是一个带有 @Aspect 注解的 Java 类,它往往包含很多通知;
(4)通知是标注有某种注解的简单的 Java 方法;
try{
@Before
method.invoke(obj,args);
@AfterReturning
}catch(e){
@AfterThrowing:在目标方法抛异常之后
}finally{
@after:在目标方法运行结束之后
}
4、切点表达式:
execution(访问权限符 返回值类型 方法签名)
5、通知注解
AspectJ 支持 5 种类型的通知注解:
告诉Spring每个方法,都什么时候运行
① @Before:前置通知,在方法执行之前执行;
② @After:后置通知,在方法执行之后执行(finally里面,永远都会执行);
③ @AfterRunning:返回通知,在方法返回结果之后执行;
④ @AfterThrowing:异常通知,在方法抛出异常之后执行;
⑤ @Around:环绕通知,围绕这方法执行;
三、完整代码
计算器核心业务:
// 计算器接口
public interface Calculator {
public int add(int i, int j);
public int sub(int i, int j);
public int mul(int i, int j);
public int div(int i, int j);
}
//计算器接口实现类
@Service
public class MyMathCalculator implements Calculator{
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
切面类:
/**
* 如果将这个类(切面类)中的这些方法(通知方法)动态的在目标方法运行的各个位置切入
*/
@Aspect
@Component
public class LogUtils {
/**
* 告诉 Spring 每个方法都什么时候运行
*
* try {
* @Before
* method.invoke(obj, args);
* @AfterReturning
* }catch(e) {
* @AfterThrowing
* }finally {
* @After
* }
*
*
* @Before:在目标方法运行之前 前置通知
* @After:在目标方法运行结束之后 后置通知
* @AfterReturning:在目标方法正常放回之后 返回通知
* @AfterThrowing:在目标方法抛异常之后 异常通知
* @Around:环绕 环绕通知
*/
//想在执行目标方法之前
//execution(访问权限符 返回值类 方法签名)
@Before("execution(public int com.njf.aop.calc.MyMathCalculator.add(int, int))")
public static void logStart(){
System.out.println("【XXX】方法执行了,参数为【XXX】");
}
//想在目标方法正执行完毕之后
@AfterReturning("execution(public int com.njf.aop.calc.MyMathCalculator.add(int, int))")
public static void logReturn(){
System.out.println("【XXX】方法执行完成,他的结果为是:");
}
@AfterThrowing("execution(public int com.njf.aop.calc.MyMathCalculator.add(int, int))")
//想在目标方法出现异常时执行
public static void logException(){
System.out.println("【XXX】方法出现了异常,异常为: ");
}
//想在目标方法结束时执行
@After("execution(public int com.njf.aop.calc.MyMathCalculator.add(int, int))")
public static void logEnd(){
System.out.println("【XXX】方法执行最终完成");
}
}
配置文件:
<!-- 扫描让 IOC 容器管理的 bean -->
<context:component-scan base-package="com.njf.aop"></context:component-scan>
<!-- 当Spring IOC容器侦测到bean配置文件中的该元素时,会自动为与AspectJ切面匹配的bean创建代理 -->
<!-- 开启 aspectJ 的自动代理功能 -->
<aop:aspectj-autoproxy />
四、细节
1、IOC容器中保存的是组件的代理对象
@Test
public void test1() {
//1、从ioc容器中拿到目标对象
//注意:如果想要用类型获取,一定要用他的接口类型,不要用他的本类
/**
* 细节一:
* com.njf.aop.calc.MyMathCalculator@368f2016
* class com.sun.proxy.$Proxy13
* AOP 的底层就是动态代理,容器中保存是组件是它的代理对象:$Proxy13, 当然不是本类的类型。
*
*/
Calculator calculator = ioc.getBean(Calculator.class);
calculator.add(1, 2);
System.out.println(calculator);
System.out.println(calculator.getClass());
}
2、没有接口就是本类类型
CGLIB会帮我们创建好代理对象,就算没有接口
@Test
public void test2() {
//没有接口就是本类类型,
//com.njf.aop.calc.MyMathCalculator@3098cf3b
//class com.njf.aop.calc.MyMathCalculator$$EnhancerByCGLIB$$13c10013
//cglib 帮我们创建好的代理对象
MyMathCalculator calculator = ioc.getBean(MyMathCalculator.class);
calculator.add(1, 2);
System.out.println(calculator);
System.out.println(calculator.getClass());
}
有接口就转成接口类型(Calculator.class),没有接口就转成本类类型(MyMathCalculator.class)