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>
1. aop:config aop装配
-
proxy-target-class默认值为false,默认采用JDK动态代理实现;否则使用cglib来实现
2. aop:pointcut:点定切点
execution():中是AspectJ中的exection表达式
1) execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?) 除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。
2) serviece..
为service当前包及其下所有子包
3) serviece..*.*
为service包及其下所有子包下所有类及所有方法
4)(..)方法任意参数
5) 多个包中间用||或or隔开
3. aop:aspect
ref:指定增强处理类
4. aop:指定位置
有五个候选字
1)before:前置通知,目标方法运行之前调用
2)alter-Returning:后置通知,在目标方法运行之后调用
3)around:环绕通知在目标方法之前和之后都调用
4)after-throwing:异常拦截通知,如果出现异常,就会调用
5)after:后置通知,在目标方法运行之后调用
method:选定要织入到切入点的通知类中的方法。
pointcut-ref:选定切入点依赖对象,值为配置切入点时的id属性。