spring AOP
AOP (Aspect Orient Programming),直译过来就是 面向切面(方面)编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
-
AOP编程可不是Spring独有的,Spring只是支持AOP编程的框架之一。
-
AOP分两类,一类可以对方法的参数进行拦截,一类是对方法进行拦截,SpringAOP属于后者,所以Spring的AOP是属于方法级的
OOP与AOP
-
OOP三大特性?
-
以下问题如果解决?
public void doSameBusiness (long lParam,String sParam){ // 1.记录日志 log.info("调用 doSameBusiness方法,参数是:"+lParam); // 2.输入合法性验证 if (lParam<=0){ throws new IllegalArgumentException("xx应该大于0"); } if (sParam==null || sParam.trim().equals("")){ throws new IllegalArgumentException("xx不能为空"); } // 3.异常处理 try{ 真正的业务处理 }catch(...){ }catch(...){ } // 4.事务控制 tx.commit(); }
AOP带来的好处
AOP是公用的框架代码放置的理想地方,将公共代码与业务代码分离,使我们在处理业务时可以专心的处理业务。
通过使用AOP我们可以将日志记录,数据合法性验证,异常处理等功能放入AOP中,那么在编写业务时就可以专心实现真正的业务逻辑代码。
基本概念
-
连接点(Joinpoint) 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。简单来说,就是在哪加入你的逻辑增强 连接点表示具体要拦截的方法,上面切点是定义一个范围,而连接点是具体到某个方法
-
切点(PointCut) 每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。比如,连接点--数据库的记录,切点--查询条件 切点用于来限定Spring-AOP启动的范围,通常我们采用表达式的方式来设置,所以关键词是范围
-
增强(Advice) 增强是织入到目标类切点上的一段程序代码。在Spring中,像BeforeAdvice等还带有方位信息 通知是直译过来的结果,我个人感觉叫做“业务增强”更合适 对照代码就是拦截器定义的相关方法,通知分为如下几种:
-
前置通知(before):在执行业务代码前做些操作,比如获取连接对象
-
后置通知(after):在执行业务代码后做些操作,无论是否发生异常,它都会执行,比如关闭连接对象
-
异常通知(afterThrowing):在执行业务代码后出现异常,需要做的操作,比如回滚事务
-
返回通知(afterReturning),在执行业务代码后无异常,会执行的操作
-
环绕通知(around),这个目前跟我们谈论的事务没有对应的操作,所以暂时不谈
-
-
目标对象(Target) 需要被加强的业务对象
-
织入(Weaving) 织入就是将增强添加到对目标类具体连接点上的过程。 织入是一个形象的说法,具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。
-
代理类(Proxy) 一个类被AOP织入增强后,就产生了一个代理类。
-
切面(Aspect) 切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。 比如上文讨论的数据库事务,这个数据库事务代码贯穿了我们的整个代码,我们就可以这个叫做切面。 SpringAOP将切面定义的内容织入到我们的代码中,从而实现前后的控制逻辑。 比如我们常写的拦截器Interceptor,这就是一个切面类
AOP运行原理:目标对象只负责业务逻辑,通知只负责AOP增强逻辑(如日志,数据验证等),而代理对象则将业务逻辑而AOP增强代码组织起来(组织者)
Spring AOP
通知类型
-
前置通知
-
后置通知
-
环绕通知
-
返回后通知
-
异常通知
实现
pom.xml
<!--属性,公共配置--> <properties> <spring.version>5.3.6</spring.version> </properties> <dependencies> <!--spring aop--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> </dependencies>
准备工作:
UserDao接口及实现类,UserService接口及其实现类
public class UserDaoImpl implements UserDao { public void save(User user) { // 这里并未实现完整的数据库操作,仅为说明问题 System.out.println("保存用户信息到数据库"); } } public class UserServiceImpl implements UserService { // 声明接口类型的引用,和具体实现类解耦合 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void add(User user) { // 调用用户DAO的方法保存用户信息 userDao.save(user); } }
将service,dao配置到spring_aop.xml配置文件中,以便于被spring管理,按自己的实际情况配置
<bean id="userDao" class="com.zking.springdemo.dao.impl.UserDaoImpl"></bean> <bean id="userService" class="com.zking.springdemo.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/> </bean>
aop增强(通知)
/** * 日志 */ public class LoggerAdvice { private static final Logger log = Logger.getLogger(LoggerAdvice.class); public void before(JoinPoint jp) { log.info("before调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法。方法入参:" + Arrays.toString(jp.getArgs())); } public Object aroundLogger(ProceedingJoinPoint jp){ Object result = null; try { System.out.println("aroundLogger调用之前"); result = jp.proceed();//调用代理目标方法 System.out.println("aroundLogger调用之后"); } catch (Throwable e) { e.printStackTrace(); System.out.println("aroundLogger异常处理"); } return result; } public void afterLogger(JoinPoint jp) { log.info("afterLogger:"+jp.getSignature().getName() + " 方法结束执行。"); } public void afterReturning(JoinPoint jp, Object result) { log.info("afterReturning调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法。方法返回值:" + result); } /** * RuntimeException是抛出的异常的父类 */ public void afterThrowing(JoinPoint jp,RuntimeException e){ System.out.println("afterThrowing"+e.getMessage()); } }
JoinPoint
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.
方法名 功能 Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 Object[] getArgs(); 获取传入目标方法的参数对象 Object getTarget(); 获取被代理的对象 Object getThis(); 获取代理对象
ProceedingJoinPoint
ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中 添加了
Object proceed() throws Throwable //执行目标方法
aop配置
在spring_aop.xml中添加aop相关配置,注意先添加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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userDao" class="com.zking.springdemo.dao.impl.UserDaoImpl"></bean> <!--1.配置目标--> <bean id="userService" class="com.zking.springdemo.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/> </bean> <!--2.配置增强--> <bean id="loggerAdvice" class="com.zking.springdemo.aop.LoggerAdvice"/> <!-- aop配置--> <aop:config proxy-target-class="false"> <!-- 定义切入点 execution(方法返回类型 包名.类名.方法名(参数的类型))--> <aop:pointcut id="pointcut" expression="execution(* com.zking.springdemo.service..*.*(..))" /> <!-- 引用包含增强方法的Bean --> <aop:aspect ref="loggerAdvice"> <!-- 指定名为before方法作为前置通知 --> <aop:before method="before" pointcut-ref="pointcut"/> <!--返回后通知--> <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result" /> <!--后置通知--> <aop:after method="afterLogger" pointcut-ref="pointcut"/> <!--环绕通知--> <aop:around method="aroundLogger" pointcut-ref="pointcut"/> <!--异常通知--> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/> </aop:aspect> </aop:config> </beans>
aop:config aop装配
proxy-target-class默认值为false,默认采用JDK动态代理实现;否则使用cglib来实现
aop:pointcut:点定切点
execution():中是AspectJ中的exection表达式
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?) 除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。
service..
为service当前包及其下所有子包
service..*.*
为service包及其下所有子包下所有类及所有方法
(..)
方法任意参数多个包中间用||或or隔开
aop:aspect
ref:指定增强处理类
aop:指定位置,
有五个候选字
before:前置通知,目标方法运行之前调用
alter-Returning:后置通知,在目标方法运行之后调用
around:环绕通知在目标方法之前和之后都调用
after-throwing:异常拦截通知,如果出现异常,就会调用
after:后置通知,在目标方法运行之后调用
method:选定要织入到切入点的通知类中的方法。
pointcut-ref:选定切入点依赖对象,值为配置切入点时的id属性。