什么是SpringAOP
- AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
- AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,
- 是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP 的作用及其优势
- 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
- 优势:减少重复代码,提高开发效率,并且便于维护
为什么要用AOP
让我们来看一个案例:
有一个学生类:
public class Student implements Serializable {
private int sid ;
private String sname;
private String ssex;
private int classid;
private int money;
// get/set 方法省略
}
有一个业务需要学生之间相互转账
public void tanfer(int sid1, int sid2, int money) {
Student stu1 = dao.findStuById(sid1);
Student stu2 = dao.findStuById(sid2);
stu1.setMoney(stu1.getMoney()-money);
stu2.setMoney(stu2.getMoney()+money);
dao.updateStudent(stu1);
int i=1/0; //异常模拟
dao.updateStudent(stu2);
}
如果在修改数据时出现某种异常,导致转账失败 ,就会出现违背一致性原则的情况 ,我们就需要去做事务管理 :
所以我们创建一个事务管理工具类:
public class transationUtil {
// 装配连接工具
connectionUtil connectionUtil;
public void setConnectionUtil(connectionUtil connectionUtil) {
this.connectionUtil = connectionUtil;
}
// 开启事务
public void beginTran(){
try {
connectionUtil.getCon().setAutoCommit(false);
System.out.println("开启事务");
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
// 提交事务
public void commitTran(){
try {
connectionUtil.getCon().commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
// 回滚事务
public void rollbackTran(){
try {
System.out.println("回滚了");
connectionUtil.getCon().rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
// 关闭事务
public void closeTan(){
try {
connectionUtil.getCon().close();
connectionUtil.closeCon();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
再修改业务方法:
public void tanfer(int sid1, int sid2, int money) {
try {
tranutil.beginTran();
Student stu1 = dao.findStuById(sid1);
Student stu2 = dao.findStuById(sid2);
stu1.setMoney(stu1.getMoney()-money);
stu2.setMoney(stu2.getMoney()+money);
dao.updateStudent(stu1);
int i=1/0; //异常模拟
// 违背一致性原则 需要去做事务管理
dao.updateStudent(stu2);
tranutil.commitTran();
}catch (Exception e){
e.printStackTrace();
tranutil.rollbackTran();
}finally {
tranutil.closeTan();
}
}
但是, 一个业务中, 做增删改的方法有很多,每一个方法都用这样的try /catch 管理事务, 不仅会显得代码过于冗余,而且还会污染代码, 我们希望业务中只包含业务方法
不仅如此,我们发现我们无法用面向对象的思想去优化代码, 只能去通过抽取横向的代码成一个公共的模块供其他类使用. 所以,就需要我们用到AOP的思想去优化代码:
AOP术语
AOP通知类型
AOP将抽取出来的共性功能称为通知;通知类型:以通知在上下文中的具体位置作为划分
前置通知(Before)
后置通知(After)
返回通知(After-returning)
异常通知(After-throwing)
环绕通知(Around)
AOP连接点(Join point)
AOP将所有的方法都视为连接点,不管是接口里面的抽象方法,还是实现类里面的重写方法,都是连接点
AOP切点(Pointcut)
AOP将可能被抽取共性功能的方法称为切入点。切入点是连接点的子集
AOP目标对象(Target):
就是挖掉功能的方法对应的类生的对象,这种对象是无法直接完成最终工作的
AOP织入(Weaving):
就是将挖掉的功能回填的动态过程
AOP切面(Aspect):
切点+通知
AOP的实现步骤(XML)
1.添加依赖
aop与aspectj表达式的依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
2.创建spring的主配置文件
bean内的命名空间要添加aop的
<?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">
3.创建业务代码并编写日志记录代码(事务管理代码)
业务
public class ServiceImp implements IService {
@Override
public void del() {
System.out.println("删除");
int i=1/0;
}
@Override
public int find(int i) {
System.out.println("单查");
return i;
}
@Override
public void update() {
System.out.println("修改");
}
}
日志记录
public class mylog {
public void beforeMethod(){
System.out.println("===前置通知===");
}
public void returnMethod(){
System.out.println("===返回通知===");
}
public void throwMethod(){
System.out.println("===异常通知===");
}
public void afterMethod(){
System.out.println("===后置通知===");
}
public Object aroundMethod(ProceedingJoinPoint pjp){
Object proceed=null;
try {
System.out.println("===环绕的前置通知===");
proceed = pjp.proceed(pjp.getArgs());
System.out.println("===环绕的返回通知===");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("===环绕的异常通知===");
}finally {
System.out.println("===环绕的后置通知===");
}
return proceed;
}
}
4.将业务层与日志记录层注入spring容器
<bean id="service" class="com.ape.service.ServiceImp"></bean>
<bean id="mylog" class="com.ape.util.mylog"></bean>
5. 配置文件
aop:config--aop配置
aop:aspect--aop切面
aop:before--通知内容与通知类型
aop:before -- 前置通知 (主业务之前)
aop:after-returning -- 返回通知 (主业务结束且无异常时)
aop:after-throwing -- 异常通知 (主业务出现异常)
aop:after -- 后置通知 (无论是否出现异常都会执行)
aop:around --环绕通知 (上述四个通知的结合) (常用)
<!-- 配置AOP-->
<aop:config>
<!-- 配置切面-->
<aop:aspect id="AopAspect" ref="mylog">
<!-- 切点 -->
<!-- expression: 切点位置 -->
<aop:pointcut id="pointcut" expression="execution(* com.ape.service.*.*(..))"/>
<!-- 通知 -->
<!-- method:通知的方法 pointcut-ref:切点关联 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
<aop:after-returning method="returnMethod" pointcut-ref="pointcut"></aop:after-returning>
<aop:after-throwing method="throwMethod" pointcut-ref="pointcut"></aop:after-throwing>
<aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
<!-- 环绕通知-->
<!-- <aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>-->
</aop:aspect>
</aop:config>
6.运行结果
无异常
有异常
AOP的实现步骤(注解)
1.添加注解驱动
<!-- AOP注解驱动-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.日志记录方法添加注解
@Pointcut 切点
@Before 前置通知
@AfterReturning 返回通知
@AfterThrowing 异常通知
@After 后置通知
@Component
@Aspect
public class mylog {
// 切点
@Pointcut("execution(* com.ape.service.*.*(..))")
public void pointCut(){}
// 通知
@Before("pointCut()")
public void beforeMethod(){
System.out.println("===前置通知===");
}
@AfterReturning("pointCut()")
public void returnMethod(){
System.out.println("===返回通知===");
}
@AfterThrowing("pointCut()")
public void throwMethod(){
System.out.println("===异常通知===");
}
@After("pointCut()")
public void afterMethod(){
System.out.println("===后置通知===");
}
}
@Around 环绕通知 (效果和上面的一样)
@Around("pointCut()")
public Object aroundMethod(ProceedingJoinPoint pjp){
Object proceed=null;
try {
System.out.println("===环绕的前置通知===");
proceed = pjp.proceed(pjp.getArgs());
System.out.println("===环绕的返回通知===");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("===环绕的异常通知===");
}finally {
System.out.println("===环绕的后置通知===");
}
return proceed;
}
运行结果和XML版一样
总结
AOP(Aspect-oriented programming:面向切面编程):
将那些与业务无关,却为业务模块所共同调用的逻辑(例如事务处理、日志管理、权限控制等)封装抽取成一个可重用的模块,这个模块被命名为“切面”( Aspect ),便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性;
Spring AoP 基于动态代理实现:
如果被代理的对象,已经实现某个接口,则spring AoP会使用(JDK 动态代理的核心是 InvocationHandler 接和类)Proxy JDK Proxy(反射),基于接口的方式,创建代理对象
如果被代理的对象,没有实现某个接口,就无法使用JDK Proxyer 类)去进行代理了,这时候springAop 会使用cglib , 基于继承的方式,生成一个被代理对象的子类来作为代理(cglib 动态代理的核心是 methodInterceptor 接口和 Enhanc