Spring-Aop
AOP:面向切面编程,AOP对OOP补枪;
- OOP:宏观层面的思想,对项目的整体架构
- AOP:站在方法的层面,不改变原来的代码基础,动态增强原来的方法
OOP:在编程的过程中会将重复的功能代码剥离出来,单独写一个方法,或者类,然后需要使用的时候在调用。提高了代码复用率,提升编程体验,但是还是有一定的耦合与不便。
AOP:站在OOP巨人的肩膀上,在不需要修改源码的基础上,给程序动态同意添加额外功能的一种技术。
AOP的优点
- 在不改变核心代码的前提下,动态的添加额外的功能
- 业务逻辑与非业务逻辑的解耦
- 解耦后可专心的实现某个功能
- 业务与非业务功能性能灵活组装(通过配置文件,或者注解)
- AOP面向切面编程:我们实现业务层的时候,业务层除了本身的逻辑代码,还需要其他的权限验证(先验证使用功能的权限在考虑是否放行),事物控制(逻辑代码中涉及到的所有sql语句,必须由事物控制,保证数据的安全性),日志记录(每个行为都应该有日记的记录)等等这些功能。
- 并且这些功能都是每个功能最基本的需求,也就是需要剥离出来,封装成代码,但是传统的OOP会带来一个问题,逻辑代码与这些功能还是有直接耦合,需要修改,或者动态的选择性调用这些涉及到修改源代码,这是我们所不能接受的。
- 因此AOP跨出了伟大的一步,将逻辑代码这一块看做一个功能,一个切面,其他额外的功能依附在切面上,不对该逻辑代码进行改变,降低了入侵性,完成了解耦。
- 将业务层逻辑代码比作是一面墙,spring中的配置文件或者注解相当于一个可以占在墙上的挂钩,而像权限验证、事物控制、日志记录相当于不同衣服,所以这面墙上就有三件衣服,承担不同的功能,想要不同的衣服,那就添加配置,在添加功能,如果不需要了将衣服取下来,粘钩拿掉,这面墙还是之前的那面墙没有什么变化。
AOP基于XML配置文件方法
-
导入Aop的相关依赖:为了开发的便捷使用的是基于Spring的aspectj
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <!--spring-aop在spring-context已经传递--> <!--aspectJ的依赖: 会在spring-aspects传递--> <!-- <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency>--> <!--spring整合aspectJ的依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.36</version> </dependency>
-
编写通知类
- before:前置增强
- afterReturning:后置增强
- afterThrowing:异常增强
- after:最终增强
- aroud:环绕增强(包括前面四种)
-
在Spring配置文件中的配置
-
把通知类交给spring管理
@Component("bizLogger") public class BizLogger {
-
在spring配置文件中配置Aop的相关配置
<!--把增强类(通知类)与目标类进行织入 --> <aop:config> <!--切入点: 对那些类那些方法进行增强 属性: id: 唯一标志符 public void com.fs.aop.service.impl.UserServiceImpl.addUser(参数列表) throws Exception public void com.fs.aop.service.impl.*.*(..) throws Exception expression:表达式 切入点表达式 语法: execution(修饰符? 返回值 方法名(参数) 异常?) 语法: ?: 0次或者1次 通配符: *: 任意 ..:参数列表中使用, 任意多个任意类型的参数 --> <aop:pointcut id="pointcut1" expression="execution(* com.fs.aop.service..*.*(..))"/> <!--织入 通知类方法与目标方法进行组装 5类通知: 前置通知 --> <!--通知类 ref: 通知类的bean的id --> <!--基于AspectJ的配置--> <aop:aspect ref="bizLogger"> <!--前置通知 method: 通知类的那个方法作为前置通知, 方法名 pointcut-ref: 切入点的id --> <!-- <aop:before method="before" pointcut-ref="pointcut1"></aop:before>--> <!--后置通知: 目标方法正常执行之后,返回结果 的增强 returning: 把目标方法的返回值 赋值给afterReturning那个参数, 参数名 --> <!-- <aop:after-returning returning="result" method="afterReturning" pointcut-ref="pointcut1"></aop:after-returning>--> <!--最终增强--> <!--<aop:after method="after" pointcut-ref="pointcut1"></aop:after>--> <!--异常增强 throwing: 把目标方法抛出异常对象赋值给增强方法那个参数接收 参数名 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut1" throwing="e"></aop:after-throwing> <!--环绕增强--> <aop:around method="aroundLogger" pointcut-ref="pointcut1"></aop:around> </aop:aspect> </aop:config>
-
AOP基于注解的方式
-
在通知类上添加一个注解,表示它是通知类(@Aspect)
-
在增强的方法上添加对应的增强类型注解
package com.fs.aop.log; import java.sql.SQLException; import java.util.Arrays; import lombok.extern.slf4j.Slf4j; import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /** * 日志增强类 */ @Slf4j @Component("bizLogger") @Aspect //表示该类是一个通知类 public class BizLogger { /** * 前置增强 * JoinPoint: 连接点 addUser()方法对象 * UserServiceImpl的addUser(User user) * getTarget(): 目标方法的目标对象 UserServiceImpl对象 * getSignature(): 目标方法 addUser() * getArgs(): 目标方法的参数的值 * @param jp */ @Before("execution(* com.fs.aop.service..*.*(..))") public void before(JoinPoint jp){ log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName() +"方法,传递的参数:"+Arrays.toString(jp.getArgs())); } /** * 后置增强 * @param jp * @param result 返回值 */ @AfterReturning(value = "execution(* com.fs.aop.service..*.*(..))",returning = "result") public void afterReturning(JoinPoint jp,Object result){ log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName() +"方法,方法的返回值:"+result); } /** * 异常增强 * @param jp * @param e 增强的异常类型 * SQLException: 这个异常增强只对目标方法抛出异常对象类型SQLException以及子类才执行 */ @AfterThrowing(value = "execution(* com.fs.aop.service..*.*(..))",throwing = "e") public void afterThrowing(JoinPoint jp,Exception e){ log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName() +"方法发生异常"+e); } /** * 最终增强 * @param jp */ @After("execution(* com.fs.aop.service..*.*(..))") public void after(JoinPoint jp){ log.info(jp.getSignature().getName()+"方法执行结束"); } /** * 环绕增强 * @param jp * ProceedingJoinPoint是JoinPoint子类 * proceed()方法: 调用这个方法, 调用目标方法, 没有调用, 目标方法都不执行 * @throws Throwable */ @Around("execution(* com.fs.aop.service..*.*(..))") public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable{ log.info(jp.getSignature().getName()+"方法开始执行"); try { //执行目标方法 Object rs = jp.proceed(); log.info(jp.getSignature().getName()+"方法正常执行完"); return rs; } catch (Exception e) { log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName() +"方法发生异常"+e); throw e; }finally { log.info(jp.getSignature().getName()+"方法执行完"); } } }
-
找到spring的配置文件,开启aspectj注解
<!--开启aspectJ注解扫描--> <aop:aspectj-autoproxy/>
ps:Spring整合junit
-
导入Spring-text和junit依赖
<!--spring-test: 整合测试--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.15.RELEASE</version> </dependency>
-
在测试类上添加两个注解: 创建Spring容器, 把Spring容器bean注入到测试类
//测试类在spring环境下运行, 创建一个Spring容器 @RunWith(SpringJUnit4ClassRunner.class) //加载spring配置文件:创建一个Spring容器 @ContextConfiguration("classpath:beans.xml") //指定配置文件