一文搞定 Spring,从小白到大牛

Spring 框架是当前 Java Web开发框架的基石,几乎每个人都会用到。

一、Spring框架的模块组成

Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。

看结构图,这些组件被分别整合在核心容器(Core Container)、AOP(Aspect Oriented Programming)、设备支持(Instrument)、数据访问及集成(Data Access/Integratioin)、Web、报文发送(Messaging)、Test等模块。

  1. spring-core:控制反转IoC(Inversion of Control)与依赖注入DI(Dependency Injection)的基本实现,控制反转是种设计思想,即将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
  2. spring-beans:Bean工厂与bean的装配,BeanFactory接口使用控制反转对应用程序的配置、依赖性规范与实际的应用程序代码进行分离。但是BeanFactory容器实例化后并不会自动实例化Bean,只有当Bean被使用时BeanFactory容器才会对该Bean进行实例化与依赖关系的装配。
  3. spring-context:spring的context上下文,即IoC容器,它扩展了BeanFactory,为它添加了Bean生命周期管理、框架事件体系、资源加载透明化等功能,此外该模块还提供了很多企业级支持,如远程访问、任务调度等。ApplicationContext是该模块的核心接口,它扩展了BeanFactory,ApplicationContext容器实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。
  4. spring-expression:spring表达式语言,是统计表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也方便的可以调用对象方法、操作数组、集合等。最出色的要数函数调用和简单字符串的模板函数。

Spring的优点:

  1. spring属于低侵入式设计,代码的污染极低;
  2. spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
  3. Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
  4. spring对于主流的应用框架提供了集成支持。

二、Spring的核心原理

Spring框架最核心的原理:IOCAOPIOC让相互协作的组件保持松散的耦合,而AOP编程允许把遍布于应用各层的功能分离出来形成可重用的功能组件。

1、控制反转 IOC

(1)IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IOC容器来动态注入对象需要的外部资源

(2)最直观的表达就是,IOC让对象的创建不用去new了,可以由Spring容器自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的

(3)Spring的IOC常用的有三种注入方式 :构造器注入、setter方法注入、基于注解注入(@Autowired)

2、面向切面 AOP

(1)AOP的理解

OOP面向对象,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。

AOP,面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。

(2)静态代理(AspectJ)和动态代理(Spring AOP)

AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

  • AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
  • Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法

静态代理与动态代理区别:在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理

(3)Spring AOP动态代理的两种方式(JDK和CGLIB)

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。

  • JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例,  生成目标类的代理对象。
  • 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

(4)AOP的核心概念

  • 切面(Aspect):被抽取的公共模块,可能会横切多个对象。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。
  • 连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。 
  • 通知(Advice):在切面的某个特定的连接点(Join point)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。
  • 切入点(Pointcut):切入点是指 我们要对哪些Join point进行拦截的定义。通过切入点表达式,指定拦截的方法,比如指定拦截add*、search*。
  • 引入(Introduction):(也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
  • 目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
  • 织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成织入。

切入点(pointcut)和连接点(join point)匹配的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。 切入点使得定位通知(advice)可独立于OO层次。 例如,一个提供声明式事务管理的around通知可以被应用到一组横跨多个对象中的方法上(例如服务层的所有业务操作)。

三、Spring的两大核心接口:BeanFactory和ApplicationContext

BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。

1、ApplicationContext 是 BeanFactory 的子接口,功能更全

BeanFactory是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext是BeanFactory的派生接口,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

  • 继承MessageSource,因此支持国际化。
  • 统一的资源文件访问方式。
  • 提供在监听器中注册bean的事件。
  • 同时加载多个配置文件。
  • 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。

2、加载方式

  • BeanFactroy 采用的是延迟加载形式来注入Bean,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
  • ApplicationContext 是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
  • 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

3、创建方式

  • BeanFactory 通常以编程的方式被创建。
  • ApplicationContext 除了编程方式,还能以声明的方式创建,如使用ContextLoader。

4、注册方式

BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册

四、Spring容器中的Bean

Bean是Spring容器中最重要的元素,Spring容器本身是一个大工厂,主要的功能就是管理好Bean,为此引入了IOC和AOP机制。

1、Bean的作用域

在Spring容器中,Bean的作用域用于确定将什么类型的Bean实例返回给调用者。

  • singleton(单例),在spring容器中仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。
  • prototype(原型)每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。
  • request每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的WebApplicationContext环境。
  • session同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的WebApplicationContext环境。
  • globalSession同一个全局 Session 共享一个 bean用于 Portlet, 该作用域仅用于 WebApplication 环境。

2、Bean注入的几种方式

Spring通过DI(依赖注入)实现IOC(控制反转),主要有两种注入方式,

  • 基于XML配置文件方式注入,分别有Setter方法注入、构造方法注入和工厂注入。
  • 基于注解的方式注入,基于注解 @Autowired和@Resource的自动装配(最常用)。

(1)基于注解 @Autowired 的自动装配(最常用)

@Autowired(required=true)  // 主要有三个属性值:constructor,byName,byType

在介绍注解注入的方式前,先简单了解bean的一个属性autowire,autowire主要有三个属性值:constructor,byName,byType。

  • constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。
  • byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。
  • byType:查找所有的set方法,将符合符合参数类型的bean注入。

@Autowired 注解,默认是以byType的方式去匹配类型相同的bean,如果只匹配到一个,那么就直接注入该bean,无论要注入的 bean 的 name 是什么;如果匹配到多个,就会调用 DefaultListableBeanFactory 的 determineAutowireCandidate 方法来决定具体注入哪个bean。

determineAutowireCandidate 的匹配规则是:

  • 先找 Bean 上有@Primary 注解的,有则直接返回 bean 的 name。
  • 再找 Bean 上有 @Order,@PriorityOrder 注解的,有则返回 bean 的 name。
  • 最后再以名称匹配(ByName)的方式去查找相匹配的 bean。

可以简单的理解为先以 ByType 的方式去匹配,如果匹配到了多个再以 ByName 的方式去匹配,找到了对应的 bean 就去注入,没找到就抛出异常。

(2)基于构造方法注入

在spring的配置文件中注册UserService,将UserDaoJdbc通过constructor-arg标签注入到UserService的某个有参数的构造方法中。如果只有一个有参数的构造方法并且参数类型与注入的bean的类型匹配,那就会注入到该构造方法中。

<!-- 注册userService -->
<bean id="userService" class="com.spring.service.impl.UserService">
	<constructor-arg ref="userDaoJdbc"></constructor-arg> <!-- 构造方法注入 -->
</bean>
<!-- 注册jdbc实现的dao -->
<bean id="userDaoJdbc" class="com.spring.dao.impl.UserDaoJdbc"></bean>

(3)基于Setter方法注入

Setter方法注入,即通过setXxx()方法注入Bean的属性值或依赖对象,具有可选择性和灵活性高的优点,是实际应用中最常采用的注入方式。Setter方法注入要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的Setter方法。Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。

<!-- 注册userService -->
<bean id="userService" class="com.spring.service.impl.UserService">
	<property name="UserDao" ref="userDaoMyBatis"></property> <!-- Setter属性注入-->
</bean>

<!-- 注册mybatis实现的dao -->
<bean id="userDaoMyBatis" class="com.spring.dao.impl.UserDaoMyBatis"></bean>

3、Bean的自动装配

在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。在Spring框架xml配置中共有5种自动装配:

  • no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
  • byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。 
  • byType:通过参数的数据类型进行自动装配。
  • constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
  • autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。

@Autowired 注解,默认是以byType的方式去匹配类型相同的bean。@Autowired可用于:构造函数、成员变量、Setter方法。@Autowired和@Resource之间的区别

  • @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
  • @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

4、Bean的生命周期和加载流程(重点)

先看下 Servlet 的生命周期:实例化,初始init,接收请求service,销毁destroy。Spring 容器中 Bean 的生命周期有点类似,具体如下。

Spring Bean 的生命周期分为四个阶段和多个扩展点。扩展点又可以分为影响多个Bean和影响单个Bean实例化和属性赋值对应构造方法和setter方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。

(1)四个阶段(实例化 -> 属性赋值 -> 初始化 -> 销毁)

  • 实例化 Instantiation
  • 属性赋值 Populate
  • 初始化 Initialization
  • 销毁 Destruction

(2)多个扩展点

  • 影响多个Bean
    • BeanPostProcessor
    • InstantiationAwareBeanPostProcessor
  • 影响单个Bean
    • Aware
      • Aware Group1
        • BeanNameAware
        • BeanClassLoaderAware
        • BeanFactoryAware
      • Aware Group2
        • EnvironmentAware
        • EmbeddedValueResolverAware
        • ApplicationContextAware(ResourceLoaderAware\ApplicationEventPublisherAware\MessageSourceAware)

(3)生命周期

  • InitializingBean,初始化Bean
  • DisposableBean,销毁Bean

五、单例Bean的线程安全和并发问题(重点)

单例Bean是Spring容器默认的方式,所有线程都共享一个单例实例Bean,确实会存在并发的问题。对于原型Bean,每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题。

1、单例Bean的线程安全问题

单例Bean分两种:无状态的单例Bean(线程安全)和有状态的单例Bean(线程不安全)

  • 无状态对象(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响,这是在实际应用中必须注意的。无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。
  • 有状态对象(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。

2、线程安全问题的解决办法

在Spring容器中,Bean默认的都是单例作用域,无状态的Bean在多线程环境下共享是没有问题的。有状态Bean的线程安全问题推荐使用ThreadLocal进行处理。主要的解决办法有,

  • 尽量避免在Bean对象中定义可变的成员变量,避免有状态的Bean。
  • 如果确实需要使用状态的Bean,则在Bean对象中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中。
  • 也可以通过加锁的方法来解决线程安全,这种以时间换空间的场景在高并发场景下显然是不实际的,还是优先推荐使用ThreadLocal。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

六、Spring的事务处理

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

1、Spring事务的种类

Spring支持编程式事务声明式事务两种方式,而声明式事务是我们在项目中最常用的事务方式。

  • 编程式事务,使用TransactionTemplate。
  • 声明式事务,建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

2、Spring事务的隔离级别(5种)

隔离级别 说明
ISOLATION_DEFAULT PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别
ISOLATION_READ_UNCOMMITTED

读未提交。这是事务最低的隔离级别,它允许令外一个事务可以看到这个事务未提交的数据,这种隔离级别会产生脏读,不可重复读和幻像读。

ISOLATION_READ_COMMITTED 读已提交。保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
ISOLATION_REPEATABLE_READ 可重复读。这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
ISOLATION_SERIALIZABLE

序列化。这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。

3、Spring事务的传播行为(7种)

Spring事务的传播行为说的是,当多个事务同时存在的时候,Spring如何处理这些事务的行为

事务行为 说明
PROPAGATION_REQUIRED 支持当前事务,假设当前没有事务。就新建一个事务
PROPAGATION_SUPPORTS 支持当前事务,假设当前没有事务,就以非事务方式运行
PROPAGATION_MANDATORY 支持当前事务,假设当前没有事务,就抛出异常
PROPAGATION_REQUIRES_NEW 新建事务,假设当前存在事务。把当前事务挂起
PROPAGATION_NOT_SUPPORTED 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起
PROPAGATION_NEVER 以非事务方式运行,假设当前存在事务,则抛出异常
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

七、Spring框架中用到的设计模式

1、工厂模式

Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象。两者的区别,

  • BeanFactory :延迟注入(使用到某个 bean 的时候才会注入),占用更少的内存,程序启动速度更快。
  • ApplicationContext :容器启动的时候,一次性创建所有 bean 。ApplicationContext除了BeanFactory的功能,还有额外更多功能,实际开发中,ApplicationContext更常用。

ApplicationContext的三个实现类:

  • ClassPathXmlApplication:把上下文文件当成类路径资源。
  • FileSystemXmlApplication:从文件系统中的 XML 文件载入上下文定义信息。
  • XmlWebApplicationContext:从Web系统中的XML文件载入上下文定义信息。

2、单例模式

Spring 中 Bean 的默认作用域就是 singleton(单例)的。使用单例模式的好处,

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
  • 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间

3、代理模式

Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术。

4、模板方法

用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate等。

5、观察者模式

定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现:ApplicationListener。

八、Spring框架的事件类型

Spring 提供了以下5种标准的事件,

  1. 上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
  2. 上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
  3. 上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
  4. 上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
  5. 请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。
发布了144 篇原创文章 · 获赞 146 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/shipfei_csdn/article/details/104149678