Spring总结
(一)Spring简单介绍
Spring是一个JAVAEE开源的轻量级别的框架,可以解决我们企业开发中遇到的问题,能够让编码变得更加简单,核心组件IOC和AOP面向切面编程。
-
IOC控制翻转:把整个对象的创建过程统一交给我们SpringIOC容器来实现管理,底层使用反射+工厂模式实现。原来是调用方自己去new Object(),现在则是由IoC容器创建需要的对象。因此创建对象的形式发生了反转。
-
依赖注入:控制反转的核心思想是由Spring负责对象的创建,在创建对象过程中,Spring会根据依赖关系,将它依赖的对象注入到当前对象中,这就是依赖注入。
-
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核心概念:
-
IOC控制翻转:把整个对象的创建过程统一交给我们SpringIOC容器来实现管理,底层使用反射+工厂模式实现。原来是用户自己去new Object(),现在则是由IoC容器创建需要的对象。因此创建对象的形式发生了反转。
-
依赖注入:控制反转的核心思想是由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 | |
| property | |
| ref | |
| value | |
| list | 用于封装 List 或数组类型的属性注入。 |
| set | 用于封装 Set 类型的属性注入。 |
| map | 用于封装 Map 类型的属性注入。 |
| entry | |
| 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注入(值注入)
使用构造函数实现属性注入大致步骤如下:
-
在 Bean 中添加一个有参构造函数,构造函数内的每一个参数代表一个需要注入的属性;
-
在 Spring 的 XML 配置文件中,通过
及其子元素 对 Bean 进行定义; -
在
元素内使用 元素,对构造函数内的属性进行赋值,Bean 的构造函数内有多少参数,就需要使用多少个 元素。
在 Spring 实例化 Bean 的过程中,IoC 容器首先会调用默认的构造方法(无参构造方法)实例化 Bean(Java 对象),然后通过 Java 的反射机制调用这个 Bean 的 setXxx() 方法,将属性值注入到 Bean 中。
setter 注入的方式进行属性注入,大致步骤如下:
-
在 Bean 中提供一个默认的无参构造函数(在没有其他带参构造函数的情况下,可省略),并为所有需要注入的属性提供一个 setXxx() 方法;
-
在 Spring 的 XML 配置文件中,使用
及其子元素 对 Bean 进行定义; -
在
元素内使用 元素对各个属性进行赋值。
在通过构造函数或 setter 方法进行属性注入时,通常是在
Spring 框架提供了 2 种短命名空间,可以简化 Spring 的 XML 配置,如下表。
| 短命名空间 | 简化的 XML 配置 | 说明 |
|---|---|---|
| p 命名空间 | 是 setter 方式属性注入的一种快捷实现方式 | |
| c 命名空间 | 是构造函数属性注入的一种快捷实现方式 |
p 命名空间注入
p 命名空间是 setter 方式属性注入的一种快捷实现方式。通过它,我们能够以 bean 属性的形式实现 setter 方式的属性注入,而不再使用嵌套的
使用 p 命名空间注入依赖时,必须注意以下 3 点:
-
Java 类中必须有 setter 方法;
-
Java 类中必须有无参构造器(类中不包含任何带参构造函数的情况,无参构造函数默认存在);
-
在使用 p 命名空间实现属性注入前,XML 配置的
元素内必须先导入 p 命名空间的 XML 约束。
c 命名空间注入
c 命名空间是构造函数注入的一种快捷实现方式。通过它,我们能够以
使用 c 命名空间注入依赖时,必须注意以下 2 点:
-
Java 类中必须包含对应的带参构造器;
-
在使用 c 命名空间实现属性注入前,XML 配置的
元素内必须先导入 c 命名空间的 XML 约束。
(四)Spring 自动装配
Spring在Bean与Bean之间建立依赖关系的行为成为"装配".
在XML配置中通过
Spring的自动装配功能可以让Spring容器依赖某种规则,为指定的Bean从应用的上下文(ApplicationContext容器)中查找它所依赖的Bean,并自动建立Bean之间的依赖关系,这一过程是完全不使用任何
自动装配规则
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 | 表示默认采用上一级元素 |
| no | 默认值,表示不使用自动装配,Bean 的依赖关系必须通过 |
从 Java 5 开始,Java 增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。
Spring 通过注解实现自动装配的步骤如下:
-
引入依赖
-
开启组件扫描
-
使用注解定义 Bean
-
依赖注入
1. 引入依赖
使用注解的第一步,就是要在项目中引入相关 Jar 包。
2. 开启组件扫描
Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过
在使用
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 相同。 |
我们可以通过以下注解将定义好 的Bean 装配到其它的 Bean 中。
| 注解 | 说明 |
|---|---|
| @Autowired | 可以应用到 Bean 的属性变量、setter 方法、非 setter 方法及构造函数等,默认按照 Bean 的类型进行装配。 @Autowired 注解默认按照 Bean 的类型进行装配,默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它的 required 属性为 false。如果我们想使用按照名称(byName)来装配,可以结合 @Qualifier 注解一起使用 |
| @Resource | 作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 的名称进行装配。 @Resource 中有两个重要属性:name 和 type。
|
| @Qualifier | 与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。 |
(五)Spring作用域与生命周期
Spring 作用域:
我们可以在
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 配置文件中,可以使用
prototype
如果一个 Bean 定义的作用域为 prototype,那么这个 Bean 就被称为 prototype bean。对于 prototype bean 来说,Spring 容器会在每次请求该 Bean 时,都创建一个新的 Bean 实例。
从某种意义上说,Spring IoC 容器对于 prototype bean 的作用就相当于 Java 的 new 操作符。它只负责 Bean 的创建,至于后续的生命周期管理则都是由客户端代码完成的,详情请参看《Spring Bean 生命周期》。
在 Spring 配置文件中,可以使用
Spring 生命周期
Spring 中 Bean 的生命周期较复杂,大致可以分为以下 5 个阶段:
-
Bean 的实例化
-
Bean 属性赋值
-
Bean 的初始化
-
Bean 的使用
-
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 生命周期的整个执行过程描述如下。
-
Spring 启动,查找并加载需要被 Spring 管理的 Bean,对 Bean 进行实例化。
-
对 Bean 进行属性注入。
-
如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
-
如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
-
如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
-
如果 Bean 实现了 BeanPostProcessor 接口,则 Spring 调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
-
如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
-
如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
-
如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
-
如果在
中指定了该 Bean 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。 -
如果 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动态代理创建步骤:
-
创建一个接口Invocation Handler的类,它必须实现invoke方法
-
创建被代理的类及接口
-
通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)创建一个代理
-
通过代理调用方法