Spring IOC & DI
1.Spring IOC
1.1 IOC-控制反转
IOC称之为控制反转,简单来说就是将对象的创建的权利及对象的生命周期的管理过程交由Spring框架来处理,从此在开发过程中不再需要关注对象的创建和生命周期的管理,而是在需要时由Spring框架提供,这个由spring框架管理对象创建和生命周期的机制称之为控制反转。
1.2 IOC入门
1.2.1 创建Spring的配置文件
Spring采用xml文件作为配置文件,xml文件名字任意,但通常都取名为applicationContext.xml,通常将该文件放置在classpath目录,方便后续使用。
<?xml version="1.0" encoding="UTF-8"?>
<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">
<bean id="person" class="com.yyy.Person"></bean>
</beans>
1.2.2 创建bean类
package com.yyy;
public class Person {
public void eat(){
System.out.println("吃饭");
}
public void say(){
System.out.println("说话");
}
public void init(){
System.out.println("初始化");
}
public void destory(){
System.out.println("销毁");
}
}
1.2.3 在方法中通过Spring容器获取对象并使用
public static void main(String[] args){
//1.初始化Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2.通过Spring容器获取bean
Person p = (Person) context.getBean("person");
p.eat();
p.say();
//3.关闭Spring容器
((ClassPathXmlApplicationContext)context).close();
}
1.3 IOC的实现原理
在初始化一个Spring容器时,Spring会去解析指定的xml文件,当解析到其中的标签时,会根据该标签中的class属性指定的类的全路径名,通过反射创建该类的对象,并将该对象存入内置的Map中管理。其中键就是该标签的id值,值就是该对象。
然后,当通过getBean方法来从容器中获取对象时,其实就是根据传入的条件在内置的Map中寻找是否有匹配的键值,如果有则将该键值对中保存的对象返回,如果没有匹配到则抛出异常。
1.4 IOC获取对象的方式
通过context.getBean()方法获取bean时,可以通过如下两种方式获取:
传入id值
传入class类型
通过class方式获取bean时,如果同一个类配置过多个bean,则在获取时因为无法确定到底要获取哪个bean会抛出异常。
而id是唯一的,不存在这样的问题,所以一般使用id获取bean。
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Person p = context.getBean(Person.class);
Person p = (Person) context.getBean("person");
SpringIOC在通过class获取bean时,如果找不到该类型的bean还会去检查是否存在该类型的子类型的bean,如果有则返回,如果找不到或找到多个则抛出异常。这符合java面向对象思想中的多态的特性。
1.5 别名标签
在Spring中提供了别名标签可以为配置的起一个别名,要注意的是这仅仅是对指定的起的一个额外的名字,并不会额外的创建对象存入map。
<?xml version="1.0" encoding="UTF-8"?>
<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">
<bean id="person" class="com.yyy.Person"></bean>
<alias name="person" alias="pers"></alias>
</beans>
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Person p = (Person) context.getBean("pers");
1.6 Spring创建对象的方式
1.6.1 通过类的无法构造方法创建对象
当用最普通方式配置一个bean时,默认就是采用类的无参构造创建对象。
在Spring容器初始化时,通过bean上配置的class属性反射得到字节码对象,通过newInstance()创建对象。
Class c = Class.forName("类的全路径名称")
Object obj = c.newInstance()
这种方式下spring创建对象,要求类必须有无参的构造,否则无法通过反射创建对象,会抛出异常。
1.6.2 通过静态工厂创建对象
当我们面对的类是无法通过无参构造去创建的,例如该类没有无参构造、是一抽象类等等情况,此时无法要求spring通过无参构造创建对象,此时可以使用静态工厂方式创建对象。
public classPerson{
public Person(Stringname){
System.out.println("Person对象被创建");
}
}
public class PersonStaticFactory {
public static Person getInstance(){
return new Person("xiaoming");
}
}
<bean id="person" class=“spring.PersonStaticFactory" factory-method="getInstance"></bean>
1.6.3 通过实例工厂创建对象
public class Person{
public Person(String name){
System.out.println("Person对象被创建");
}
}
public class PersonInstanceFactory {
public Person getInstance(){
return new Person("xiaoming");
}
}
<bean id=“personInstanceFactory” class=" spring.PersonInstanceFactory "></bean>
<bean id="person"factory-bean="personInstanceFactory"factory-method="getInstance"></bean>
1.6.4 Spring工厂创建对象
Spring内置了工厂接口FactoryBean,也可以通过实现这个接口来创建对象
public class Person{
public Person(String name){
System.out.println("Person对象被创建");
}
}
public class PersonSpringFactory implements FactoryBean<Person>{
@Override
public Person getObject() throws Exception {
return new Person("xiaoming");
}
@Override
public Class<?> getObjectType() {
return Person.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
<bean id=“person”
class=“spring.PersonSpringFactory"></bean>
1.7 单例和多例
Spring容器管理的bean在默认情况下是单例的,也即,一个bean只会创建一个对象,存在内置map中,之后无论获取多少次该bean,都返回同一个对象。
Spring默认采用单例方式,减少了对象的创建,从而减少了内存的消耗。
但是在实际开发中是存在多例的需求的,Spring也提供了选项可以将bean设置为多例模式。
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--scope属性控制当前bean的创建模式:
singleton:则当前bean处在单例模式中,默认就是此模式
prototype:则当前bean处在多例模式中-->
<bean id="person" class="com.yyy.Person" scope="prototype"></bean>
</beans>
bean在单例模式下的生命周期:
bean在单例模式下,spring容器启动时解析xml发现该bean标签后,直接创建该bean的对象存入内部map中保存,此后无论调用多少次getBean()获取该bean都是从map中获取该对象返回,一直是一个对象。此对象一直被Spring容器持有,直到容器退出时,随着容器的退出对象被销毁。
bean在多例模式下的生命周期:
bean在多例模式下,spring容器启动时解析xml发现该bean标签后,只是将该bean进行管理,并不会创建对象,此后每次使用getBean()获取该bean时,spring都会重新创建该对象返回,每次都是一个新的对象。这个对象spring容器并不会持有,什么销毁取决于使用该对象的用户自己什么时候销毁该对象。
1.8 懒加载机制
Spring默认会在容器初始化的过程中,解析xml,并将单例的bean创建并保存到map中,这样的机制在bean比较少时问题不大,但一旦bean非常多时,spring需要在启动的过程中花费大量的时间来创建bean 花费大量的空间存储bean,但这些bean可能很久都用不上,这种在启动时在时间和空间上的浪费显得非常的不值得。
所以Spring提供了懒加载机制。所谓的懒加载机制就是可以规定指定的bean不在启动时立即创建,而是在后续第一次用到时才创建,从而减轻在启动过程中对时间和内存的消耗。
懒加载机制只对单例bean有作用,对于多例bean设置懒加载没有意义。
懒加载的配置方式:
为指定bean配置懒加载
<bean id="person" class="com.yyy.Person" lazy-init="true"></bean>
为全局配置懒加载
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--scope属性控制当前bean的创建模式:
singleton:则当前bean处在单例模式中,默认就是此模式
prototype:则当前bean处在多例模式中-->
<bean id="person" class="com.yyy.Person"></bean>
</beans>
如果同时设定全局和指定bean的懒加载机制,且配置不相同,则对于该bean局部配置覆盖全局配置。
1.9 配置初始化和销毁的方法
在Spring中如果某个bean在初始化之后或销毁之前要做一些额外操作可以为该bean配置初始化和销毁的方法,在这些方法中完成要功能。
<bean id="person" class="com.yyy.Person"
init-method="init" destroy-method="destory"></bean>
Spring中关键方法的执行顺序:
在Spring创建bean对象时,先创建对象(通过无参构造或工厂),之后立即调用init方法来执行初始化操作,之后此bean就可以哪来调用其它普通方法,而在对象销毁之前,spring容器调用其destory方法来执行销毁操作。
2 Spring DI
2.1 DI-依赖注入
创建对象的过程中Spring可以依据配置对对象的属性进行设置,这个过称之为依赖注入,也即DI。
2.2 set方法注入
通常的javabean属性都会私有化,而对外暴露setXxx()getXxx()方法,此时spring可以通过这样的setXxx()方法将属性的值注入对象。
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<bean id="person" class="spring.Person" init-method="init"></bean>
<bean id="hero" class="spring.Hero">
<property name="id" value="123"></property>
<property name="name" value="拉克丝"></property>
<property name="jobs">
<list>
<value>上单</value>
<value>中单</value>
<value>辅助</value>
<value>打野</value>
<value>adc</value>
</list>
</property>
<property name="set">
<set>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
<value>aaa</value>
</set>
</property>
<property name="map">
<map>
<entry key="addr" value="王者荣耀"></entry>
<entry key="addr" value="lol"></entry>
<entry key="skill" value="风火轮"></entry>
</map>
</property>
<property name="prop">
<props>
<prop key="k1">v1</prop>
<prop key="k2">v2</prop>
<prop key="k3">v3</prop>
<prop key="k4">v4</prop>
</props>
</property>
<property name="dog" ref="dog"></property>
<property name="cat" ref="cat"></property>
</bean>
<bean id="dog" class="com.yyy.Dog"></bean>
<bean id="cat" class="com.yyy.Cat"></bean>
</beans>
2.3 自动装配
在Spring的set方式实现的注入过程中,支持自动装配机制,所谓自动装配机制,会根据要设置的javabean属性的名字或类型到spring中自动寻找对应id 或类型的进行设置,从而省去依次配置的过程,简化了配置。
package com.yyy;
public classTeacher {
privateDog dog;
privateCat cat;
public voidsetDog(Dog dog) {
this.dog = dog;
}
public voidsetCat(Cat cat) {
this.cat = cat;
}
@Override
publicString toString() {
return "Teacher [dog="+ dog + ", cat="+ cat + "]";
}
}
<!--
autowire设定自动装配:
byName:根据javabean中需要注入的属性的名字 ,在spring容器中找对应id的<bean>将该<bean>的对象复制给 当前的属性
byType:根据javabean中需要注入的属性的类型,在spring容器中找对应class类型的<bean>将该<bean>的对象复制给 当前的属性
**byType方式 根据类型进行匹配,可能匹配到多个<bean>,此时会抛出异常。而byName是通过id来寻找<bean>,id没有重复,不会有这方面的问题,所以推荐使用byName方式
-->
<bean id="teacher" class="com.yyy.Teacher" autowire="byName"></bean>
2.4 基于构造方法的注入
对象属性设置的另一种方式是在对象创建的过程中通过构造方法传入并设置对象的属性。
<bean id="student" class="com.yyy.Student">
<!--
index:为构造方法的第几个参数 进行配置
name:为构造方法的哪个名字的参数进行配置
**index 和name 可以配置任何一个或同时配置但要求一旦配置必须正确
**推荐优先使用index方式配置防止没有源码造成name无法匹配到对应参数
type:该构造方法参数的类型
value:该构造方法参数的值 ,用来指定基本值
ref:该构造方法参数的值,用来指定引用其他bean的值
-->
<constructor-arg index="0" name="id" value="999"/>
<constructor-arg index="1" type="java.lang.String" value="张无忌"/>
<constructor-arg name="dog" ref="dog"/>
</bean>
<bean id="dog" class="com.yyy.Dog"></bean>
3 Spring注解方式实现IOC 和DI
3.1 Spring注解方式实现IOC
3.1.1 开启包扫描
<context:component-scan base-package="com.yyy"></context:component-scan>
3.1.2 使用注解注册bean
在配置的包中的类上使用@Component注解,则这个类会自动被注册为bean,通常情况下使用类名首字母小写为id。
package com.yyy;
import org.springframework.stereotype.Component;
@Component
public class Person {
public void eat(){
System.out.println("吃饭");
}
public void say(){
System.out.println("说话");
}
public void init(){
System.out.println("初始化");
}
public void destory(){
System.out.println("销毁");
}
}
通常情况下注解注册bean使用类名首字母小写为bean的id,但是如果类名的第二个字母为大写则首字母保留原样。
也可以通过在@Component中配置value属性,明确的指定bean的id。
3.1.3 注解方式实现工厂注册bean
工厂中生产bean的方法要被@Bean修饰
则此方法会被SpringIOC调用,并将返回的对象注册为Spring的bean,默认自动推断id,也可以通过value属性手工指定id。
@Component
public class Pesron5Factory {
@Bean("per")
public Person getInstance(){
return new Person("xiaoming");
}
}
3.2 Spring注解方式实现DI
3.2.1 配置开启注解实现DI选项
<!--开启IOC包扫描 -->
<context:component-scan base-package="com.yyy"/>
<!--开启注解配置DI -->
<context:annotation-config></context:annotation-config>
3.2.2 注解方式注入spring内置支持的类型数据-非集合类型
spring中可以通过@Value注解来实现spring内置支持的类型的属性的注入。
package com.yyy;
importorg.springframework.beans.factory.annotation.Value;
importorg.springframework.stereotype.Component;
@Component
public class Student {
@Value("zs")
privateString name;
@Value("19")
private intage;
@Override
publicString toString() {
return "Student [name="+ name + ", age="+ age + "]";
}
}
这种方式可以实现spring内置支持类型的注入,但是这种方式将注入的值写死在了代码中,后续如果希望改变注入的值,必须来修改源代码,此时可以将这些值配置到一个properties配置文件中,再在spring中进行引入。
<context:property-placeholder location="classpath:/*.properties/>
3.2.3 注解方式注入spring内置支持的类型数据-集合类型<beans xmlns="http://www.springframework.org/schema/beans
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<context:annotation-config></context:annotation-config>
<context:component-scan base-package="spring"></context:component-scan>
<context:property-placeholder location="classpath:student-data.properties" />
<util:list id="l1">
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</util:list>
<util:set id="s1">
<value>111</value>
<value>111</value>
<value>222</value>
<value>333</value>
</util:set>
<util:map id="m1">
<entry key="k1" value="v1"></entry>
<entry key="k2" value="v3"></entry>
<entry key="k2" value="v2"></entry>
<entry key="k3" value="v2"></entry>
</util:map>
<util:properties id="p1">
<prop key="p1">v1</prop>
<prop key="p2">v2</prop>
<prop key="p3">v3</prop>
<prop key="p2">v4</prop>
</util:properties>
</beans>
再在类的属性中通过@Value注入赋值
@Component
public classStudent {
@Value("${name}")
privateString name;
@Value("${age}")
private intage;
@Value("#{@l1}")
privateList<String> list;
@Value("#{@s1}")
privateSet<String> set;
@Value("#{@m1}")
privateMap<String,String> map;
@Value("#{@p1}")
privateProperties prop;
@Override
publicString toString() {
return "Student [name="+ name + ", age="+ age + ", list="+ list
+ ", set="+ set + ", map="+ map + ", prop="+ prop + "]";
}
}
2.3.4 使用注解注入自定义bean类型数据
在bean中的属性上通过@Autowired实现自定义bean类型的属性注入
当Spring容器解析到@Component注解时,创建当前类的bean在spring容器中进行管理,在创建bean的过程中发现了@Autowired注解,会根据当前bean类型,寻找在spring中是否存在该类型的bean,找到直接注入,如果找不到还会检查是否有子孙类、实现类存在,如果存在唯一的则自动注入,如果还是没有找到或找到多个无法注入,则还会按照属性名对应id去查找对应的bean,如果存在则注入,如果还是没有找到则抛出异常。
也可以额外配置@Qualifier(value=“dog1”)注解强制要求按照id寻找bean,则此时会直接使用给定的id寻找bean,而不会进行基于类型的匹配。
2.4 其他注解
@Scope(value=“prototype”) 多例
@Lazy 配置修饰的类的bean采用懒加载机制
@PostConstruct 初始化
@PreDestroy 销毁
@Controller 控制层
@Service 业务层
@Repository 持久层
@Component 组件