Spring之IOC、AOP和事务
IOC
什么是IOC?如果你回答:Spring是个IOC容器,实现原理是反射,没了。这样证明你没有真正理解IOC。
IOC的意思是控制反转,还有个外号叫依赖注入(DI)。为什么起这么晦涩难懂的名字呢?
首先,我们看没有Spring之前,是怎么写代码的。
class SomeController{ // 传统的new一个对象的方式 private SomeService service = new SomeServiceImpl(); public String doSomeThing(){ return service.getSomeThing(); } }
这种有什么缺点呢?
1. 这个类直接依赖于另一个类的实现(代码耦合)。
2. 如果未来发生了变动,比如变成另一种实现 SomeServiceImplV2,那么我们需要改动代码让它变成一个SomeServiceImplV2对象。
再看用了Spring之后
class SomeController{ // 用了Spring之后 @Autowired private SomeService service; public String doSomeThing(){ return service.getSomeThing(); } }
这样做有什么效果呢?
1. 这个类只知道我依赖你哪一个接口,但是具体实现我就不管了。具体最终的实现类是哪一个,由第三方Spring负责注入。
2. 就算未来发生了变动,我们也不需要改动这部分代码。我们只需要告诉第三方Spring:这个接口的实现类变成V2版本了,你注入的时候注意一下。
这个重要的改变就是——控制反转(以前是我主动去控制依赖关系,现在是我被迫接受依赖关系,而这种关系的转变导致了代码的耦合度降低)
而实现控制反转的方式就是依赖注入——在运行期间,由IOC容器动态地将某种依赖关系注入到对象之中
所以
Spring是IOC理论的实践。通过把代码之间的依赖关系交给Spring来管理,解决了代码耦合的问题。
AOP
Spring另一个特性就是AOP(面向切面编程),又是一个晦涩难懂的名词。
先来看一个例子
引入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.0.RELEASE</version> </dependency>
写个配置类
package com.demo.aop; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @ComponentScan("com.demo.aop") @EnableAspectJAutoProxy public class SimpleConfig { }
写个业务接口
package com.demo.aop; public interface SimpleService { void fun(); }
写个业务实现
package com.demo.aop; import org.springframework.stereotype.Service; @Service public class SimpleServiceImpl implements SimpleService { public void fun(){ System.out.println("业务方法..."); } }
写个日志类
package com.demo.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * 日志类就是一个切面 */ @Aspect @Component public class LogAOP { /** * 具体业务方法就是切入点 */ @Pointcut("execution(* com.demo.aop.*.*(..))") public void aspect(){} /** * 通知(before/after/around) * @param joinPoint * @return * @throws Throwable */ @Around("aspect()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println(">>>>>>>>业务方法之前"); long start = System.currentTimeMillis(); Object proceed = joinPoint.proceed(); long end = System.currentTimeMillis(); System.out.println(">>>>>>>>业务方法之后"); System.out.println(joinPoint + "|Time-Consuming:" + (end-start)); return proceed; } /** * 如果出现异常 * @param joinPoint * @param e */ @AfterThrowing(pointcut = "aspect()", throwing = "e") public void afterException(JoinPoint joinPoint, Exception e){ // TODO } }
最后写一个启动方法
package com.demo.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Main { public static void main(String[] args) throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext(SimpleConfig.class); SimpleService bean = context.getBean(SimpleService.class); bean.fun(); } }
输出:
写这么多,只完成了一件事情——在业务方法前后做一些额外的工作。
那么AOP出现的原因呢?
1. 没有AOP,如果我们想要在一批接口方法执行的前后做一些额外的工作,那么每个方法我们都要写重复的逻辑代码,这可是个体力活。
2. 使用AOP,我们只需要写好额外工作的逻辑,然后配置一下,就可以在一批方法的前后执行我们写好的处理逻辑了。
3. 解决了代码重复的问题,实现了代码复用。
利用AOP可以做很多事情,比如:拦截器、日志、事务管理等等
下面是AOP相关概念
切面(Aspect):要增强的内容,比如上面例子中的日志类
连接点(JoinPoint):从哪里增强,比如上面例子中具体的业务方法就是连接点
切入点(PonitCut):从哪里增强,比如上面例子中具体的业务方法的集合就是切入点
通知(Advise):具体增强的逻辑,比如上面日志类中的方法(@Around标记)
切入点表达式(execution):
常见的比如:
IOC和AOP小结
IOC:对象之间的依赖关系交由容器处理,【降低组件的耦合度】。不用IOC的话,会出现编码依赖问题:程序要分层,层与层之间调用就会存在依赖。一般都是new一个接口的具体实现类,这是硬编码的方式,如果改了需求,还要重新编译class,这也体现不出面向接口编程的好处。最直观的表达就是,IOC让对象的创建不用去new了,而是由spring利用Java的反射机制,根据配置在运行时动态的去创建以及管理对象。
AOP:支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,减少重复代码,从而提供更好的【代码复用】。比如我要在一批方法前后做一些相同的事情,不用AOP的话每个方法前后都要写一堆同样的处理逻辑。
IOC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
Spring事务
数据库-事务特性(ACID)
⑴ 原子性(Atomicity)
一个事务内所有操作共同组成一个原子包,要么全部成功,要么全部失败。⑵ 一致性(Consistency)
数据库事务的一致性就规定了事务提交前后,永远只可能存在事务提交前的状态和事务提交后的状态,从一个一致性的状态到另一个一致性状态,而不可能出现中间的过程态。⑶ 隔离性(Isolation)
事务的隔离性,基于原子性和一致性,每个事务互不干扰。⑷ 持久性(Durability)
持久性,当一个事物提交之后,数据库状态永远的发生了改变。
数据库-隔离级别
⑴ 读未提交
在这种隔离级别下,查询是不会加锁的,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。⑵ 读已提交
只能读到其它事务已经提交的内容。没有查询加锁,但是却能够避免脏读。可能会产生“不可重复读”、“幻读”。⑶ 可重复读
可以防止“脏读”、“不可重复读”。可能会产生“幻读”。⑷ 串行化
在该级别下,事务顺序执行,不仅避免了脏读,不可重复读,而且避免了幻读。由于加锁,导致大量的请求超时,因此性能会比较低下。脏读:一个事务读到另一个事务未提交的数据
不可重复读:一个事务读到另一个事务已提交的更新的数据。【多次读取数据内容不一致】
幻读:一个事务读到另一个事务已提交的新插入的数据。【多次读取数据数量不一致】。
Spring事务传播(7种)
Propagation.REQUIRED:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。
Propagation.SUPPORTS:支持当前事务,如果没有当前事务,就以非事务方法执行。
Propagation.MANDATORY:支持当前事务,如果没有当前事务,就抛出异常。
Propagation.REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
Propagation.NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
Propagation.NEVER:以非事务方式执行操作,如果当前事务存在则抛出异常。
Propagation.NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
具体使用也是非常简单
@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED) public void addXXX(UserRequest request) { // 业务逻辑 }