文章目录
Spring IoC容器与Bean管理
Spring快速入门
IoC控制反转
- IoC控制反转, 全称Inversion of Control, 是一种设计理念
- 由代理人来创建与管理对象, 消费者通过代理人来获取对象
- IoC的目的是降低对象之间的直接耦合
- 加入IoC容器将对象统一管理, 让对象关联变为弱耦合
DI依赖注入
- IoC是设计理念, 是现代程序设计遵循的标准, 是宏观目标
- DI(Dependency Injection) 是具体技术实现, 是微观实现
- DI在Java中利用反射技术实现对象注入(Injection)
传统开发方式
- 对象之间存在彼此直接引用导致对象硬性关联, 程序难以扩展维护
- 比如Service调用Dao是直接new的话, 如果这个Dao不在适合当前应用, 要换其他Dao类的话, 就要修改Service类的源代码, 来new其他Dao类, 而修改源代码后, 就要重新编译、测试、发布、上线, 这个过程是非常繁琐的.
Spring IoC容器
- IoC容器是Spring生态的地基, 用于统一创建与管理对象依赖
Spring IoC容器职责
- 对象的控制权交由第三方统一管理(IoC控制反转)
- 利用Java反射技术实现运行时对象创建与关联(DI依赖注入)
- 基于配置提高应用程序的可维护性与扩展性
Spring XML配置管理对象(Bean)
三种配置方式
- 基于XML配置Bean
- 基于构造方法实例化(常用)
- 基于静态工厂实例化(了解)
- 基于工厂实例方法实例化(了解)
- 基于注解配置Bean
- 基于Java代码配置Bean
基于XML配置Bean
核心配置文件名(约定俗成): applicationContext.xml
<bean id="sweetApple" class="com.zk.spring.ioc.entity.Apple">
<property name="title" value="红富士"/>
<property name="origin" value="欧洲"/>
<property name="color" value="红色"/>
</bean>
XML方式创建IoC容器(加载xml配置文件)
// 创建Spring IoC容器, 并根据配置文件在容器中实例化对象
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
从IoC容器获取Bean
bean 标签 id 与 name属性相同点
- bean id 与 name 都是设置对象在IoC容器中唯一标识
- 两者在同一个配置文件中都不允许出现重复
- 两者允许在多个配置文件中出现重复, 新对象覆盖旧对象
bean 标签 id 与 name属性区别
- id要求更为严格, 一次只能定义一个对象标识(推荐)
- name更为宽松, 一次允许定义多个对象标识
- tips: id与name的命名要求有意义, 按小驼峰命名书写
路径匹配表达式
加载单个配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
classpath:applicationContext.xml
路径表达式是指:
从类路径中寻找applicationContext.xml文件, Maven工程加载的时候的类路径是target/classes这一级目录
加载多配置文件
String[] configLocations = new String[]{"classpath:applicationContext.xml", "classpath:applicationContext-1.xml"};
ApplicationContext context = new ClassPathXmlApplicationContext(configLocations);
路径表达式
表达式实例 | 说明 |
---|---|
classpath:config.xml | 扫描classpath根路径(不包含jar)的config.xml |
classpath:com/zk/config.xml | 扫描classpath下(不包含jar)com.zk包中的config.xml |
classpath*:com/zk/config.xml | 扫描classpath下(包含jar)com.zk包中的config.xml |
classpath:config-*.xml | 扫描classpath根路径下所有的以config-开头的XML文件 |
classpath:com/**/config.xml | 扫描com包下(包含任何子包)的config.xml(了解) |
file:/linux/home/config.xml | 扫描/linux/home根路径config.xml(了解) |
对象依赖注入
- 依赖注入是指运行时将容器内对象利用反射赋给其他对象的操作
- 基于setter方法注入对象(常用)
- 基于构造方法注入对象
利用setter实现对象依赖注入
<bean id="bookDao" class="com.zk.spring.ioc.bookshop.dao.BookDaoOracleImpl">
</bean>
<bean id="bookService" class="com.zk.spring.ioc.bookshop.service.BookService">
<property name="bookDao" ref="bookDao"/>
</bean>
利用构造方法实现对象依赖注入
<bean id="sweetApple" class="com.zk.spring.ioc.entity.Apple">
<!-- IoC容器自动利用反射机制在运行时调用setXXX方法为属性赋值 -->
<property name="title" value="红富士"/>
<property name="origin" value="欧洲"/>
<property name="color" value="红色"/>
</bean>
<bean id="andy" class="com.zk.spring.ioc.entity.Child">
<constructor-arg name="name" value="andy"/>
<constructor-arg name="apple" ref="sweetApple"/>
</bean>
注入集合对象
List, Set, Map, Properties
<bean id="company" class="com.zk.spring.ioc.entity.Company">
<property name="rooms">
<list>
<value>2001-总裁办</value>
<value>2003-总经理办公室</value>
<value>2010-研发部会议室</value>
<value>2010-研发部会议室</value>
</list>
</property>
<property name="computers">
<map>
<entry key="dev-88172" value-ref="c1"/>
<entry key="dev-88173">
<bean class="com.zk.spring.ioc.entity.Computer">
<constructor-arg name="brand" value="联想"/>
<constructor-arg name="type" value="台式机"/>
<constructor-arg name="sn" value="84324324324"/>
<constructor-arg name="price" value="3085"/>
</bean>
</entry>
</map>
</property>
<property name="info">
<props>
<prop key="phone">0101023-123123</prop>
<prop key="address">北京市xxx</prop>
<prop key="website">https://www.xxx.com</prop>
</props>
</property>
</bean>
查看容器内对象
// 获取容器内所有beanId数组
String[] beanNames = context.getBeanDefinitionNames();
for (String beanName : beanNames) {
System.out.println(beanName);
System.out.println("类型:" + context.getBean(beanName).getClass().getName());
System.out.println("内容:" + context.getBean(beanName));
}
bean scope属性
- bean scope属性用于决定对象何时被创建与作用范围
- bean scope配置将影响容器内对象的数量
- bean scope默认值singleton(单例), 指全局共享同一个对象实例
scope属性 | 说明 |
---|---|
singleton | 单例(默认值), 每一个容器有且只有唯一的实例, 实例被全局共享 |
prototype | 多例, 每次使用时都是创建一个实例 |
request | web环境下, 每一次独立请求存在唯一实例(生存范围: 请求), 了解 |
session | web环境下, 每一个session存在有唯一实例, 了解 |
application | web环境下, ServletContext存在唯一实例, 了解 |
websocket | 每一次WebSocket连接中存在唯一实例, 了解 |
singleton在容器是单例多线程执行, 存在线程安全风险
prototype在容器中多实例, 占用更多资源, 不存在线程安全问题
singleton与prototype对比
singleton | prototype | |
---|---|---|
对象数量 | 全局唯一 | 存在多个 |
实例化时机 | IoC容器启动时 | getBean()或对象注入时 |
线程安全问题 | 存在 | 不存在 |
执行效率 | 高 | 低 |
tips: 因为Service中的Dao类是稳定的(恒定不变的), 所以设置为单例没问题
如果一个类中某个属性是会改变的, 要设置成多例
bean的生命周期
<bean id="order1" class="com.zk.spring.ioc.entity.Order" init-method="init" destroy-method="destroy">
<property name="price" value="19.8"/>
<property name="quantity" value="1000"/>
</bean>
public class Order {
private Float price;
private Integer quantity;
private Float total;
public void init() {
total = price * quantity;
}
public void destroy() {
System.out.println("释放与订单对象相关的资源");
}
((ClassPathXmlApplicationContext) context).registerShutdownHook(); // IoC容器开始进行销毁, 会调用对象的destory-method所声明的方法, 来完成对象的释放
实现极简IoC容器
public class ClassPathXmlApplicationContext implements ApplicationContext {
private Map<String, Object> iocContainer = new HashMap<>();
public ClassPathXmlApplicationContext() {
try {
String filePath = this.getClass().getResource("/applicationContext.xml").getPath();
filePath = URLDecoder.decode(filePath, "UTF-8");
SAXReader reader = new SAXReader();
Document document = reader.read(new File(filePath));
List<Node> beans = document.getRootElement().selectNodes("bean");
for (Node bean : beans) {
Element element = (Element) bean;
String id = element.attributeValue("id");
String className = element.attributeValue("class");
Class<?> c = Class.forName(className);
Object obj = c.newInstance();
List<Node> properties = element.selectNodes("property");
for (Node property : properties) {
Element ele = (Element) property;
String propName = ele.attributeValue("name");
String propValue = ele.attributeValue("value");
String setMethodName = "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
System.out.println("准备执行" + setMethodName + "方法注入数据");
Method setMethod = c.getMethod(setMethodName, String.class);
// 通过setter方法注入数据
setMethod.invoke(obj, propValue);
}
iocContainer.put(id, obj);
}
System.out.println("IoC容器初始化完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Object getBean(String beanId) {
return iocContainer.get(beanId);
}
}
注解与Java Config配置IoC容器
基于注解配置IoC容器
基于注解的优势
- 摆脱繁琐的XML形式的bean与依赖注入配置
- 基于"声明式"的原则, 更适合轻量级的现代企业应用
- 让代码可读性变得更好, 研发人员拥有更好的开发体验
三类注解
- 组件类型注解-声明当前类的功能与职责
- 自动装配注解-根据属性特征自动注入对象
- 元数据注解-更细化的辅助IoC容器管理对象的注解
四种组件类型注解
注解 | 说明 |
---|---|
@Component | 组件注解, 通用注解, 被该注解描述的类将被IoC容器管理并实例化 |
@Controller | 语义注解, 说明当前类是MVC应用中的控制器类 |
@Service | 语义注解, 说明当前类是Service业务服务类 |
@Repository | 语义注解, 说明当前类用于业务持久层, 通常描述对应Dao类 |
开启组件扫描
<!-- XML配置开启组件扫描, 才能使用注解, 在IoC容器初始化时自动扫描四种组件类型注解并完成实例化 -->
<context:component-scan base-package="com.zk">
<!-- 排除某个包, 不进行IoC管理, 不常用 -->
<context:exclude-filter type="regex" expression="com.zk.xx.*"/>
</context:component-scan>
两类自动装配注解
分类 | 注解 | 说明 |
---|---|---|
按类型装配(不建议使用) | @Autowired | 按容器内对象类型动态注入属性, 由Spring机构提供 |
@Inject | 基于JSR-330标准, 其他同@Autowired, 但不支持required属性 | |
按名称装配 | @Named | 与@Inject配合使用, JSR-330规范, 按属性名自动装配属性 |
@Resource | 基于JSR-250规范, 优先按名称, 再按类型智能匹配(最常用) |
- 如果装配注解放在set方法上, 则自动按类型/名称对set方法参数进行注入
- 如果装配注解放在属性上, Spring IoC容器会自动通过反射技术将属性private修饰符自动改为public, 直接进行赋值, 不再执行set方法
@Resource注解详解
- @Resource如果设置name属性, 则按name属性的值作为bean name在IoC容器中将bean注入
- @Resource未设置name属性
- 以属性名作为bean name在IoC容器中匹配bean, 如有匹配则注入
- 按属性名未匹配到, 则按类型进行匹配, 同@Autowired, 需加入@Primary注解解决类型冲突
- 使用建议: 在使用@Resource时推荐设置name或保证属性名与bean name一致
元数据注解
注解 | 说明 |
---|---|
@Primary | 按类型装配时出现多个相同类型对象, 拥有此注解对象优先被注入 |
@PostConstruct | 描述方法, 相当于XML中init-method配置的注解版本 |
@PreDestroy | 描述方法, 相当于XML中destroy-method配置的注解版本 |
@Scope | 设置bean的scope属性 |
@Value | 为属性注入静态数据 |
@Value读取properties文件属性值的用法
<!-- 通知Spring IoC容器初始化时加载属性文件 -->
<context:property-placeholder location="classpath:config.properties"/>
@Value("${metaData}")
private String metaData;
基于Java Config配置IoC容器
- 完全摆脱XML的束缚, 使用独立Java类管理对象与依赖
- 注解配置相对分散, 利用Java Config可对配置集中管理
- 可以在编译时进行依赖检查, 不容易出错
Java Config 核心注解
注解 | 说明 |
---|---|
@Configuration | 描述类, 说明当前类是Java Config配置类, 完全替代XML文件 |
@Bean | 描述方法, 方法返回的对象将被IoC容器管理, beanId默认为方法名 |
@ImportResource | 描述类, 加载静态配置文件, 可使用@Value注解获取 |
@ComponentScan | 描述类, 同XML的<context:component-scan>标签 |
// 基于Java Config配置IoC容器的初始化
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Spring单元测试
Spring Test测试模块
- Spring Test是Spring中用于测试的模块
- Spring Test对JUnit单元测试框架有良好的整合
- 通过Spring Test可在JUnit在单元测试时自动初始化IoC容器
Spring与JUnit4整合过程
- Maven工程依赖spring-test和junit
- 利用@RunWith与@ContextConfiguration注解描述测试用例类
@RunWith(SpringJUnit4ClassRunner.class)
将JUnit4的执行权交由Spring Test, 在测试用例执行前自动初始化IoC容器@ContextConfiguration(location = {"classpath:applicationContext.xml"})
在IoC容器初始化时指定要加载的配置文件
- 测试用例类从容器获取对象完成测试用例的执行