ssm框架之Spring(上)
你是否也曾在 Java 开发中陷入这样的困境:手动管理对象依赖时,代码里满是繁琐的new关键字,一旦需求变动,牵一发而动全身;想要实现事务控制,又得在业务逻辑中嵌入大量重复的事务处理代码,既臃肿又难以维护?如果你对这些场景深有体会,那么你一定听说过甚至正在使用一个改变了 Java 开发格局的框架 ——Spring。从 2003 年首次发布至今,Spring 早已不是简单的 “工具类集合”,它以 “简化开发、降低耦合” 为核心,构建起了一套覆盖从后端服务到前端交互、从单体应用到微服务架构的完整生态,成为全球数百万 Java 开发者的 “标配”。今天,我们就一起走进 Spring 的世界,看看这个 “开发神器” 究竟如何解决我们的痛点,又为何能在二十多年的技术迭代中始终保持活力,尤其聚焦其核心思想之一 ——IOC(控制反转)的魅力。
一、核心概念
1,IOC(Inversion of Control)控制反转
使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
2,Spring技术对IOC思想进行了实现
Spring提供了一个容器,称为IOC容器,用来充当IOC思想的“外部”。
IOC容器负责对象的创建,初始化等一系列工作,被创建或被管理的对象在IOC容器中统称为Bean。
3,DI(Dependency Injection)依赖注入
在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入。

二、基于XML配置的Spring应用
1,Bean的常用配置概览

2,SpringBean的配置详解
1,Bean的基础配置
例如:配置UserDaoImpl由Spring容器负责管理
<bean id="userDao" class="com.Dao.Impl.UserDaoImpl"/>此时存储到Spring容器(singleObjects单例池)中的Bean的beanName是userDao,值是UserDaoImpl对象,可以根据beanName获取Bean实例
applicationContext.getBean("userDao");如果不配置id,则Spring会把当前Bean实例的全限定名作为beanName
2,Bean的别名配置(少用)
可以为当前Bean指定多个别名,根据别名也可以获得Bean对象
<bean id="userDao" name="aaa,bbb" class="com.mxw.Dao.Impl.UserDaoImpl"/>此时多个名称都可以获得UserDaoImpl实例对象
userDao userdao = (userDao) applicationContext.getBean("userDao"); userDao userdao1 = (userDao) applicationContext.getBean("aaa"); userDao userdao2 = (userDao) applicationContext.getBean("bbb");3,Bean的范围配置------scope
默认情况下,单纯的Spring环境Bean的作用范围有两个:Singleton和Prototype
singleton:单例,默认值,Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例;
prototype:原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。(不会存储到容器内部的单例池中)
4,Bean的延迟加载
当lazy-init设置为true时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等到用到时在创建Bean实例并存储到单例池中去,后续再使用该Bean直接从单例池获取即可,本质上该Bean还是单例的。
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl" lazy-init="true"/>5,Bean的初始化和销毁方法配置(不常用)
Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作,初始化方法名称和销毁方法名称通过以下配置:
// xml配置内容 <bean id="userDao" class="com.Dao.Impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>// 测试类内容 //ApplicationContext类中没有关闭方法,需要使用ClassPathXmlApplicationContext类 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml"); userDao userdao = (userDao) applicationContext.getBean("userDao"); // 显式关闭 applicationContext.close();扩展:除此之外,我们还可以通过实现InitializingBean接口,完成一些Bean的初始化操作。
6,Bean的实例化配置
Spring的实例化方式主要如下两种:
1)构造方式实例化:底层通过构造方法对Bean进行实例化
构造方式实例化Bean又分为无参构造方法实例化和有参构造方法实例化,Spring中配置的几乎都是无参构造方式,以下是有参构造方式实例化Bean:
public UserDaoImpl(String name){System.out.println(name);}有参构造在实例化Bean时,需要参数的注入,通过标签,嵌入在标签内部提供构造参数,如下:
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl"> <!-- "name"表示参数名 "haohao"表示给参数赋的值 --> <constructor-arg name="name" value="haohao"/> </bean>工厂方式实例化:底层通过调用自定义的工厂方法对Bean进行实例化,又分为如下三种:
1,静态工厂方法实例化Bean
// 无参数版
// factory类中内容
public class MyBeanFactory {public static userDao userDao(){// 利用工厂方法创建对象的好处:// Bean创建之前可以进行一些其他业务逻辑操作return new UserDaoImpl();}
}
// xml文件内容
<bean id="userDao1" class="com.mxw.factory.MyBeanFactory" factory-method="userDao"></bean>// 有参数版
// factory类中内容
public class MyBeanFactory {public static userDao userDao(String name,int age){return new UserDaoImpl();}
}
// xml文件内容
<bean id="userDao1" class="com.mxw.factory.MyBeanFactory" factory-method="userDao"><constructor-arg name="name" value="haohao"></constructor-arg><constructor-arg name="age" value="18"></constructor-arg>
</bean>2,实例工厂方法实例化Bean
// 无参数版
// factory类中内容
public class MyBeanFactory2 {public userDao userDao(){return new UserDaoImpl();}
}
// xml文件内容
// 实例方法需要现有实例对象才能调用 1,配置工厂对象 2,配置工厂方法
<!-- 配置工厂对象 -->
<bean id="myBeanFactory2" class="com.mxw.factory.MyBeanFactory2"></bean><bean id="userDao2" factory-bean="myBeanFactory2" factory-method="userDao"></bean>
// 有参数版
// factory类中内容
public class MyBeanFactory2 {public userDao userDao(String username){return new UserDaoImpl();}
}
// xml文件内容
<!-- 配置工厂对象 -->
<bean id="myBeanFactory2" class="com.mxw.factory.MyBeanFactory2"></bean><bean id="userDao2" factory-bean="myBeanFactory2" factory-method="userDao"><constructor-arg name="username" value="tom"></constructor-arg>
</bean>注意:只要是产生bean对象的方法内部参数配置都可以使用标签
3,实现FactoryBean规范延迟实例化Bean
// factory类中内容
public class MyBeanFactory3 implements FactoryBean<userDao> {@Overridepublic userDao getObject() throws Exception {return new UserDaoImpl();}@Overridepublic Class<?> getObjectType() {return userDao.class;}
}
// xml文件内容
// 不用配置工厂方法,由于是一种规范,所以默认调用getObject方法
<bean id="userDao3" class="com.mxw.factory.MyBeanFactory3"></bean>注意:对象不像之前一样保存在beanFactory中的signaltonObjects中(signaltonObjects中保存的是MyBeanFactory3),而是保存在beanFactory中的factoryBeanObjectCache中。并且是在调用了getbean方法时才调用getObject方法返回对象。(用到的时候产生bean对象)
7,Bean的依赖注入配置
Bean的依赖注入有两种方式:

其中,ref是reference的缩写形式,翻译为:涉及,参考的意思,用于引用其他Bean的id。value用于注入普通属性值。
依赖注入的数据类型有如下三种:
1)普通数据类型,例如:String,int,boolean等,通过value属性指定。
2)引用数据类型,例如:UserDaoImpl,DataSource等,通过ref属性指定。
3)集合数据类型,例如:List,Map,Properties等。
// userServiceImpl类中内容
public class userServiceImpl implements userService {// 注入Listprivate List<String> stringList;private List<userDao> daoList;public void setStringList(List<String> stringList) {this.stringList = stringList;}public void setDaoList(List<userDao> daoList) {this.daoList = daoList;}
}
// xml文件内容
<bean id="userService" class="com.mxw.Service.Impl.userServiceImpl"><property name="stringList"><list><value>1</value><value>2</value><value>3</value></list></property><property name="daoList"><list><!-- 方式一 --><bean class="com.mxw.Dao.Impl.UserDaoImpl"></bean><!-- 方式二 --><ref bean="userDao"></ref></list></property></bean>
<!-- 该处bean又上面引用 --><bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl"></bean>// userServiceImpl类中内容
public class userServiceImpl implements userService {// 注入setprivate Set<String> stringSet;private Set<userDao> daoSet;public void setDaoSet(Set<userDao> daoSet) {this.daoSet = daoSet;}public void setStringSet(Set<String> stringSet) {this.stringSet = stringSet;}
}
// xml文件内容
<bean id="userService" class="com.mxw.Service.Impl.userServiceImpl"><property name="stringSet"><set><value>1</value><value>2</value><value>3</value></set></property><property name="daoSet"><set><!-- 注入set的两种方式和注入list的两种方式一样,这里只写第一种 --><bean class="com.mxw.Dao.Impl.UserDaoImpl"></bean></set></property><!--Map注入-->
<property name="objects"><map><entry key="张明" value="1"></entry><entry key="李白" value="2"></entry><entry key="王伟" value="3"></entry></map>
</property>
</bean>// userServiceImpl类中内容
public class userServiceImpl implements userService {// 注入propertiesprivate Properties properties;public void setProperties(Properties properties) {this.properties = properties;}@Overridepublic void show() {System.out.println(properties);}
}
// xml文件内容
<bean id="userService" class="com.mxw.Service.Impl.userServiceImpl"><property name="properties"><props><prop key="p1">ppp</prop><prop key="p2">ppp</prop></props></property>
</bean>扩展:自动装配方式
如果被注入的属性类型是Bean引用的话,那么可以在标签中使用autowire属性去配置自动注入方式,属性值有两个:
byName:通过属性名自动装配,即去匹配setXxx与id="xxx"(name="xxx")是否一致;
byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时,报错。
// 1,byName// userServiceImpl类中内容public class userServiceImpl implements userService {private userDao userDao;public void setUserDao(userDao userDao) {this.userDao = userDao;}
}
// xml文件内容
// 根据id="userDao"的beanName和setUserDao的“UserDao”一样,所以将第二个bean对象注入到第一个bean对象中。
<bean id="userService" class="com.mxw.Service.Impl.userServiceImpl" autowire="byName"></bean>
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl"></bean>// 1,byType
// userServiceImpl类中内容不变
// xml文件内容
<bean id="userService" class="com.mxw.Service.Impl.userServiceImpl" autowire="byType"></bean>
// 如果设置多个同类型的bean会报错(报错内容:No qualifying bean of type 'com.mxw.Dao.userDao' available: expected single matching bean but found 2: userDao,userDao1)
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl"></bean>
<bean id="userDao1" class="com.mxw.Dao.Impl.UserDaoImpl"></bean>8,Spring的其他配置标签
Spring的xml标签大体上分为两类,一种是默认标签,一种是自定义标签
默认标签:就是不用额外导入其他命名空间约束的标签,例如标签
Spring的默认标签用到的是Spring的默认命名空间

该命名空间约束下的默认标签如下:

自定义标签:就是需要额外引入其他命名空间约束,并通过前缀引用的标签,例如标签
标签,除了经常用的作为根标签外,还可以嵌套在根标签内,使用profile属性切换开发环境
<!-- 配置测试环境下,需要加载的Bean实例--><beans profile="test"></beans>
<!-- 配置开发环境下,需要加载的Bean实例--><beans profile="dev"></beans>可以使用以下两种方式指定被激活的环境:
使用命令行动态参数,虚拟机参数未知加载-Dspring.profiles.active=test
使用代码的方式设置环境变量System.setProperty("spring.profiles.active","test")
// 测试类中内容// 指定开发环境 (如果指定了环境那么只有环境里面的和公共部分的生效,如果没有指定环境那么只有公共环境的生效)
System.setProperty("spring.profiles.active","dev");<import>标签,用于导入其他配置文件,项目变大后,就会导致一个配置文件内容过多,可以将一个配置文件根据业务模块进行拆分,拆分后,最终通过标签导入到一个主配置文件中,项目加载主配置文件就连同导入的文件一并加载了
<!-- 导入用户模块配置文件--><import resource="classpath:UserModuleApplicationContext.xml"></import>
<!-- 导入商品模块配置文件--><import resource="classpath:UserModuleApplicationContext.xml"></import><alias>标签是为某个Bean添加别名,与在标签上使用name属性添加别名的方式一样。
// xml文件内容
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl"></bean>
<alias name="userDao" alias="aaa"></alias>在beanFactory中维护着一个名为aliasMap集合,存储别名和beanName之间的映射关系

9,Spring的get方法

// 测试类中内容
// 根据beanName获取容器中的Bean实现,需要手动强转
userService userService = (userService) applicationContext.getBean("userService");
// 根据Bean类型去容器中匹配对应的Bean实例,如存在多个匹配Bean则报错
userDao userDao = applicationContext.getBean(userDao.class);
// 根据beanName获取容器中的Bean实例,指定Bean的Type类型
userService userService1 = applicationContext.getBean("userService", userService.class);3,Spring配置非自定义Bean
以上在xml配置的Bean都是自己定义的,例如UserDaoImpl。但是在实际开发中有些功能类并不是我们自己定义的,而是使用的第三方jar包中的,那么,这些Bean要想让Spring进行管理,也需要对其进行配置。
配置非自定义的Bean需要考虑如下两个问题:
1,被配置的Bean的实例化方式是什么?无参构造,有参构造,静态工厂还是实例工厂方式;
2,被配置的Bean是否需要注入必要属性。
三、Bean实例化的基本流程
Spring容器在进行初始化时,会将xml配置的的信息封装成一个BeanDefinition对象(一个bean对应一个BeanDefinition对象),所有的BeanDefinition存储到一个名为beanDefinitionMap的Map集合中,Spring框架在对该Map进行遍历,使用反射创建Bean实例对象,创建好的Bean对象存储在一个名为singletonObjects的Map集合中,当调用getBean方法时则最终从该Map集合中取出Bean实例对象返回。
DefaultListableBeanFactory对象内部维护着一个Map用于存储封装好的BeanDefinitionMap
public class DefaultListBeanFactory extends ... implements ...{// 存储<bean>标签对应的BeanDefinition对象// key:是Bean的beanName,value:是Bean定义对象BeanDefinitionprivate final Map<String,BeanDefinition> beanDefinitionMap;
}Spring框架会取出beanDefinitionMap中的每个BeanDefinition信息,反射构造方法或调用指定的工厂方法生成Bean实例对象,所以只要将BeanDefinition注册到beanDefinitionMap这个Map中,Spring就会进行对应的Bean的实例化操作。
Bean实例及单例池singletonObjects,beanDefinitionMap中的BeanDefinition会被转化成对应的Bean实例对象,存储到单例池singletonObjects中去,在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中,维护着singletonObjects。
// 部分源码
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);总结(Bean实例化的基本流程)
1,加载xml配置文件,解析获取配置中的每个的信息,封装成一个个的BeanDefinition对象;
2,将BeanDefinition存储在一个名为beanDefinitionMap的Map中;
3,ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
4,创建好的Bean实例对象,被存储在一个名为singletonObjects的Map中;
5,当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。

四、SpringBean的生命周期
Spring Bean的生命周期是从Bean实例化之后,即通过反射创建出对象之后,到Bean成为一个完整的对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期,Spring Bean的生命周期大体上分为三个阶段:
1,Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化;
2,Bean的初始化阶段:Bean创建之后还仅仅是个“半成品”,还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。
3,Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期。
1,Spring Bean的初始化阶段
Spring Bean的初始化过程涉及如下几个过程:
1,Bean实例的属性填充
2,Aware接口属性注入
3,BeanPostProcessor的before()方法回调
4,InitialzingBean接口的初始化方法回调
5,自定义初始化方法init回调
6,BeanPostProcessor的after()方法回调
1)Bean实例的属性填充
BeanDefinition中有对当前Bean实体的注入信息通过属性propertyValues进行了存储,例如UserService的属性信息如下(beanFactory->beanDefinitionMap->某个beaDefinition对象->value->properValues):

Spring在进行属性注入时,会分为如下几种情况:
注入普通属性,String,int或存储基本类型的集合时,直接通过set方法的反射设置进去
注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完整整个生命周期)后,在进行注入操作;
注入双向对象引用属性时,涉及了循环引用(循环依赖)问题;
多个实体之间相互依赖并形成闭环的情况就叫做“循环依赖”,也叫做”循环引用“

// userDaoImpl文件内容
private userDao userDao;
public void setUserDao(userDao userDao) {this.userDao = userDao;
}
// userServiceImpl文件内容
private userServiceImpl userService;
public void setUserService(userServiceImpl userService) {this.userService = userService;
}
// xml文件内容
<bean id="userService" class="com.mxw.Service.Impl.userServiceImpl"><property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl"><property name="userService" ref="userService"></property>
</bean>
Spring提供了三级缓存存储完整Bean实例和半成品Bean实例,用于解决循环引用问题
在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map:
// 1,最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称之为“一级缓存”
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 2,早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用,称之为“二级缓存”
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 3,单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为“三级缓存”
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);UserService和UserDao循环依赖的过程结合上述三级缓存详细描述:
1)UserService实例化对象,但尚未初始化,将UserService存储到三级缓存;
2)UserService属性注入,需要UserDao,从缓存中获取,没有UserDao;
3)UserDao实例化对象,但尚未初始化,将UserDao存储到三级缓存;
4)UserDao属性注入,需要UserService,从三级缓存获取UserService(从一级缓存开始查找),UserService从三级缓存移入二级缓存;
5)UserDao执行其他生命周期过程,最终成为了一个完整Bean,存储到一级缓存,删除二三级缓存;
6)UserService注入UserDao;
7)UserService执行其他生命周期过程,最终成为了一个完整Bean,存储到一级缓存,删除二三级缓存。
2)常用的Aware接口
Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了,就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。

2,SpringIOC整体流程总结

从解决依赖注入的 “痛点”,到支撑微服务架构的 “全能”,Spring 的价值从来不止于 “简化代码”,更在于它以 IOC 为核心搭建的一套灵活、稳定且持续进化的 “生态体系”。无论是初入行的开发者理解对象管理逻辑,还是深耕多年的架构师优化系统耦合度,IOC 都是探索 Spring 绕不开的关键。当然,Spring 的学习之路并非一蹴而就,从 IOC 容器的原理到 AOP 的实际应用,从 Spring Boot 的快速开发到 Spring Cloud 的分布式治理,每一步深入都能带来新的收获。希望这篇文章能成为你探索 Spring 的起点,接下来不妨亲手搭建一个 Spring 项目,在实践中感受 IOC 如何简化依赖管理 —— 毕竟,最好的学习方式,永远是 “动手去做”。JUST DO IT。
