社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
Spring是一种轻量级开发框架,旨在简化开发以及系统的可维护性。
Spring框架的优点:1、非侵入式设计,可以使应用程序代码对框架的依赖最小化。2、方便解耦、简化开发,将所有对象的创建和依赖关系的维护工作都交给Spring容器的管理。3、支持AOP,提高了程序的复用性。4、支持声明式事务处理,只需要通过配置就可以完成对事物的管理。5、方便程序的测试,Spring提供了对Junit4的支持,可以通过注解方便的测试Spring程序。6、方便集成各种优秀框架。
Spring是很多模块的集合,包括核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。
Spring的核心容器是其他模块建立的基础,有spring-core、spring-beans、spring-context等模块组成。
spring-core 模块:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
spring-beans 模块:主要用来管理bean,这个模块实现BeanFactory的工厂模式,Spring中Bean形式是普通Java类.
Spring Context 模块:此模块表示Spring应用的环境,通过此模块可访问任意Bean,ApplicationContext
接口是模块的关键组成.
Spring表达式语言(SpEL):这个模块提供对表达式语言(SpEL)支持
spring-aop 模块:是 Spring 的另一个核心模块,提供对面向切面编程的支持。
Aspects 模块: 提供与AspectJ集成,AspectJ是另一个面向切面编程的框架。
spring-jdbc 模块:对Java JDBC接口再次包装,让Spring应用中使用JDBC更简单。
spring-orm 模块:ORM代表对象关系映射,该模块提供对ORM的支持。Hibernate
spring-oxm 模块:OXM代表对象XML映射器,将 java 对象映射成 XML 数据, 或者将 XML 数据映射成 java 对象,该模块提供对OXM的支持
spring-jms模块(Java Messaging Service):指Java消息传递服务,包含用于生产和使用消息的功能。
Transations 模块:事务模块,该模块提供数据库事务的支持。
spring-web 模块:提供了基本的Web开发集成功能,如文件下载、rest接口支持等。
servlet 模块:提供MVC(Model-View-Controller)功能实现
WebSocket 模块:提供Socket通信,web端的的推送功能;
portlet 模块:实现web模块功能的聚合,(如网站首页(Port)下面可能会有不同的子窗口(Portlet))。
spring-test 模块:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。
传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建对象和设置依赖关系。问题:导致类与类之间高耦合,难于测试。而IOC理念,将对象的创建和设置依赖关系交由Spring容器来进行管理,开发人员只需要关注具体实现就可以了。
Spring框架中控制反转(Inversion of Control / IoC)与依赖注入(Dependency Injection / DI)实际上讲的是同一个事情,只是角度不同。
DI依赖注入:依赖类不由程序员实例化,而是通过spring容器帮我们创建指定实例并且将实例动态的注入到需要该对象的类中。
Spring 通过一个配置文件描述 Bean 及 Bean 之间的依赖关系,利用 Java 语言的反射功能实例化Bean 并建立 Bean 之间的依赖关系。Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
Spring 启动时读取应用程序提供的 Bean 配置信息,并在 Spring 容器中生成一份相应的 Bean 配置注册表,然后根据这张注册表实例化 Bean,装配好 Bean 之间的依赖关系,为上层应用提供准备就绪的运行环境。其中 Bean 缓存池为 HashMap 实现。
①XML文件注入
示例Demo:创建一个玩家类,该玩家可以使用武器有刀剑和枪。
传统写法:玩家的武器只能是剑Sword
,而不能把Sword
替换成枪Gun
。想替换涉及代码修改。
class Player{
Weapon weapon;
Player(){
// 与 Sword类紧密耦合
this.weapon = new Sword();
}
public void attack() {
weapon.attack();
}
}
依赖注入:将对象的创建和依赖关系的设置交由Spring容器进行管理。消除类之间依赖关系。A类要依赖B类,A类不再直接创建B类,而是把这种依赖关系配置在外部xml文件(或java config文件)中,然后由Spring容器根据配置信息创建、管理bean类。
class Player{
Weapon weapon;
// weapon 被注入进来
Player(Weapon weapon){
this.weapon = weapon;
}
public void attack() {
weapon.attack();
}
public void setWeapon(Weapon weapon){
this.weapon = weapon;
}
}
Weapon
类的实例并不在代码中创建,而是外部通过构造函数传入,传入类型是父类Weapon
,所以传入的对象类型可以是任何Weapon
子类。Spring容器根据配置信息创建所需子类实例,并注入Player
类中,如下所示:
<bean id="player" class="com.qikegu.demo.Player">
<construct-arg ref="weapon"/>
</bean>
<bean id="weapon" class="com.qikegu.demo.Gun">
</bean>
上面代码中<construct-arg ref="weapon"/>
ref指向id="weapon"
的bean,传入的武器类型是Gun
,如果想改为Sword
,可以作如下修改:
<bean id="weapon" class="com.qikegu.demo.Sword"> </bean>
② 注解方式注入:Bean也可以通过Java注解的方式配置。Java注解直接加在需要装配的Bean Java类上。
要定义一个Bean,可以通过@Bean
注解,Spring容器会注册这个Bean,并将方法名作为Bean ID。
示例:SpringConfig.java
@Configuration
public class SpringConfig {
// 定义 App Bean
@Bean
public App app() {
return new App(logger()); // 调用Bean方法logger()注入Logger Bean实例
}
// 定义 Database Bean
@Bean
public Database database() {
return new Database();
}
// 定义 Logger Bean
@Bean
public Logger logger() {
return new Logger();
}
}
可以使用AnnotationConfigApplicationContext
读取配置类。
public class Test {
public static void main(String[] args) {
// 使用`AnnotationConfigApplicationContext`读取配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
App app = context.getBean("app", App.class);
}
}
①xml常见注入方式:Setter方法注入、构造函数注入以及工厂方法注入
Setter方法注入:要求 Bean 提供一个默认的构造函数,并为需要注入的属性提供对应的 Setter 方法 。Spring 先调用 Bean 的默认构造函数实例化 Bean 对象,然后通过反射来调用 Setter 方法注入属性值 。 下面我们举一个例子:
public class Book {
/**
* 书名
*/
private String name;
/**
* 定价
*/
private double price;
/**
* 作者
*/
private Author author;
public void setName(String name) {
this.name = name;
}
...省略get、set
}
<bean id="book" class="net.deniro.spring4.bean.Book">
<property name="name">
<value>面纱</value>
</property>
<property name="price">
<value>25.5</value>
</property>
<property name="wordNum">
<value>80000</value>
</property>
</bean>
构造函数注入:使用构造函数注入的前提是 Bean 必须提供带参的构造函数。构造函数注入保证一些必要的属性在 Bean 实例化时就得到设置,这样 Bean 在实例化后就可以使用啦。我们为 “书” 这个类,添加一个带参的构造函数:
public Book(String name, double price) {
this.name = name;
this.price = price;
}
public Book(String name, Author author) {
this.name = name;
this.author = author;
}
配置1:<constructor-arg>
的元素中有一个 type 属性,它表示构造函数中参数的类型,这是 spring 用来判断配置项与构造函数入参对应关系的 “桥梁”。
<bean id="book2" class="net.deniro.spring4.bean.Book">
<constructor-arg type="java.lang.String">
<value>人生的枷锁</value>
</constructor-arg>
<constructor-arg type="double">
<value>35</value>
</constructor-arg>
</bean>
配置2:如果 Book 类定义的构造函数具有多个类型相同入参,那么就需要依赖配置顺序咯,如果想定义的更加灵活,那么就可以使用 “ 按索引匹配入参”。注意: Spring 底层是采用 Java 反射能力来实现依赖注入的,但 Java 反射无法获知构造函数的参数名,所以只能通过入参类型与索引信息来间接地确定构造函数的配置项与入参之间的对应关系。
<bean id="book3" class="net.deniro.spring4.bean.Book">
<constructor-arg index="0">
<value>人生的枷锁</value>
</constructor-arg>
<constructor-arg index="1">
<value>上海译文出版社</value>
</constructor-arg>
</bean>
配置3:如果 Bean 构造函数的入参类型不是基础数据类型,而且入参类型各异,那么可以通过 Java 反射机制获取构造函数的入参类型。
<bean id="author" class="net.deniro.spring4.bean.Author">
<constructor-arg value="毛姆"/>
</bean>
<bean id="book5" class="net.deniro.spring4.bean.Book">
<constructor-arg>
<value>人生的枷锁</value>
</constructor-arg>
<constructor-arg>
<ref bean="author"/>
</constructor-arg>
</bean>
工厂方法注入:工厂类负责创建目标类实例,它对外屏蔽了目标类的实例化细节,返回的类型一般是接口或抽象类的形式。
public class BookFactory {
public Book create() {
return new Book("面纱", "重庆出版社");
}
}
配置1:非静态
<bean id="bookFactory" class="net.deniro.spring4.bean.BookFactory"/>
<!-- factory-bean:指定工厂类;factory-method:指定工厂方法-->
<bean id="book11" factory-bean="bookFactory" factory-method="create"/>
配置2:静态
<!--class:指定工厂类; factory-method:指定静态工厂方法 -->
<bean id="book12" class="net.deniro.spring4.bean.BookFactory" factory-method="createBook"/>
②基于注解的注入
注解方式注册bean,注入依赖 ,主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
描述依赖关系主要有两种:
@Resource
@Qualifier("userDaoMyBatis")
private IUserDao userDao;
public UserService(){
@Autowired
@Qualifier("userDaoJdbc")
private IUserDao userDao;
注1:懒加载,即当对象需要用到的时候再去加载。初始化时不进行加载。
注解方式:@Lazy
xml配置方式:
<bean id="app" class="App" lazy-init="true"></bean>
注2:集合类型属性的注入
<!--注入list集合-->
<property name="list" >
<list>
<value>张三</value>
<value>李四</value>
<value>王五</value>
</list>
</property>
<!--注入set集合-->
<property name="set">
<set>
<value>aaa</value>
<value>bbb</value>
<value>xxx</value>
</set>
</property>
<!--注入Map集合-->
<property name="map">
<map>
<entry key="aa" value="11"/>
<entry key="bb" value="22"/>
<entry key="cc" value="33"/>
</map>
</property>
Spring定义了多种Bean作用域,可以基于这些作用域创建bean,包括:
分为注解和xml两种形式:在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MyIsBean{...}
<bean id="BEANID" class = "com.my.beans" scope="prototype">
Bean 的生命周期概括起来就是 4 个阶段:
如上图所示,Bean 的生命周期还是比较复杂的,下面来对上图每一个步骤做文字描述:
Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
Bean实例化后对将Bean的引入和值注入到Bean的属性中
检查Aware的相关接口并设置相关依赖:①如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法 ②如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入 ③如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
注册Destruction相关回调接口,不是真正意义上的销毁(还没使用呢),而是先在使用前注册了销毁的相关调用接口,为了后面第9步真正销毁 bean 时再执行相应的方法。
此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。
下面我们结合代码来直观的看下,在 doCreateBean() 方法中能看到依次执行了这 4 个阶段:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 1. 实例化
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object exposedObject = bean;
try {
// 2. 属性赋值
populateBean(beanName, mbd, instanceWrapper);
// 3. 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
// 4. 销毁-注册回调接口
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
return exposedObject;
}
容器管理的 Bean 一般不需要了解容器的状态和直接使用容器, 但是在某些情况下, 是需要在 Bean 中直接对IOC容器进行操作的, 可以通过特定的 Aware
接口来完成。
Spring中提供了一些以Aware结尾的接口,实现了Aware接口的bean再被初始化之后,可以获取相应资源,通过Aware接口,可以对Spring相应资源进行操作。
接口名 | 描述 |
ApplicationContextAware | 实现了这个接口的类都可以获取到一个 ApplicationContext 对象. 可以获取容器中的所有 Bean |
BeanClassLoaderAware | 获取 bean 的类加载器 |
BeanFactoryAware | 获取 bean 的工厂 |
BeanNameAware | 获取 bean 在容器中的名字 |
BeanPostProcessor也称为Bean后置处理器,它是Spring中定义的接口,在Spring容器的创建过程中(具体为Bean初始化前后)会回调BeanPostProcessor中定义的两个方法。BeanPostProcessor的源码如下:
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
其中postProcessBeforeInitialization方法会在每一个bean对象的初始化方法调用之前回调;
postProcessAfterInitialization方法会在每个bean对象的初始化方法调用之后被回调。
文章参考:
https://www.toutiao.com/a6759714946667250179/
https://www.toutiao.com/a6795466742278652419/
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!