一、AOP的简介
AOP的全称是 Aspect-Oriented Programming,即面向切面编程(也称面向方面编程)。它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。
在传统的业务处理代码中,通常会进行事务处理、日志记录等操作。虽然OOP可以通过组合或者继承的方式来达到代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率。
为了解决这一问题,AOP的思想随之产生。AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,采用传统的OOP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,但不是OOP的替代品,它只是OOP的延伸和补充。
二、AOP相关术语:
- Aspect(切面):在实际应用中,切面通常是指封装的用于横向插入系统功能(如事务)的类,如上图的Aspect。该类要被Spring容器识别为切面,需要在配置文件中通过元素指定。
- Joinpoint(连接点):在程序执行过程中的某个阶段点,它实际上是对象的一个操作,例如方法的调用或异常的抛出。在 Spring AOP中,连接点就是指方法的调用。
- Pointcut(切入点):是指切面与程序流程的交叉点,即那些需要处理的连接点,如下图程序流程所示。通常在程序中,切入点指的是类或方法名,如某个通知要应用到所有以add开头的方法中,那么所有满足这一规则的方法都是切入点。
- Advice(通知/增强处理):AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法,它是切面的具体实现。
- Target Object(目标对象):是指所有被通知的对象,也称为被增强对象。如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理对象。
- Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
- Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。
三、spring基于xml的aop配置步骤
1.把通知的bean交给spring管理
2.使用aop:config标签表明开始AOP配置
3.使用aop:aspect标签表明配置切面
id:给切面提供一个唯一标识
ref:是指通知类bean的id
4.在aop:aspect标签的内部使用对应标签配置通知的类型。
aop:before:表示前置通知
method属性:用于指定Logger类中的哪个方法是前置通知
pointcut:用于指定切入点表达式,该表达式的含义是指对业务层中的哪些方法增强
- 切入点表达式写法:
关键字:execution(表达式)
标准表达式写法:public void AccountServiceImpl.saveAccount()
访问修饰符可以省略: void AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值 :* AccountServiceImpl.saveAccount()
包名可以使用通配符表示任意包,但是有几级包就需要写几个*:*.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用…表示当前包及其子包:* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配:* *..*.*()
参数列表:基本类型写名称,引用类型写包名类名的形式
全通配写法:* *..*.*(..)
四、AOP配置形式示例:
1.引入依赖:
<?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">
<parent>
<artifactId>SpringTest</artifactId>
<groupId>com.tulun</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springAOP</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
</project>
2.xml配置文件:
<?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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的ioc,把service对象配置进来-->
<bean id="accountService" class="com.service.AccountServiceImpl"></bean>
<!--配置Logger类-->
<bean id="logger" class="com.uiils.Logger"></bean>
<!--配置aop-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型并且建立通知方法和切入点的关联-->
<aop:before method="printLog" pointcut="execution( * *..*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
</beans>
3.账户业务层实现类
public class AccountServiceImpl implements AccountService {
public void saveAccount() {
System.out.println("执行了保存");
}
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
public int deteteAccount() {
System.out.println("执行了删除");
return 0;
}
}
4.账户业务层接口
public interface AccountService {
/**
* 模拟保存账户
*/
void saveAccount();
/**
* 模拟更新账户
* @param i
*/
void updateAccount(int i);
/**
* 删除账户
* @return
*/
int deteteAccount();
}
5.用于记录日志工具类,它里面提供了公共代码
public class Logger {
/**
* 用于打印日志,计划让其在切入点方法执行之前执行(切入点就是业务层方法)
*/
public void printLog(){
System.out.println("Logger类中的pringLog方法开始记录日志了。。。");
}
}
6.测试类:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
AccountService as = (AccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
as.updateAccount(1);
as.deteteAccount();
}
}
运行结果:
五、通知类型:
通知(Advice)
Spring AOP 支持五种类型的通知,它们分别定义了切面在什么时候使用,以及定义了切面需要做些什么。
Before 前置通知,目标方法被调用之前执行
After 后置通知,目标方法完成之后执行
AfterReturning返回通知,目标方法执行成功(未抛出异常)之后执行
AfterThrowing 异常通知,目标方法执行失败(抛出异常)之后执行
Around环绕通知,目标方法执行前后都会调用
AfterThrowing 和 After 只会出现一个,不能同时执行
演示:
1.xml配置文件:
<?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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的ioc,把service对象配置进来-->
<bean id="accountService" class="com.service.impl.AccountServiceImpl"></bean>
<!--配置Logger类-->
<bean id="logger" class="com.uiils.Logger"></bean>
<!--配置aop-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置前置通知-->
<aop:before method="beforePrintLog" pointcut="execution(* com.service.impl.*.*(..))"></aop:before>
<!--配置后置通知-->
<aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.service.impl.*.*(..))"></aop:after-returning>
<!--配置异常通知-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* com.service.impl.*.*(..))"></aop:after-throwing>
<!--配置最终通知-->
<aop:after method="afterPrintLog" pointcut="execution(* com.service.impl.*.*(..))"></aop:after>
</aop:aspect>
</aop:config>
</beans>
2.用于记录日志工具类,它里面提供了公共代码
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知Logger类中的pringLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
}
3.测试类:
package com;
import com.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
AccountService as = (AccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
//as.updateAccount(1);
//as.deteteAccount();
}
}
运行结果:
环绕通知:
它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式
xml配置文件:
<?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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的ioc,把service对象配置进来-->
<bean id="accountService" class="com.service.impl.AccountServiceImpl"></bean>
<!--配置Logger类-->
<bean id="logger" class="com.uiils.Logger"></bean>
<!--配置aop-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置环绕通知-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
Logger类:
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。");
}
}
运行结果: