AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往应用在权限校验、日志、事务控制、性能监控中。
名词 | 解释 |
---|---|
Joinpoint(连接点) | 它指的是那些可以用于把增强代码加入到业务主线中的点,这些点指的就是方法。在方法执行的前后通过动态代理技术假如增强的代码。在Spring框架AOP思想的技术实现中,也只支持方法类型的连接点。 |
Pointcut(切入点) | 它指的是那些已经把增强代码加入到业务主线进来之后的连接点。在项目中判断是否有访问权限就是一个连接点 |
Advice(通知/增强) | 它指的是切面类中用于提供增强功能的方法,并且不同的方法增强的时机是不一样的。比如,开启事务肯定要在业务方法执行之前执行;提交事务要在业务方法正常执行之后执行,而回滚事务要在业务方法执行产生异常之后执行等等。那么这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知 |
Target(目标对象) | 它指的是代理的目标对象。即被代理对象。 |
Proxy(代理) | 它指的是一个类被AOP织如增强后,产生的代理类。即代理对象。 |
Weaving(织入) | 它指的是把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。 |
Aspect(切面) | 它指定是增强的代码所关注的方面,把这些相关的增强代码定义到一个类中,这个类就是切面类。例如:事务切面,它里面定义的方法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务无关的方法。 |
连接点:方法开始时、结束时、正常运行完毕时、方法异常时等这些特殊的时机点,我们称之为连接点,项目中每个方法都有连接点,连接点也是一种候选点
切入点: 指定AOP思想想要影响的具体方法时哪些
Advice增强:
第一个层次:指的是横切逻辑
第二个层次:方位点(在某一些连接点上加入横切逻辑,那么这些连接点就叫做方位点)
Aspect切面:切面概念是对上述概念的一个综合
切面 = 切入点 + 增强
= 切入点(锁定方法)+ 方位点(锁定方法中的特殊时机)+横切逻辑
众多的概念,目的就是为了锁定要在哪个地方插入什么横切逻辑代码
Spring实现AOP思想使用的是动态代理技术
默认情况下,Spring会根据被代理对象是否实现接口来选择使用JDK还是CGLIB。当被代理对象没有实现任何接口时,Spring会选择CGLIB。当被代理对象实现了接口,Spring会选择JDK官方的代理技术,不过我们可以通过配置的方式,让Spring强制使用CGLIB。
在Spring的AOP配置中,也和IoC配置一样,支持3类配置方法
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.3.15version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.7version>
dependency>
<bean id="logUitls" class="com.lxsh.utils.LogUtils">bean>
<aop:config>
<aop:aspect id="idAdvice" ref="logUtils">
<aop:before method="printLog" pointcut="execution(public * com.lxsh.service.*.*(..))">aop:before>
aop:aspect>
aop:config>
细节
关于切入点表达式
上述配置实现了对service包下任意方法进行增强,在其执行之前,输出了记录日志的语句。在这里,我们接触了一个比较陌生的名称:切入点表达式,它是做什么的呢?我们继续往下看。
概念及作用
切入点表达式,也称之为AspectJ切入点表达式,**指的是遵循特定语法结构的字符串,其作用是用于对符合语法格式的连接点进行增强。**他是AspectJ表达式的一部分。
关于AspectJ
AspectJ是一个基于Java语言的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切入点表达式的部分,开始支持AspectJ切入点表达式。
切入点表达式的使用示例
全限定方法名 访问修饰符 返回值 包名.包名.包名.类名.方法名(参数列表)
全匹配方式
public void com.lxsh.service.UserService.saveUser(com.lxsh.po.User)
访问修饰符可以省略
void com.lxsh.service.UserService.saveUser(com.lxsh.po.User)
返回值可以用 * 表示任意返回值
* com.lxsh.service.UserService.saveUser(com.lxsh.po.User)
包名可以使用 . 表示任意包,但是有几级包,必须写几个
* ...UserService.saveUser(com.lxsh.po.User)
包名可以使用 .. 表示当前包及其子包
* ..UserService.saveUser(com.lxsh.po.User)
类名和方法名,都可以用 . 表示任意类、任意方法
* ...(com.lxsh.po.User)
参数列表,可以使用具体类型
基本类型直接写类型名称:int
引入类型必须写全限定类型:java.lang.String
参数列表可以使用*,表示任务参数类型,但是必须有参数
* *..*.*(*)
参数列表也可以使用 .. 表示有无参数均可,有参数可以是任意类型
* *..*.*(..)
全通配方式
* *..*.*(..)
改变代理方式的配置
在前面我们已经说了,Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择的。被代理对象实现了接口,则采用基于接口的动态代理。当被代理对象没有实现任何接口的时候,Spring会自动切换到基于子类的动态代理方式。
但是我们都知道,无论被代理对象是否实现接口,只要不是final修饰的类都可以采用CGLIB提供的方式创建代理对象。所以Spring也考虑到了这个情况,提供了配置的方式实现强制使用基于子类的动态代理(即CGLIB的方式),配置方式有两种
<aop:config proxy-target-class="true">aop:config>
<aop:aspectj-autoproxy proxy-target-class="true">aop:aspectj-autoproxy>
五种通知类型
<aop:before method="beforeMethod" pointcut-ref="pt">aop:before>
执行时机
前置通知永远都会在切入点方法(业务核心方法)执行之前执行。
细节
前置通知可以获取切入点方法的参数,并对其进行增强。
<aop:after-returning method="returningMethod" pointcut-ref="pt">aop:after-returning>
执行时机
后置通知的执行时机时在切入点方法正常执行之后执行(业务核心方法),当切入点方法执行产生异常之后,后置通知就不再执行了,而是执行异常通知。
细节
后置通知既可以获取切入点方法的参数,也可以获取到切入点方法的返回值。
<aop:after-throwing method="throwingMethod" pointcut-ref="pt">aop:after-throwing>
执行时机
异常通知的执行时机时在切入点方法(业务核心方法)执行产生异常之后,异常通知执行,如果切入点方法执行没有产生异常,则异常通知不会执行。
细节
异常通知不仅可以获取切入点方法执行的参数,也可以获取切入点方法执行产生的异常信息。
<aop:after method="afterMethod" pointcut-ref="pt">aop:after>
执行时机
最终通知的执行时机时在切入点方法正常执行之后执行(业务核心方法),切入点方法返回之前执行,换句话说,无论切入点方法执行是否产生异常,他都会在返回之前执行。
细节
最终通知执行时,可以获取到通知方法的参数,同时它可以做一些清理操作。
<aop:around method="aroundMethod" pointcut-ref="pt">aop:around>
特别说明
环绕通知,它是有别于前面四种通知类型外的特殊通知,前面四种通知(前置、后置、异常和最终)它们都是指定何时增强的通知类型。而环绕通知,它是Spring框架为我们提供的一种可以通过编码的方式,控制增强代码何时执行的通知类型,它里面借助的ProceedingJoinPoint接口及其实现类,实现手动触发切入点方法的调用。
执行时机
当配置环绕通知之后,在环绕通知里面必须要明确调用业务层的方法,如果不调用,就会出现只出现通知,而不执行方法。其中原理和动态代理是一样的
细节
环绕通知可以代替其他的四个通知,所以环绕通知不可以与其他四种通知共用。
基于xml模式的演进
/**
* @Aspect注解 开启切面
*/
@Component
@Aspect
public class LogUtils{
/**
* 配置切入点
*/
@Pointcut("execution(public * com.lxsh.service.*.*(..))")
public void pointCut(){
}
/**
* 业务逻辑开始之前
*/
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
//方法参数
Object[] args = joinPoint.getArgs();
System.out.println("业务逻辑开始之前执行...")
}
/**
* 业务逻辑结束时执行(无论异常与否)
*/
@After("pointCut()")
public void afterMethod(JoinPoint joinPoint){
System.out.println("业务逻辑结束时执行(无论异常与否)...")
}
/**
* 业务异常时执行
*/
@AfterThrowing("pointCut()")
public void throwingMethod(JoinPoint joinPoint){
System.out.println("异常时执行...")
}
/**
* 业务逻辑正常时执行
*/
@AfterReturning(value = "pointCut()" returning ="retVal")
public void sucessMethod(Object retVal){
System.out.println("业务逻辑正常时执行...")
}
/**
* 环绕通知
*/
@Around("pointCut()")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("环绕通知中的before method...")
Object result = null
try{
result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
System.out.println("环绕通知中的beforemethod...")
}catch(Exception e){
System.out.println("环绕通知中的throwing method...")
}finally{
System.out.println("环绕通知中的after method...")
}
System.out.println("环绕通知中的afterReturning method...")
return result;
}
}
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
基于xml+注解模式的演进
在启动类/配置类上加入以下注释即可
@EnableAspectJAutoProxy
**编程式事务:**在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
**声明式事务:**通过XML或者注解配置的方式达到事务控制的目的,叫做声明式事务
事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功,从而确保了数据的准确与安全。
例如:A–>B转账,对应如下两条sql语句:
/*转出账户减钱*/
update account set money = money-100 where name = 'A';
/*转入账户加钱*/
update account set money = money+100 where name = 'B';
这两条语句的执行,要么全部成功,要么全部不成功。
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么多发生,要么都不发生。
从操作的角度来描述,事务中的各个操作要么都成功要么都失败
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态
例如转账前A有1000,B有1000,转账后A+B也的是2000.
一致性时从数据的角度来说的。(1000,1000) (900,1100) 不应该出现(900,1000)
隔离性(isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务。
每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
比如:事务1给员工涨工资2000,但是事务1尚未被提交,员工发起事务2查询工资,发现工资涨了2000块钱,读到了事务1尚未提交的数据(脏读)
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性,接下来即使数据库发生故障也不应该对其有任何影响。
不考虑隔离级别,会出现以下情况:(以下情况全是错误的),也即为隔离级别在解决事务并发问题
数据库共定义了四种隔离级别:
Serializable(串行化):可避免脏读,不可重复读,虚读情况的发生。(串行化) 最高
Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。(幻读有可能发生) 第二
该机制下会对update的行进行加锁
Read committed(读已提交):可避免脏读情况发生,不可重复读和幻读一定会发生。 第三
Read uncommitted(读未提交):最低级别,以上情况均无法保证(读未提交) 最低
注意:级别依次升高,效率依次降低
MySQL的默认隔离级别是:REPEATABLE READ
查询当前使用的隔离级别:select @@tx_isolation;
设置MySQL事务的隔离级别:set session transaction isloation level xxx;(设置的是当前mysql连接会话的,并不是永久改变的)
事务往往在service层进行控制,如果出现service层方法A调用了另外一个service层方法B,A和B方法本身都已经被添加了事务控制,那么A调用B的时候,就需要进行事务的一些协商,这就叫做事务的传播行为。
A调用B,我们站在B的角度来观察来定义事务的传播行为(前两种情况最常用)
行为 | 描述 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 |
PlatformTransactionManager
public interface PlatformTransactionManager{
/**
* 获取事务状态信息
*/
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
}
/**
* 提交事务
*/
void commit(TransactionStatus status) throws TransactionException;
/**
* 回滚事务
*/
void rollback(TransactionStatus status) throws TransactionException;
作用
此接口是Spring的事务管理器核心接口。Spring本身并不支持事务实现,只是负责提供标准,应用底层支持什么样的事务,需要提供具体实现类。此处也是策略模式的具体应用给。在Spring框架中,也为我们内置了一些具体策略,例如:DataSourceTransactionManager,HibernateTransactionManager等等(HibernateTransactionManager事务管理器在spring-orm-5.1.12.RELEASE.jar中)
SpringJdbcTemplate(数据库操作工具)
DataSourceTransactionManager(Mybatis) 归根结底是横切逻辑代码,声明式事务要做的就是使用AOP(动态代理)将事务控制逻辑织入到业务代码
基于XML+注解
- 事务的必要坐标
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.15version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.7version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.3.15version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.3.15version>
dependency>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
@Transaction(readOnly=true,propagation=Propagation.SUPPORTS)
基于纯注解
Spring基于注解驱动开发的事务控制配置,只需要把xml配置部分改为注解实现,只是需要一个注解替换掉xml配置文件中的
配置。
在Spring配置类上添加@EnableTransactionManagement注解即可
//开启Spring注解事务的支持
@EnableTransactionManagement
public class SpringConfiguration{
}