Spring

Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架

Spring 的特性

  1. 非侵入式: 基于Spring开发的应用中的对象可以不依赖于Spring的API
  2. 依赖注入: DI-Dependency Injection,反转控制(IOC)最经典的实现。 DI 和 IOC的关系就是:IOC是一种思想,DI 是 IOC的具体实现
  3. 面向切面编程 : Aspect Oriented Programming-- AOP
  4. 容器: Spring是一个容器,因为它包含并且管理应用对象的生命周期
  5. 组件化: Spring 实现了使用简单的组件配置组合成一个复杂的应用。在Spring中可以使用XML和Java注解组合这些对象。
  6. 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring自身也提供了表述层的SpringMVC和持久层的Spring JDBC)。

Spring模块

核心容器:由 spring-beans、spring-core、spring-context 和 spring-expression(Spring Expression Language, SpEL) 4 个模块组成。

数据访问及集成:由spring-jdbc、spring-tx、spring-orm、spring-jms 和 spring-oxm 5 个模块组成。

AOP 和设备支持:由 spring-aop、spring-aspects 和 spring-instrument 3 个模块组成。

Web:由 spring-web、spring-webmvc、spring-websocket 和 spring-webflux 4 个模块组成。

 

Spring IOC/DI

IOC(Inversion of Control)控制反转:由spring来负责控制对象的生命周期和对象间的关系。

传统的Java SE程序设计,我们通过new进行创建对象,是程序主动去创建依赖对象;而IOC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;就是把new对象实例化的工作交给spring容器来完成,spring帮我们负责销毁对象,控制对象的生命周期,在需要使用对象的时候直接向spring申请即可。控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

DI(Dependency Injection)依赖注入:组件之间的依赖关系由容器在运行期决定,由容器动态的将某个依赖关系注入到组件之中。

Spring就是通过反射(reflection)来实现依赖注入的。
注入方式:set方式注入、构造器注入、工厂方法注入,注解方式注入

set方式注入:目标对象中需要提供相关的set方法,需要调用set方法将资源传递给目标对象使用。

构造器注入:目标对象中提供带参数的构造方法,通过构造方法将资源传递给目标对象使用。

静态工厂注入:调用静态工厂的方法来获取自己需要的对象。

实例工厂注入:实例工厂的意思是获取对象实例的方法不是静态的,所以你需要首先new工厂类,再调用普通的实例方法

注解方式注入:@Autowired 注解、@Autowired注解

@Autowired注解默认按类型装配(属于Spring),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用。

@Resource注解默认按名称装配(属于J2EE),名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。如果使用type属性时则按照类型进行装配。

@Resource装配顺序
  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
  4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。

Spring AOP

SpringAOP(Aspect Orient Programming) 是一种设计思想,称为面向切面编程,利用横切技术剖析对象内部,将业务之间共同调用的逻辑提取并封装为一个可复用的模块,这个模块被命名为切面(Aspect),该模块减少系统中的重复代码,降低模块间的耦合度,可用于日志、权限认证、事务管理等。

SpringAOP思想的实现一般基于代理模式 ,在Java中采用JDK动态代理模式,但是JDK动态代理模式只能代理接口而不能代理类。因此SpringAOP会在CGLIB、JDK动态代理之间进行切换。

使用JDK动态代理还是CGLIB?

  1. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
  2. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
  3. 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。

相关术语

  • 切面(Aspect):切面是通知和切点的结合,通知和切点共同定义了切面的全部内容,一般使用@Aspect实现切面的定义;
  • 通知(Advice):通知定义了切面是什么以及何时使用,如Before、After;
  • 切点(PonitCut):切点定义了在何处应用连接点(JoinPonit),通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点;
  • 连接点(JoinPonit):连接点是在应用执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出以异常时、甚至修改一个字段时;
  • 目标对象(Target):需要被代理的类,如UserService;
  • 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。

通知类型

  • 前置通知(Before):在目标方法被调用之前调用通知功能;
  • 后置通知(After):在目标方法执行完成之后调用通知,此时不会关心方法的输出是什么;
  • 返回通知(After-returning):在目标方法成功执行之后调用通知;
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知;
  • 环绕通知(Around):在被通知的方法调用之前和调用之后执行自定义的行为。

例:

@Component
@Aspect //定义切面
public class LogAop {

    //定义切点
    @Pointcut("execution(* com.demo.service.*(..))")
    public void ponitCut() {
        //日志、权限等操作...
    }
    
    //前置通知,方法执行之前执行
    @Before("ponitCut()")
    public void beforeAdvice() {
        System.out.println("方法执行之前执行");
    }
    
    //后置通知,方法执行之后执行(不管是否发生异常)
    @After("ponitCut()")
    public void afterAdvice() {
        System.out.println("方法执行之后执行");
    }
    
    //环绕通知。注意要有ProceedingJoinPoint参数传入
    @Around("ponitCut()")
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前");
        pjp.proceed();//执行方法
        System.out.println("环绕后");
    }
    
    //返回通知,方法正常执行完毕之后执行
    @AfterReturning(value="PointcutDeclaration()",returning="result")
    public void AfterReturningMethod(JoinPoint jp,Object result) {
        String methodName = jp.getSignature().getName();
        Object[] args = jp.getArgs();
        System.out.println("AfterReturningMethod  The method   "+ methodName +"   parameter is  "+Arrays.asList(args)+" "+result);
        System.out.println();
    } 

    //异常通知,在方法抛出异常之后执行
    @AfterThrowing(value="PointcutDeclaration()",throwing="e")
    public void AfterThrowingMethod(JoinPoint jp,Exception e) {
        String methodName = jp.getSignature().getName();
        System.out.println("AfterThrowingMethod  The method   "+ methodName +"exception :"+e);
    }
}

 

Spring Transaction:@Transactional

事务是用户定义的一系列数据库操作,是数据库操作的最小工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。

事务的特性

  • 原子性(Atomacity):事务包含的操作要么全部成功,要么全部失败,即使回滚也不会对数据库产生影响。
  • 一致性(Consistency):事务必须使数据从一个一致性状态变换到另一个一致性状态,也就是说当一个系统在一致状态下更新后,系统中所有数据都保持一致。
  • 隔离性(Isolation):当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间相互隔离。
  • 持久性(Durability):事务一旦被提交,对数据库中的数据的改变就是永久性的,即使数据库系统遇到故障也不会丢失提交事务的操作。

事务的管理

  • 编程式事务管理:侵入式事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
  • 声明式事务管理:非侵入式事务管理,声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。

声明事务原理:
AOP编程-环绕通知-方法之前或者之后进行执行

使用声明事务方式,方法一定不要try,将异常抛出去。
业务逻辑层不要使用try,将异常抛出给上一层。或者在catch中throw 异常。
不然在try中内部消化异常,AOP无法捕获异常,就无法执行回滚语句。

事务的传播

  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

事务的隔离

  • TransactionDefinition.ISOLATION_DEFAULT:使用后端数据库默认的隔离级别。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的。

脏读(Dirty read):一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。

不可重复读(Nonrepeatable read):一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。

幻读(Phantom reads):当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。

Spring声明式事务配置属性

@Transactional(propagation=Propagation.REQUIRED) //事务的传播

@Transactional(isolation = Isolation.READ_UNCOMMITTED) //事务的隔离

@Transactional(readOnly=true) //只读事务

@Transactional(timeout=30) //事务超时

@Transactional(rollbackFor={RuntimeException.class, Exception.class}) //事务回滚指定多个异常类

Spring Bean

构成应用程序主干并由Spring IoC容器管理的对象称为Bean。Bean是一个由Spring IoC容器实例化、组装和管理的对象。

Spring中的bean默认都是单例的,Spring的单例是基于 BeanFactory 也就是 Spring容器的,单例Bean在此容器内只有一个,Java的单例是基于 JVM,每个 JVM 内只有一个实例。

Spring Bean 线程安全问题理解:https://segmentfault.com/a/1190000019971145

bean的作用域:@scope("prototype")

类别说明
singleton 在Spring IoC容器中仅存在一个Bean实例,Bean以单例方式存在,默认值,适用于无状态bean
prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行new XxxBean(),适用于有状态的Bean
request 每次HTTP请求都会创建一个新的Bean,该该作用域仅适用于WebApplicationContext环境
session

同一个HTTP Session共享一个Bean,不同Session使用不同Bean,仅适用于WebApplicationContext环境

globalSession 一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境

Spring bean加载过程

 

 

 

Spring bean生命周期

概要流程:

 

 

生命周期扩展详细流程:

 

posted @ 2020-10-14 18:32  柒月丶  阅读(237)  评论(0)    收藏  举报