入门Spring框架的AOP
什么是AOP的技术
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
AOP是一种编程范式,最早由AOP联盟组织提出的一套规范.
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,
提高程序的可重用性,同时提高了开发的效率,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查)
AOP的底层实现
Srping框架的AOP技术底层也是采用的代理技术,代理的方式提供了两种
基于JDK的动态代理:
必须是面向接口的,只有实现了具体接口的类才能生成代理对象
基于CGLIB动态代理
对于没有实现了接口的类,也可以产生代理,产生这个类的子类的方式,Spring的核心包已经集成了cglib的包
具体采用哪种是由Spring来决定的,如果代理的类实现了接口则使用JDK动态代理完成AOP, 如果没有实现接口则采用CGLIB动态代理完成AOP
AOP基于XML方式的实现
步骤一:引入maven依赖
tip:这里并没有使用SpringBoot
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
<!-- StringUtils-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.5.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
步骤二:定义目标类和切面类
//定义目标类,对其方法进行日志处理
public class UserDao {
public void saveUser() {
System.out.println("保存用户成功...");
}
}
//切面类
public class MyAspectXml {
// 定义通知
public void log() {
System.out.println("记录日志...");
}
}
步骤三:配置spring的核心配置类
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 注册目标类 -->
<bean id="userDao" class="springaop.UserDao"></bean>
<!--切面类-->
<bean id="myAspectXml" class="springaop.MyAspectXml"></bean>
<!-- 完成aop的配置 -->
<aop:config>
<!-- 引入切面类 -->
<aop:aspect ref="myAspectXml">
<!--
aop:after :定义通知类型,常用的有5种通知类型,后面会介绍
method:切面类的方法
pointcut:切入点的表达式,用于指定要切入的目标类的方法, execution()固定写法,里面的表达式可以有简写方式
-->
<aop:after method="log" pointcut="execution(public void springaop.UserDao.saveUser())"/>
</aop:aspect>
</aop:config>
</beans>
测试
@ContextConfiguration(locations = "classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestUserDao {
@Autowired
private UserDao userDao; // 通过ioc注入值(name的值对应的是xml配置中bean的id属性)
/*
这里如果打印了
切面类MyAspectXml里面的
System.out.println("记录日志...");则测试Aop成功
*/
@Test
public void test1() {
userDao.saveUser();
}
}
测试通过
5种AOP通知类型
-
前置通知
在目标类的方法执行之前执行。
配置文件信息:<aop:before /> -
最终通知
在目标类的方法执行之后执行,如果程序出现了异常,最终通知也会执行。
在配置文件中编写具体的配置:<aop:after /> -
后置通知
方法正常执行后的通知。
在配置文件中编写具体的配置:<aop:after-returning /> -
异常抛出通知
在抛出异常后通知
在配置文件中编写具体的配置:<aop:after-throwing /> -
环绕通知
方法的执行前后执行。
在配置文件中编写具体的配置:<aop:around />
需要手动使用ProceedingJoinPoint对来让目标对象的方法执行
切入点表达式
完整表达式为:execution([权限修饰符] 返回值类型 包名.类名.方法名(参数))
简写规则如下:
1.权限修饰符非必填,如果目标方法是private的,则必填
2.返回值类型必填,根据你的方法来编写返回值。但是可以使用代替。
3. com..saveUser 可以匹配到 com.UserDao.saveUser
*…*则表示任意的包的结构 如: ….saveUser() 也可以匹配到 com.UserDao.saveUser
4.类名必填,也可以使用 * 号代替任何,同样有类似的写法:Dao表示以Dao结尾的类名
5.方法必填,也可以使用 * 号代替任何,或者以save表示以save开头的方法名
6.参数非必填,如果是一个参数可以使用 *号代替,如果想代表任意参数使用 …
AOP基于注解方式的实现
简单来说:就是省去了在xml中配置aop:config,通过注解来实现,同时需要在核心配置来进行开启Aop注解化
步骤一:引入maven依赖(这里和上面的依赖一样)
步骤二:定义目标类和切面类
//定义目标类,对其方法进行日志处理
public class UserDao {
public void saveUser() {
System.out.println("保存用户成功2...");
}
}
//切面类(与XML不同的是,这里要加上@Aspect标签)
@Aspect
public class MyAspectXml {
/**
* 定义通知方法,常用的有
* @Before:前置通知
* @AfterReturing:后置通知
* @Around:环绕通知
* @After:最终通知
* @AfterThrowing:异常抛出通知
*
* value:切入点表达式(这里也可以使用通配符)
*/
@Before(value = "execution(public void springaop.userdao.updateUser())")
public void log() {
System.out.println("记录日志2...");
}
}
步骤三:配置spring的核心配置类
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启注解化的方式配置切面类 -->
<aop:aspectj-autoproxy/>
<!-- 注册目标类 -->
<bean id="userDao" class="springaop.UserDao"></bean>
<!--切面类-->
<bean id="myAspectXml2" class="springaop.MyAspectXml2"></bean>
</beans>
测试
@ContextConfiguration(locations = "classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestUserDao {
@Autowired
private UserDao userDao; // 通过ioc注入值(name的值对应的是xml配置中bean的id属性)
/*
这里如果打印了
切面类MyAspectXml2里面的
System.out.println("记录日志...");则测试Aop成功
*/
@Test
public void test1() {
userDao.saveUser();
}
}
测试通过
配置通用的切入点
当切面类多个通知方法都对应同一个切入点时,你会发现你需要重复写很多个相同的切入点表达式,例如:
@Aspect
public class MyAspectXml2 {
@Before(value = "execution(public void springaop.UserDao.updateUser())")
public void log() {
System.out.println("前置通知...");
}
@Before(value = "execution(public void springaop.UserDao.updateUser())")
public void log1() {
System.out.println("后置通知...");
}
@Around(value = "execution(public void springaop.UserDao.updateUser())")
public void log2(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("环绕通知1...");
try {
proceedingJoinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("环绕通知2...");
}
}
这样很麻烦,接下来通过 定义通用的切入点,来更好的处理
@Aspect
public class MyAspectXml2 {
/**
* 定义通用的切入点
*/
@Pointcut(value = "execution(public void springaop.UserDao.updateUser())")
public void method() {}
//value不再是切入点表达式了,而是定义好的通用的切入点
@Before(value = "MyAspectXml2.method()")
public void log() {
System.out.println("前置通知...");
}
@Before(value = "MyAspectXml2.method()")
public void log1() {
System.out.println("后置通知...");
}
@Around(value = "MyAspectXml2.method()")
public void log2(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("环绕通知1...");
try {
proceedingJoinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("环绕通知2...");
}
}