Spring篇

一款开源免费的企业级的轻量型的JAVA框架。支持事务处理。主要有两个核心特性,也就是控制反转(Inversion of Control,IOC)和面向切面编程(aspect-oriented programming,AOP)。

1、七大组件

img

1.1、核心容器(Core)

核心容器提供Spring框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。

1.2、面向切面模块(AOP)

通过配置管理特性,Spring AOP模块直接将面向方面的编程功能集成到了Spring框架中。所以,可以很容易地使Spring框架管理的任何对象支持AOP。Spring AOP模块为基于Spring 的应用程序中的对象提供了事务管理服务。通过使用Spring AOP,不用依赖EJB 组件,就可以将声明性事务管理集成到应用程序中。

1.3、对象/关系映射集成模块(ORM)

Spring框架插入了若干个ORM框架,从而提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有这些都遵从Spring的通用事物和DAO异常层次结构。

1.4、JDBC抽象和DAO模块(DAO)

JDBC、DAO的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。

1.5、Web模块(Web)

Web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供了上下文。所以Spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

1.6、应用上下文模块(Context)

Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。

1.7、MVC模块(Web MVC)

MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。MVC容纳了大量视图技术,其中包括JSP、POI等。

2、IoC控制反转

它是一种编程思想。把对象的创建,初始化,销毁等工作交给Spring容器来做。由Spring容器控制对象的生命周期。

3、DI依赖注入

IoC的一种具体实现。在容器使用不同的注入方式去创建对象的过程就是依赖注入。就是将应用程序依赖的某个对象注入到Spring容器当中去。

三种注入方式:

  • 构造方法注入:接受注入的类中定义一个构造方法,并在构造方法参数中定义需要注入的元素。
  • setter方法注入:接受注入的类中定义Set方法,并在方法参数中定义需要注入的元素。
  • 接口注入:接口中定义要注入的信息,并通过接口完成注入。

4、Spring容器的启动流程

  1. 解析spring配置文件,解析其中配置的<bean>解析到BeanDefinition,存入beanFactory。把BeanDefinition中的定义读取到一个map集合中管理起来了,但还没有创建 bean 的单例对象。
  2. 执行beanFactory的后处理器。
  3. 接下来,由beanFactory创建每个类对应的单例对象, 利用了反射根据类名创建每个类的实例对象(构造,初始化方法)。
  4. 执行bean的后处理器, 它其中有两个方法,会在bean的初始化方法前后被调用会把这些结果 存入beanFactory的singletonObjects这样一个map集合里。
  5. 执行依赖注入,主要为了解决循环引用问题。
  6. 容器.getBean("bean id") 根据bean的id,把bean id当做key,进入singletonObjects获取值,返回Bean的实例结果。

5、Bean

5.1、生命周期

在默认情况之下,Bean是单例模式,容器加载的时候创建实例,可以通过设置lazy-init属性为true来让第一次获取的时候创建实例;也可以通过设置scope属性来改变模式,例如设置值为prototype变为原型模式(指每次获取都创建新的对象)。对象在容器卸载的时候会销毁。可以设置初始化以及销毁的生命周期方法来执行一些额外操作。

5.2、作用域

类别 说明
singleton(单例模式) 作用域默认值。每次从Spring容器中获取某一个类型的Bean实例都是同一个对象。
propertype(原型模式) 每次从Spring容器中获取某一个类型的Bean实例都是不同的对象。
request(请求模式) 不同的HTTP请求从Spring容器中获取某一个类型的Bean实例都是不同的对象。只有基于web的Spring ApplicationContext可用。
session(会话模式) 不同的会话从Spring容器中获取某一个类型的Bean实例都是不同的对象。只有基于web的Spring ApplicationContext可用。
globalSession(全局会话模式) 和上述的session类似,不过作用域限定范围在globalSession中。只有基于web的Spring ApplicationContext可用。

5.3、Bean的装配

三种装配方式:

  • 在xml中显式的配置
  • 在java中显式的配置
  • 隐式的自动装配

5.3.1、手动装配

通过手动去注入Bean的属性值的方式来完成Bean的装配。

<!-- 手动装配Bean -->
<bean id="address" class="com.roger.spring.beans.Address"
  p:city="WhuHan" p:street="Bayi"></bean>
         
<bean id="car" class="com.roger.spring.beans.Car"
  c:brand="Audi" c:price="300000"></bean>
         
<bean id="person" class="com.roger.spring.beans.Person"
  p:name="Roger" p:address-ref="address" p:car-ref="car" />
  • p命名注入:通过setter方法注入。
  • c命名注入:通过构造方法注入。

5.3.2、自动装配

Spring会在上下文中自动寻找合适的Bean,并自动给Bean装配属性。以下是自动装配的4中方式:

5.3.2.1、no方式(默认)
<bean id="address" class="com.figsprite.bean.autowire.Address"  
      p:city="上海" p:street="陆家嘴" /></bean>
    
<bean id="car" class="com.figsprite.bean.autowire.Car"  
      p:brand="奥迪" p:price="10000" /></bean>
    
<bean id="person" class="com.figsprite.bean.autowire.Person"
      p:name="Roger" p:address-ref="address" p:car-ref="car"
      autowire="no" />

(默认)无自动装配。

5.3.2.2、byName方式
<bean id="address" class="com.figsprite.bean.autowire.Address"  
      p:city="上海" p:street="陆家嘴" /></bean>
    
<bean id="car" class="com.figsprite.bean.autowire.Car"  
      p:brand="奥迪" p:price="10000" /></bean>
    
<bean id="person" class="com.figsprite.bean.autowire.Person"  
      autowire="byName" />

根据id查找Bean实例对象。需要保证Bean的id唯一,并且需要具备相关的setter方法。

5.3.2.3、byType方式
<bean id="address" class="com.figsprite.bean.autowire.Address"  
      p:city="上海" p:street="陆家嘴" /></bean>
    
<bean id="car" class="com.figsprite.bean.autowire.Car"  
      p:brand="奥迪" p:price="10000" /></bean>
    
<bean id="person" class="com.figsprite.bean.autowire.Person"  
      autowire="byType" />

根据Class类型查找Bean实例对象。需要保证Bean的Class类型唯一。

5.3.2.4、constructor方式
<bean id="address" class="com.figsprite.bean.autowire.Address"  
      p:city="上海" p:street="陆家嘴" /></bean>
    
<bean id="car" class="com.figsprite.bean.autowire.Car"  
      p:brand="奥迪" p:price="10000" /></bean>
    
<bean id="person" class="com.figsprite.bean.autowire.Person"  
      autowire="constructor" />

类似于byType但适用于在构造方法中的参数。需要保证必须有一个存在该类型参数的构造方法。

5.3.3、注解装配

5.3.3.1、@Autowired

这个注解由Spring提供,可作用在属性、setter方法和构造方法上,完成依赖注入的自动装配工作。可省略依赖注入对象的setter方法。默认情况下按照byType的自动注入方式进行依赖对象的匹配,如果存在多个相同类型的依赖对象则按照byName自动注入方式,也可以通过配合@Qualifier注解更改为byName的自动注入方式。

依赖对象需要存在于Spring容器中。

<bean id="address" class="com.figsprite.bean.autowire.Address"  
      p:city="上海" p:street="陆家嘴" /></bean>
    
<bean id="car" class="com.figsprite.bean.autowire.Car"  
      p:brand="奥迪" p:price="10000" /></bean>
public class Person {
    
    // 默认byType自动注入方式
    @Autoired
    private Address address;
    
    // byName自动注入方式
    @Autoired
    @Qualifier("car")
    private Car car;
}

属性:

  • required:boolean类型,默认为true。默认情况下表示注入的依赖对象不能为null,设置为false后表示该依赖对象可以为null。
5.3.3.2、@Resource

这个注解由JDK提供,与@Autowired注解的作用类似。默认情况下按照byName的自动注入方式进行依赖对象的匹配,如果存在多个相同名称(Bean的id)的依赖对象则按照byType自动注入方式。

属性:

  • name:指定依赖对象的Bean名称(id)。
  • type:指定依赖对象的Class类型。

6、注解

从Spring5开始,若要使用注解开发,则必须要导入Spring-AOP的依赖jar包。

6.1、@Component

标注一个类为Spring容器的Bean。就是这个类的对象实例化到spring容器中,相当于配置文件中的<bean id="" class="" />。衍生注解有@Controller、@Service、@Repository。

6.1.1、@Controller

作为@Component的衍生注解,作用与其一样,只是作为一种标识区分。标注控制层组件。

6.1.2、@Service

作为@Component的衍生注解,作用与其一样,只是作为一种标识区分。标注业务层组件。

6.1.3、@Repository

作为@Component的衍生注解,作用与其一样,只是作为一种标识区分。标注持久层组件。

6.2、@Value

给Bean的属性注入值。

  • ${ property : default_value }:将application.propertites文件的某个属性值注入到Bean的属性当中去。
  • #{ obj.property? :default_value }:SpEL表达式对应的内容。

6.3、@Scope

可以更改被Spring容器管理的Bean对象的作用域。

6.4、@Lazy

可以设置Bean的延迟加载。默认情况下,Spring容器在启动的时候会实例化所有的单例模式的Bean,通过@Lazy注解可以让其在该Bean需要被使用的时候再去实例化。

6.5、xml与注解的区别

xml:更加灵活,使用任何场景,但是配置复杂。

注解:不是自己定义的类无法使用注解开发,适用于一些特定的场景,使用也更加方便简单。

6.6、扩展

6.6.1、@Bean

@Bean不能注释在类上,只能用于在配置类(使用了@Configuration注解的类)中显式声明单个Bean(一个使用了@Bean注解且带有返回值的方法)。意思就是,我要获取这个bean的时候,spring要按照这种方式去获取这个bean。默认情况下@Bean注释的方法名作为对象的名字,也可以用name属性定义对象的名字。

如果你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component注解的,因此就不能使用自动化装配的方案了,但是我们可以使用@Bean,当然也可以使用XML配置。

7、代理模式

Spring AOP的底层就是代理模式。在其他对象的基础上做了一层封装,本来该其他对象做的事情让其代理了该对象的代理对象去做了。

7.1、静态代理

要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。

img

每次要增加一个被代理对象同时也要增加一个代理对象去代理它。在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

7.2、动态代理

  • 基于接口代理:JDK反射机制提供的代理。
  • 基于类代理:基于cglib提供的代理。

程序运行期间由JVM根据反射等机制动态的生成,所以程序运行前不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定的。

8、面向切面编程(AOP)

称为面向切面编程。在不改变原有的逻辑的基础上,增加一些额外的功能。

8.1、概念

  • 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。

  • Aspect(切面):通常是一个类,里面可以定义切入点和通知。

  • JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用。被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。

  • Advice(通知):AOP在特定的切入点上执行的增强处理,有before(前置)、after(后置)、afterReturning(最终)、afterThrowing(异常)、around(环绕)。

    img

  • Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式。

  • weave(织入):将切面应用到目标对象并导致代理对象创建的过程。

  • introduction(引入):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。

  • AOP代理(AOP Proxy):AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。

  • 目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。

8.2、特点

Spring中的AOP代理离不开Spring的容器,代理的生成,管理及其依赖关系都是由容器负责。Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理。

9、Spring事务

  • 编程式事务:编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
  • 声明式事务:声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

9.1、Spring事务的五种隔离级别

在这里插入图片描述

  • DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是READ_COMMITTED。
  • READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
  • READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
  • SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

9.2、Spring事物的七种传播行为

如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。

  • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。

9.3、声明式事务与编程式事务

  • 声明式事务:@Transactional注解,最小支持方法级别。
  • 编程式事务:细粒度更低,可在代码块使用。
    • TransactionTemplate类。
    • Spring中的Bean对象Transactions,使用时需要注入。链式操作transactions.required(() -> {});

9.3、事务失效

  1. 事务方法修饰符使用了privatefinalstatic任意一种即会失效

    @Service
    public class UserService {
    
        @Transactional
        private final static void add(UserModel userModel) {
             saveData(userModel);
             updateData(userModel);
        }    
    }
    
  2. 自身方法内部调用

    @Service
    public class UserService {
    
        @Autowired
        private UserMapper userMapper;
    
        @Transactional
        public void add(UserModel userModel) {
            userMapper.insertUser(userModel);
            updateStatus(userModel);
        }
    
        @Transactional
        public void updateStatus(UserModel userModel) {
            doSameThing();
        }
    }
    
  3. 未被Spring管理(注解所在类未被加载成Bean)

    //@Service
    public class UserService {
    
        @Transactional
        public void add(UserModel userModel) {
             saveData(userModel);
             updateData(userModel);
        }    
    }
    
  4. 多线程调用

    @Service
    public class RoleService {
    
        @Transactional
        public void doOtherThing() {
            System.out.println("保存role表数据");
        }
    }
    
    @Service
    public class UserService {
    
        @Autowired
        private UserMapper userMapper;
        
        @Autowired
        private RoleService roleService;
    
        @Transactional
        public void add(UserModel userModel) throws Exception {
            userMapper.insertUser(userModel);
            new Thread(() -> {
                roleService.doOtherThing();
            }).start();
        }
    }
    
  5. 数据库(引擎)不支持事务

  6. 未开启事务

  7. 错误的传播特性(@Transactional注解使用了错误的propagation参数)

  8. 自己吞了异常(方法异常自行处理了未抛出)

    @Service
    public class UserService {
        
        @Transactional
        public void add(UserModel userModel) {
            try {
                saveData(userModel);
                updateData(userModel);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }
    
  9. 手动抛了别的异常

    @Service
    public class UserService {
        
        @Transactional
        public void add(UserModel userModel) throws Exception {
            try {
                 saveData(userModel);
                 updateData(userModel);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                throw new Exception(e);
            }
        }
    }
    
  10. 自定义的回滚异常与出现的异常不一致

    @Service
    public class UserService {
        
        @Transactional(rollbackFor = CustomException.class)
        public void add(UserModel userModel) throws Exception {
           saveData(userModel);
           int i = 1 / 0;
           updateData(userModel);
        }
    }
    

9.4、局部事务管理

  1. 使用try/catch语句

    可以将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务。

    @Service
    public class RoleService {
    
        @Transactional(propagation = Propagation.NESTED)
        public void doOtherThing() {
            System.out.println("保存role表数据");
        }
    }
    
    @Service
    public class UserService {
    
        @Autowired
        private UserMapper userMapper;
    
        @Autowired
        private RoleService roleService;
    
        @Transactional
        public void add(UserModel userModel) throws Exception {
    
            userMapper.insertUser(userModel);
            try {
                roleService.doOtherThing();
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }
    
  2. 使用编程式事务TransactionTemplateTransactions

9.5、事务超时

一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。

9.6、Spring事务回滚机制

默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出哪些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()方法后你所能执行的唯一操作就是回滚。

10、缓存

Spring中的一级缓存名为singletonObjects,二级缓存名为earlySingletonObjects,三级缓存名为singletonFactories,除了一级缓存是ConcurrentHashMap之外,二级缓存和三级缓存都是HashMap。它们的定义是在DefaultSingletonBeanRegistry类中。

在这里插入图片描述

  • 一级缓存:singletonObjects是用来存放就绪状态的Bean。就绪状态的Bean指的是完成了实例化和初始化的Bean,也就是一个完整的Bean。
  • 二级缓存:earlySingletonObjects是用来存放提前曝光的Bean。提前曝光的Bean指的是完成了实例化但未完成初始化的Bean,也就是一个半成品的Bean。
  • 三级缓存:singletonFactories是用来存放创建用于获取Bean的工厂类ObjectFactory的实例。在IoC容器中,所有刚被创建出来的Bean,默认都会保存到该缓存中。

10.1、缓存流转

  • Bean在这三个缓存之间的流转顺序为:
    • 通过反射创建Bean实例。是单例Bean,并且IoC容器允许Bean之间循环引用,保存到三级缓存中。
    • 当发生了循环引用时,从三级缓存中取出Bean对应的ObjectFactory实例,调用其getObject()方法,来获取提前曝光的Bean,从三级缓存中移除,保存到二级缓存中。不存在循环引用时直接到下一步。
    • Bean初始化完成,生命周期的相关方法执行完毕,保存到一级缓存中,从二级缓存以及三级缓存中移除。

在这里插入图片描述

10.2、注意

并不是所有Bean都会经历这个过程,例如对于原型Bean(Prototype),IoC容器不会将其保存到任何一个缓存中的,另外即便是单例Bean(Singleton),如果没有循环引用关系,也不会被保存到二级缓存中的。

11、循环依赖

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。

这里写图片描述

循环依赖场景:

  • 构造器循环依赖
  • field属性循环依赖

11.1、检测循环依赖

Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。

11.2、解决循环依赖

Spring的单例对象创建的三个步骤:

bean初始化

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象。
  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充。
  3. initializeBean:初始化Bean,调用spring xml中的init方法。

循环依赖主要发生在第1、2步。也就是构造器循环依赖和field循环依赖。

11.2.1、网上说明图

img

img

11.2.2、详细说明

Spring的循环依赖解决方案如下所示:

public class A {
    private B b;
}
public class B {
    private A a;
}
  1. 在创建对象A的时候,调用构造方法进行实例化。
  2. 填充对象A的属性b,这时候时候会发现对象B不存在。
  3. 创建对象B,调用构造方法创建之后,填充B对象属性a。
  4. 此时对象B会先去一级缓存中尝试获取用于填充属性a的对象A,发现没有,于是从二级缓存中获取到刚刚创建的对象A。因为对象A虽然没有初始化完成,但是已经实例化了,放置在二级缓存中成为一个提前曝光的Bean。将这个提前曝光的对象A填充到对象B的a属性中。
  5. B对象填充完属性之后,执行Bean的一系列生命周期方法后完成初始化,对象B放入一级缓存中。
  6. 接下来对象A也完成属性填充和初始化之后,从二级缓存进入一级缓存。
  7. 此时的对象A和对象B均都已成功创建出来,整个循环依赖被解决。

11.3、代理对象(如aop)的循环依赖(使用三级缓存的原因)

为了解决代理对象(如aop)循环依赖的问题。

  1. 对象A实例化,放入三级工厂缓存,设置属性b,对象B实例化放入三级缓存。
  2. 对象B设置属性a,从三级工厂缓存中获取代理后的对象A,同时,代理后的对象A放入二级缓存。
  3. 对象B初始化,然后执行后置处理器,进行aop增强,将增强后的代理对象B放入到一级缓存。删除三级缓存中的对象B。
  4. 此时对象A拿到对象B,设置属性b成功,开始初始化,初始化后执行后置处理器,此时二级缓存已存在代理对象A了,所以不再进行aop增强,将二级缓存中的代理对象A放入到一级缓存。删除二级缓存中的代理对象A。
posted @ 2022-02-17 21:10  是老胡啊  阅读(16)  评论(0)    收藏  举报