【一步一步学习spring】【番外】IOC核心知识点
1、为什么需要IOC
在JAVA的世界中,一个对象A怎么才能调用对象B?通常有以下几种方法。
类别 | 描述 | 时间点 |
---|---|---|
外部传入 | 构造方法传入 | 创建引用对象时 |
外部传入 | 属性设置传入 | 设置对象状态时 |
外部传入 | 运行时做为参数传入 | 调用时 |
内部创建 | 属性中直接创建 | 创建引用对象时 |
内部创建 | 初始化方法创建 | 创建引用对象时 |
内部创建 | 运行时动态创建 | 调用时 |
上表可以看到, 引用一个对象可以在不同地点(其它引用者)、不同时间由不同的方法完成。如果B只是一个非常简单的对象 如直接new B(),怎样都不会觉得复杂,比如你从来不会觉得创建一个String 是一个件复杂的事情。
但如果B 是一个有着复杂依赖的Service对象,这时在不同时机引用B将会变得很复杂。无时无刻都要维护B的复杂依赖关系,试想B对象如果项目中有上百过,系统复杂度将会成陪数增加。
IOC容器 的出现正是为解决这一问题,其可以将对象的构建方式统一,并且自动维护对象的依赖关系,从而降低系统的实现成本。
在Spring中,对象无需自己查找和创建与其关联的其他对象。相反,容器负责把需要相互协作的对象引用赋予各个对象。例如:一个订单管理组件需要信用卡认证组件,但他不需要自己创建信用卡组件,只需要表明自己需要即可,容器就会自动赋予一个信用卡组件。
2、实体Bean的构建
-
创建spring demo工程
- 创建maven工程。pom文件如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.xxx</groupId> <artifactId>spring-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.18.RELEASE</version> </dependency> </dependencies> </project>
- 创建一个bean类
package com.test; public class HelloSpring { private String name; private int age; public HelloSpring(String name, int age) { this.name = name; this.age = age; } public void say() { System.out.println("hello " + this.name + ", age " + this.age + "."); } }
- 在resources下创建spring.xml文件,其中定义HelloSpring的对象。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.test" /> <bean class="com.test.HelloSpring"> <constructor-arg name="name" value="xxx"></constructor-arg> <constructor-arg name="age" value="28"></constructor-arg> </bean> </beans>
- 创建启动类,加载spring.xml文件
package com.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("spring.xml"); HelloSpring h = context.getBean(HelloSpring.class); h.say(); } }
-
基于ClassName构建
<bean class="com.tuling.spring.HelloSpring"></bean>
这是最常规的方法,其原理是在spring底层会基于class,属性通过Class.newInstance()
反射进行构建。
- 构造方法构建(上边的示例中就是这个方式)
<bean class="com.spring.HelloSpring">
<constructor-arg name="name" type="java.lang.String" value="luban"/>
<constructor-arg index="1" type="java.lang.String" value="sex" />
</bean>
如果需要基于参数进行构建,就采用构造方法构建,其对应属性如下:
- name:构造方法参数变量名称
- type:参数类型
- index:参数索引,从0开始
- value:参数值,spring 会自动转换成参数实际类型值
- ref:引用容串的其它对象
3、静态工厂方法创建(个人感觉这个应该挺好用的)
<bean class="com.spring.HelloSpring" factory-method="build">
<constructor-arg name="type" type="java.lang.String" value="B"/>
</bean>
如果你正在对一个对象进行A/B测试 ,就可以采用静态工厂方法的方式创建,其于策略创建不同的对像或填充不同的属性。
该模式下必须创建一个静态工厂方法,并且方法返回该实例,spring 会调用该静态方法创建对象。
public static HelloSpring build(String type) {
if (type.equals("A")) {
return new HelloSpring("xxx", "man");
} else if (type.equals("B")) {
return new HelloSpring("yyy", "woman");
} else {
throw new IllegalArgumentException("type must A or B");
}
}
4、FactoryBean创建(自定义bean的创建过程,更加灵活,mybatis中就很多用到了。)
<bean class="com.spring.LubanFactoryBean" id="helloSpring123"></bean>
指定一个Bean工厂来创建对象,对象构建初始化 完全交给该工厂来实现。配置Bean时指定该工厂类的类名。
public class LubanFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
return new HelloSpring();
}
@Override
public Class<?> getObjectType() {
return HelloSpring.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
3、bean的基本特性
- 作用范围
很多时候Bean对象是无状态的,而有些又是有状态的。
无状态的对象我们采用单例即可,而有状态则必须是多例的模式,
模式通过scope 即可区分:
scope="prototype":多例
scope="singleton":单例
<bean class="com.spring.HelloSpring" scope="prototype"></bean>
- 生命周期
Bean对象的创建、初始化、销毁即是Bean的生命周期。通过 init-method、destroy-method 属性可以分别指定期构建方法与初始方法。
<bean class="com.spring.HelloSpring" init-method="init" destroy-method="destroy"></bean>
如果觉得麻烦,可以让Bean去实现 InitializingBean.afterPropertiesSet()、DisposableBean.destroy()方法。分别对应初始和销毁方法
- 加载机制
指示Bean在何时进行加载。设置lazy-init 即可,其值如下:
true: 懒加载,即延迟加载
false:非懒加载,容器启动时即创建对象
default:默认,采用default-lazy-init 中指定值,如果default-lazy-init 没指定就是false
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-lazy-init="true">
什么时候使用懒加载?
懒加载会容器启动的更快,而非懒加载可以容器启动时更快的发现程序当中的错误 ,选择哪一个就看追求的是启动速度,还是希望更早的发现错误,一般我们会选 择后者。
4、依赖注入
试想IOC中如果没有依赖注入,那这个框架就只能帮助我们构建一些简单的Bean,而之前所说的复杂Bean的构建问题将无法解决,spring这个框架不可能会像现在这样成功。 其实构建的Bean只是原料,具体的使用还是在依赖注入上。
spring 中 ioc 如何依赖注入呢。有以下几种方式:
- set方法注入
- 构造方法注入
- 自动注入(byName、byType)
- 方法注入(lookup-method)
- set方法注入
<bean class="com.spring.HelloSpring">
<property name="fine" ref="fineSpring"/>
</bean>
- 构造方法注入
<bean class="com.spring.HelloSpring">
<constructor-arg name="fine">
<bean class="com.spring.FineSpring"/>
</constructor-arg>
</bean>
- 自动注入(byName\byType\constructor)
<bean id="helloSpringAutowireConstructor" class="com.spring.HelloSpring" autowire="byName">
</bean>
byName:基于变量名与bean 名称相同作为依据插入
byType:基于变量类别与bean 名称作
constructor:基于IOC中bean 与构造方法进行匹配(语义模糊,不推荐)
- 依赖方法注入(lookup-method)(用的比较少)
当一个单例的Bean,依赖于一个多例的Bean,用常规方法只会被注入一次,如果每次都想要获取一个全新实例就可以采用lookup-method 方法来实现。
#编写一个抽像类
public abstract class MethodInject {
public void handlerRequest() {
// 通过对该抽像方法的调用获取最新实例
getFine();
}
# 编写一个抽像方法
public abstract FineSpring getFine();
}
// 设定抽像方法实现
<bean id="MethodInject" class="com.spring.MethodInject">
<lookup-method name="getFine" bean="fine"></lookup-method>
</bean>
该操作的原理是基于动态代理技术,重新生成一个继承至目标类,然后重写抽像方法到达注入目的。
前面说所单例Bean依赖多例Bean这种情况也可以通过实现 ApplicationContextAware 、BeanFactoryAware 接口来获取BeanFactory 实例,从而可以直接调用getBean方法获取新实例,推荐使用该方法,相比lookup-method语义逻辑更清楚一些。