Spring总结


(一)Spring简单介绍

Spring是一个JAVAEE开源的轻量级别的框架,可以解决我们企业开发中遇到的问题,能够让编码变得更加简单,核心组件IOC和AOP面向切面编程。

  1. IOC控制翻转:把整个对象的创建过程统一交给我们SpringIOC容器来实现管理,底层使用反射+工厂模式实现。原来是调用方自己去new Object(),现在则是由IoC容器创建需要的对象。因此创建对象的形式发生了反转。

  2. 依赖注入:控制反转的核心思想是由Spring负责对象的创建,在创建对象过程中,Spring会根据依赖关系,将它依赖的对象注入到当前对象中,这就是依赖注入。

  3. Aop面向切面编程:对我们功能(方法)前后实现增强,比如日志打印、事务管理、权限管理、底层使基于动态代理模式实现的。

Spring的优点:

1 方法的解耦,简化开发;

2 Aop技术的支持

3 提供声明事务支持

4 Junit单元测试

5 方便整合其它框架(Mybatis、SpringMVC、SpringBoot、SpringCloud、Redis等)

6 降低我们的JAVAEE api的开发难度(Spring 对很多复杂的api接口实现了封装)

IOC容器底层实现原理:

1 IOC容器中非常核心的接口BeanFactory , Bean对象Factory工厂

2 IOC容器的基本概念:控制翻转

3 IOC容器底层实现技术:反射技术、解析XML、工厂模式

4 IOC作用:降低我们代码的耦合度

(二)Spring IoC

spring核心概念:

  1. IOC控制翻转:把整个对象的创建过程统一交给我们SpringIOC容器来实现管理,底层使用反射+工厂模式实现。原来是用户自己去new Object(),现在则是由IoC容器创建需要的对象。因此创建对象的形式发生了反转。

  2. 依赖注入:控制反转的核心思想是由Spring负责对象的创建,在创建对象过程中,Spring会根据依赖关系,将它依赖的对象注入到当前对象中,这就是依赖注入。

IoC容器的两种实现:

IoC容器底层其实是一个Bean工厂。Spring 框架为我们提供了两种不同的IoC容器,它们分别是BeanFactory和ApplicationContext。

BeanFactory是IoC容器的基本实现,由org.springframework.beans.factory.BeanFactory 接口定义。其采用懒加载的机制,容器在加载配置文件时并不会立即创建java对象,只有程序中获取(使用)这个对象时才会创建。

ApplicationContext是BeanFactory接口的子接口,是对BeanFactory的扩展。ApplicationContext在BeanFactory的基础上增加了许多企业级的功能,如AOP、国际化、事务支持等。

ApplicationContext 接口有两个常用的实现类,具体如下表。

实现类描述示例代码
ClassPathXmlApplicationContext 加载类路径 ClassPath 下指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作 ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);
FileSystemXmlApplicationContext 加载指定的文件系统路径中指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作 ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);

在 XML 配置的 元素中可以包含多个属性或子元素,常用的属性或子元素如下表所示。

属性名称描述
id Bean 的唯一标识符,Spring IoC 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始,可以使用字母、数字、下划线等符号。
name 该属性表示 Bean 的名称,我们可以通过 name 属性为同一个 Bean 同时指定多个名称,每个名称之间用逗号或分号隔开。Spring 容器可以通过 name 属性配置和管理容器中的 Bean。
class 该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,即类的全限定名。
scope 表示 Bean 的作用域,属性值可以为 singleton(单例)、prototype(原型)、request、session 和 global Session。默认值是 singleton。
constructor-arg 元素的子元素,我们可以通过该元素,将构造参数传入,以实现 Bean 的实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型。
property 元素的子元素,用于调用 Bean 实例中的 setter 方法对属性进行赋值,从而完成属性的注入。该元素的 name 属性用于指定 Bean 实例中相应的属性名。
ref 等元素的子元索,用于指定对某个 Bean 实例的引用,即 元素中的 id 或 name 属性。
value 等元素的子元素,用于直接指定一个常量值。
list 用于封装 List 或数组类型的属性注入。
set 用于封装 Set 类型的属性注入。
map 用于封装 Map 类型的属性注入。
entry   元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值。
init-method 容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法
destroy-method 容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效
lazy-init 懒加载,值为 true,容器在首次请求时才会创建 Bean 实例;值为 false,容器在启动时创建 Bean 实例。该方法只在 scope=singleton 时有效

(三)Spring Bean属性注入

Spring主要通过两种方式实现属性注入:

1 构造函数注入

2 setter注入(值注入)

使用构造函数实现属性注入大致步骤如下:

  1. 在 Bean 中添加一个有参构造函数,构造函数内的每一个参数代表一个需要注入的属性;

  2. 在 Spring 的 XML 配置文件中,通过 及其子元素 对 Bean 进行定义;

  3. 元素内使用  元素,对构造函数内的属性进行赋值,Bean 的构造函数内有多少参数,就需要使用多少个 元素。

在 Spring 实例化 Bean 的过程中,IoC 容器首先会调用默认的构造方法(无参构造方法)实例化 Bean(Java 对象),然后通过 Java 的反射机制调用这个 Bean 的 setXxx() 方法,将属性值注入到 Bean 中。

setter 注入的方式进行属性注入,大致步骤如下:

  1. 在 Bean 中提供一个默认的无参构造函数(在没有其他带参构造函数的情况下,可省略),并为所有需要注入的属性提供一个 setXxx() 方法;

  2. 在 Spring 的 XML 配置文件中,使用 及其子元素 对 Bean 进行定义;

  3. 元素内使用  元素对各个属性进行赋值。

在通过构造函数或 setter 方法进行属性注入时,通常是在 元素中嵌套 元素来实现的。这种方式虽然结构清晰,但书写较繁琐。

Spring 框架提供了 2 种短命名空间,可以简化 Spring 的 XML 配置,如下表。

短命名空间简化的 XML 配置说明
p 命名空间 元素中嵌套的  元素 是 setter 方式属性注入的一种快捷实现方式
c 命名空间 元素中嵌套的 元素 是构造函数属性注入的一种快捷实现方式

p 命名空间注入

p 命名空间是 setter 方式属性注入的一种快捷实现方式。通过它,我们能够以 bean 属性的形式实现 setter 方式的属性注入,而不再使用嵌套的 元素,以实现简化 Spring 的 XML 配置的目的。

使用 p 命名空间注入依赖时,必须注意以下 3 点:

  • Java 类中必须有 setter 方法;

  • Java 类中必须有无参构造器(类中不包含任何带参构造函数的情况,无参构造函数默认存在);

  • 在使用 p 命名空间实现属性注入前,XML 配置的 元素内必须先导入 p 命名空间的 XML 约束。

c 命名空间注入

c 命名空间是构造函数注入的一种快捷实现方式。通过它,我们能够以 属性的形式实现构造函数方式的属性注入,而不再使用嵌套的 元素,以实现简化 Spring 的 XML 配置的目的。

使用 c 命名空间注入依赖时,必须注意以下 2 点:

  • Java 类中必须包含对应的带参构造器;

  • 在使用 c 命名空间实现属性注入前,XML 配置的 元素内必须先导入 c 命名空间的 XML 约束。

(四)Spring 自动装配 

Spring在Bean与Bean之间建立依赖关系的行为成为"装配".

在XML配置中通过中的ref属性,手动维护bean与bean之间的依赖关系,这对于少量bena的应用来说可以满足要求。但是应用比较庞大时,对象之间的依赖关系也会越来越复杂,这样维护XML的难度与成本也会增加。

 Spring的自动装配功能可以让Spring容器依赖某种规则,为指定的Bean从应用的上下文(ApplicationContext容器)中查找它所依赖的Bean,并自动建立Bean之间的依赖关系,这一过程是完全不使用任何  元素 ref 属性的情况下进行的。

自动装配规则

Spring 共提供了 5 中自动装配规则,它们分别与 autowire 属性的 5 个取值对应,具体说明如下表。

属性值说明
byName 按名称自动装配。

Spring 会根据的 Java 类中对象属性的名称,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 id 或 name 属性值与这个对象属性的名称相同,则获取这个 Bean,并与当前的 Java 类 Bean 建立关联关系。
byType 按类型自动装配。

Spring 会根据 Java 类中的对象属性的类型,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 class 属性值与这个对象属性的类型相匹配,则获取这个 Bean,并与当前的 Java 类的 Bean 建立关联关系。
constructor 与 byType 模式相似,不同之处在与它应用于构造器参数(依赖项),如果在容器中没有找到与构造器参数类型一致的 Bean,那么将抛出异常。

其实就是根据构造器参数的数据类型,进行 byType 模式的自动装配。
default 表示默认采用上一级元素 设置的自动装配规则(default-autowire)进行装配。
no 默认值,表示不使用自动装配,Bean 的依赖关系必须通过  元素的 ref 属性来定义。

从 Java 5 开始,Java 增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。

Spring 通过注解实现自动装配的步骤如下:

  1. 引入依赖

  2. 开启组件扫描

  3. 使用注解定义 Bean

  4. 依赖注入

1. 引入依赖

使用注解的第一步,就是要在项目中引入相关 Jar 包。

2. 开启组件扫描

Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。

在使用   元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 中添加 context 相关的约束。

3. 使用注解定义 Bean

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

注解说明
@Component 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。

使用时只需将该注解标注在相应类上即可。
@Repository 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller 该注解通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
4. 基于注解方式实现依赖注入

我们可以通过以下注解将定义好 的Bean 装配到其它的 Bean 中。

注解说明
@Autowired 可以应用到 Bean 的属性变量、setter 方法、非 setter 方法及构造函数等,默认按照 Bean 的类型进行装配。

@Autowired 注解默认按照 Bean 的类型进行装配,默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它的 required 属性为 false。如果我们想使用按照名称(byName)来装配,可以结合 @Qualifier 注解一起使用
@Resource 作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 的名称进行装配。

@Resource 中有两个重要属性:name 和 type。
  • Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。

  • 如果指定 name 属性,则按实例名称进行装配;

  • 如果指定 type 属性,则按 Bean 类型进行装配;

  • 如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。

@Qualifier 与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。

(五)Spring作用域与生命周期

Spring 作用域:

我们可以在 元素中添加 scope 属性来配置 Spring Bean 的作用范围。例如,如果每次获取 Bean 时,都需要一个新的 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。

Spring 5 共提供了 6 种 scope 作用域,如下表。

作用范围描述
singleton 默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例
prototype 原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个新的 Bean 实例。
request 每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。
session 同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。
application 同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。

与 singleton 类似,但 singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而一个 Web 应用中可能会存在多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。
websocket websocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。

注意:在以上 6 种 Bean 作用域中,除了 singleton 和 prototype 可以直接在常规的 Spring IoC 容器(例如 ClassPathXmlApplicationContext)中使用外,剩下的都只能在基于 Web 的 ApplicationContext 实现(例如 XmlWebApplicationContext)中才能使用,否则就会抛出一个 IllegalStateException 的异常。

singleton

singleton 是 Spring 容器默认的作用域。当 Bean 的作用域为 singleton 时,Spring IoC 容器中只会存在一个共享的 Bean 实例。这个 Bean 实例将存储在高速缓存中,所有对于这个 Bean 的请求和引用,只要 id 与这个 Bean 定义相匹配,都会返回这个缓存中的对象实例。
如果一个 Bean 定义的作用域为 singleton ,那么这个 Bean 就被称为 singleton bean。在 Spring IoC 容器中,singleton bean 是 Bean 的默认创建方式,可以更好地重用对象,节省重复创建对象的开销。

在 Spring 配置文件中,可以使用 元素的 scope 属性,将 Bean 的作用域定义成 singleton,其配置方式如下所示:

prototype

如果一个 Bean 定义的作用域为 prototype,那么这个 Bean 就被称为 prototype bean。对于 prototype bean 来说,Spring 容器会在每次请求该 Bean 时,都创建一个新的 Bean 实例。

从某种意义上说,Spring  IoC 容器对于 prototype bean 的作用就相当于 Java 的 new 操作符。它只负责 Bean 的创建,至于后续的生命周期管理则都是由客户端代码完成的,详情请参看《Spring Bean 生命周期》。

在 Spring 配置文件中,可以使用 元素的 scope 属性将 Bean 的作用域定义成 prototype,其配置方式如下所示:

Spring 生命周期

Spring 中 Bean 的生命周期较复杂,大致可以分为以下 5 个阶段:

  1. Bean 的实例化

  2. Bean 属性赋值

  3. Bean 的初始化

  4. Bean 的使用

  5. Bean 的销毁


Spring 根据 Bean 的作用域来选择 Bean 的管理方式,

  • 对于 singleton 作用域的 Bean 来说,Spring IoC 容器能够精确地控制 Bean 何时被创建、何时初始化完成以及何时被销毁;

  • 对于 prototype 作用域的 Bean 来说,Spring IoC 容器只负责创建,然后就将 Bean 的实例交给客户端代码管理,Spring IoC 容器将不再跟踪其生命周期。

Spring 生命周期流程

Spring Bean 的完整生命周期从创建 Spring IoC 容器开始,直到最终 Spring IoC 容器销毁 Bean 为止,其具体流程如下图所示。

Bean 生命周期的整个执行过程描述如下。

  1. Spring 启动,查找并加载需要被 Spring 管理的 Bean,对 Bean 进行实例化。

  2. 对 Bean 进行属性注入。

  3. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。

  4. 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。

  5. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。

  6. 如果 Bean 实现了 BeanPostProcessor 接口,则 Spring 调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。

  7. 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。

  8. 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。

  9. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。

  10. 如果在 中指定了该 Bean 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。

  11. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

(六)Spring AOP

1 将分散在程序各处的横切关注点剥离出来并以集中的方式进行表达

2 使得开发人员能够关注于业务逻辑的实现而非繁杂的非功能代码,简化了程序的编写与单元测试

3 应用场景:日志、安全、事务

AOP VS 继承:

AOP重点考虑的是程序的横切逻辑

继承重点考虑的是纵向的职责分派

AOP核心概念:

Advice(通知)

定义在连接点处的行为,围绕方法调用而进行注入。也就是一些可执行的代码,我们在执行执行某个方法之前,先去做某些动作。

Pointcut(切点)

将通知应用于某个连接点上。

Advisor(通知器)

组合Advice和Pointcut(切点)

ProxyFactoryBean是Spring AOP的底层实现与源头,其功能是针对目标对象创建代理对象,将对目标对象的方法调用转到对应代理对象方法的调用,

并且可以在代理对象方法调用的前后执行与之匹配的各个通知器中定义好的方法。

ProxyFactoryBean的组成:

target:目标对象,需要对其进行切面增强,目标对象为spring的工厂所管理的普通对象,对应到程序的一个class文件定义的对象。

proxyInterfaces: 代理对象所实现的接口。

interceptorNames: 通知器(Advisor)列表,通知其中包含了通知Advice 与切点pointcut。保留了在什么地方增加增强功能以及增加什么样的功能等信息。

Spring通过三种方式来创建目标代理对象

JDK动态代理:定义接口,目标对象和代理对象 都实现这个接口,此外队理对象比目标对象要多出一些增强的逻辑功能。

CGLIB:目标对象没有实现接口的功能,无法通过JDK动态代理实现,这个时候可以创建目标对象的子类对象,在子类对象中重写目标对象的方法,以此来完成功能的增强。

ObjenesisCglibAopProxy, spring4.0新增加的,是基于CGLIB实现的,但是更为高效,是spring4.0后的代理默认实现方式。

DefaultAopProxyFactory 默认的aop代理创建工厂是根据配置文件来创建aop代理的。

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    public DefaultAopProxyFactory() {
    }

    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (NativeDetector.inNativeImage() || !config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
            return new JdkDynamicAopProxy(config);
        } else {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
            } else {
                return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
            }
        }
    }
    。。。。
}

静态代理与动态代理:

静态代理的作用:为其他对象提供一种代理以控制对这个对象的访问。另一种情况是,有时一个客户不想或者不能直接引用另外一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

JDK动态代理:Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类(接口):

(1)Interface InvocationHandler:该接口定义了一个方法public object invoke(Object obj, Method method, Object[] args) 在实际使用时第一个object一般是指代理类,method是被代理的方法,

args为该方法的参数数组,这个抽象方法在代理类中动态实现。

(2)Proxy:该类即为动态代理类,其中主要包括以下内容:

static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) , 通过该方法返回代理类的一个实例,返回后的代理实例可以当作代理类来使用。

动态代理是这样一种class: 在运行时生成class,在生成它时我们必须提供一组interface给它,然后该class就会声明它实现了这些interface。因此我们可以将该class的实例当作这些interface中的任何一个来用。当然,这个动态代理其实就是一个proxy,它不会替我们实质性的工作,在生成它的实例时我们必须提供一个handler,由它接管实际的工作。

JDK动态代理创建步骤:

  1. 创建一个接口Invocation Handler的类,它必须实现invoke方法

  2. 创建被代理的类及接口

  3. 通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)创建一个代理

  4. 通过代理调用方法