辛苦堆砌,转载请注明出处,谢谢!
上一篇说了Spring的依赖注入,今天再看看Spring的AOP,牵扯的AOP的理论知识,大家可以搜索一些文章了解一下,这里不做过多解释,本文主要介绍使用Spring如何实现AOP。
Spring的AOP只限于对方法进行拦截,如果有其他需求,可以考虑使用AspectJ。
Spring提供的通知有以下几种:
1.前向通知,在调用目标方法前执行,使用@Before注解
2.返回通知,在调用目标方法无异常返回后执行,使用@AfterReturning注解
3.异常返回通知,在调用目标方法抛出异常后执行,使用@AfterThrowing注解
4.后向通知,在调用目标方法返回后执行,无论是正常返回还是抛出异常,使用@After注解
5.环绕通知,在调用目标方法前后都会进行通知,使用@Around注解
本篇基于上一篇的示例,给出一个新的场景,有一名计算机老师教学生如何点击鼠标选择一个图标,如何点击鼠标弹出一个上下文菜单。我们先看一下我们的Teacher类。
package com.yjp.spring.study.beans;
@Component
public class Teacher {
public void teachSelectAnIcon() {
System.out.println("请在一个图标上点击鼠标左键");
}
public void selectAnIconSuccess() {
System.out.println("做得好!你选中了一个图标");
}
public void teachShowContextMenu() {
System.out.println("请点击鼠标右键");
}
public void showContextMenuSuccess() {
System.out.println("做得好!弹出了一个上下文菜单");
}
}
可以看到,Teacher是一个Spring Bean(用@Component注解), 如果不使用AOP,我们需要在Computer中注入依赖,来使用teacher bean。
package com.yjp.spring.study.beans;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class Computer {
private Mouse mouse;
@Autowired
private Teacher teacher;
@Autowired
public Computer(@Qualifier("blackMouse") Mouse mouse) {
this.mouse = mouse;
}
public void selectAnIcon() {
teacher.teachSelectAnIcon();
mouse.clickLeft();
teacher.selectAnIconSuccess();
}
public void contextMenu() {
teacher.teachShowContextMenu();
mouse.clickRight();
teacher.showContextMenuSuccess();
}
}
可以看到,我们需要用teacher的方法"拥抱"Computer,两者不分你我,融合得很好,但是,有一些问题,teacher和computer本就是独立的,为什么必须要求computer持有一个teacher的引用,我们就没有办法让teacher在它该出场的时候再出场吗?当然可以,这时候我们使用AOP的时候来了。我们先删除上面代码中的teacher相关的逻辑,恢复到一开始Computer的状态。
首先,启用我们的AOP,在我们的配置类上加入@EnableAspectJAutoProxy注解
@Configuration
@EnableAspectJAutoProxy
@ComponentScan({"com.yjp.spring.study.beans"})
public class ComputerConfig {
}
这样就启用了Spring的AOP,然后为Teacher类添加注解
package com.yjp.spring.study.beans;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class Teacher {
@Before("execution(* com.yjp.spring.study.beans.Computer.selectAnIcon(..))")
public void teachSelectAnIcon() {
System.out.println("请在一个图标上点击鼠标左键");
}
@After("execution(* com.yjp.spring.study.beans.Computer.selectAnIcon(..))")
public void selectAnIconSuccess() {
System.out.println("做得好!你选中了一个图标");
}
@Before("execution(* com.yjp.spring.study.beans.Computer.contextMenu(..))")
public void teachShowContextMenu() {
System.out.println("请点击鼠标右键");
}
@After("execution(* com.yjp.spring.study.beans.Computer.contextMenu(..))")
public void showContextMenuSuccess() {
System.out.println("做得好!弹出了一个上下文菜单");
}
}
首先,Teacher有@Component注解,说明它是一个Bean,之后添加了@Aspect注解,该注解说明Teacher这个Bean要作为切面使用,这里注意@Aspect注解是aspectjrt包中定义的,不是Spring的注解,需要加入依赖。然后,将几个方法使用@Before和@After进行注解,注解的参数是一个excution表达式,一般结构是
返回值类型 完整类名.方法名(参数)
其中可以用* 代表任意类型,..代表任意参数
再次运行程序,会看到如下打印
请在一个图标上点击鼠标左键
BlackMouse Left Clicked!
做得好!你选中了一个图标
请点击鼠标右键
BlackMouse Right Clicked!
做得好!弹出了一个上下文菜单
运行成功,结果还不错,但是有一点还不太好,我们是不是可以环绕通知,把对应的方法包裹起来,当然可以,在Teacher类删除之前的方法,添加新的方法和注解,如下所示:
package com.yjp.spring.study.beans;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class Teacher {
@Around("execution(* com.yjp.spring.study.beans.Computer.selectAnIcon(..))")
public void selectAnIconAround(ProceedingJoinPoint jp) {
try {
System.out.println("环绕通知 请在一个图标上点击鼠标左键");
jp.proceed();
System.out.println("环绕通知 做得好!你选中了一个图标");
} catch (Throwable e) {
e.printStackTrace();
}
}
@Around("execution(* com.yjp.spring.study.beans.Computer.contextMenu(..))")
public void showContextMenuAround(ProceedingJoinPoint jp) {
try {
System.out.println("环绕通知 请点击鼠标右键");
jp.proceed();
System.out.println("环绕通知 弹出了一个上下文菜单");
} catch (Throwable e) {
e.printStackTrace();
}
}
}
这样就有了环绕通知,环绕通知的差别在于,使用@Around对方法进行注解,环绕通知方法有一个参数ProceedingJpinPoint,字面意思就是连接点,连接点是我们调用被环绕方法的途径,jp.proceed()之前的内容,在方法调用前执行,之后的内容,在方法调用之后执行。运行结果如下:
环绕通知 请在一个图标上点击鼠标左键
BlackMouse Left Clicked!
环绕通知 做得好!你选中了一个图标
环绕通知 请点击鼠标右键
BlackMouse Right Clicked!
环绕通知 弹出了一个上下文菜单
Spring使用AOP就是如此简单,当然,AOP还可以处理带有参数的方法,还能配合其他一些注解,限制AOP作用的条件,这些可以在使用中慢慢学习。下面给出目前的pom.xml配置供参考。
<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.yjp.spring</groupId>
<artifactId>study</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>study</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<junit.version>4.12</junit.version>
<spring.version>4.3.4.RELEASE</spring.version>
<aspectjrt.version>1.8.9</aspectjrt.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</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>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectjrt.version}</version>
</dependency>
</dependencies>
</project>
![](https://img-my.csdn.net/uploads/201612/08/1481201471_6133.png)