Spring IOC容器、Bean对象(基于XML和注解的配置)、依赖注入DI

工厂模式解耦

提前将程序所需要的对象信息配置在配置文件中,程序最初运行的时候,利用一个类(工厂类)读取配置文件,把这些对象创建出来并保存(保存在容器中)。程序的其他地方需要用某个对象的时候,这容器中取这个对象即可。工厂类负责读取配置文件、创建并保存对象、提供获取对象的方法Object getBean(id)

控制反转IOC(Inversion Of Control)

一般,我们会通过new Person()的方式来获取一个Person类的实例,但是利用上面提到的工厂模式,通过调用getBean方法就能获取到某个类的实例,这种实例化一个对象的方式不再是我们主动的去new一个,而是new对象的权利交给了工厂类,这就是控制反转。

Spring的IOC容器

将IOC容器的创建工作交给Spring,可以通过ApplicationContext或者BeanFactory接口的实现类去创建Spring的IOC容器,然后调用getBean方法获取实例对象,如下面这样:

//获取核心容器对象
ApplicationContext ac = new FileSystemXmlApplicationContext("bean.xml在磁盘上的绝对路径");
//根据id获取Bean对象(IAccountService是业务层接口,IAccountDao是dao层接口)
IAccountService as = (IAccountService)ac.getBean("accountService");
//也可以加上第二个参数指定类类型,就不用使用强制类型转换
IAccountDao adao = ac.getBean("accountDao", IAccountDao.class);

//或者
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("bean.xml"));
IAccountService as = (IAccountService)ac.getBean("accountService");

bean.xml:

<?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">

    <!--把对象的创建交给spring管理-->
    <bean id="accountService" class="com.exapmle.spring.service.impl.AccountServiceImpl"></bean>

    <bean id="accountDao" class="com.exapmle.spring.dao.impl.AccountDaoImpl"></bean>

</beans>

ApplicationContext的三个常用实现类

  • ClassPathXmlApplicationContext:可以加载类路径下的配置文件,要求配置文件必须在类路径下
  • FileSystemXmlApplicationContext:可以加载磁盘任意路径下的配置文件(必须有访问权限)
  • AnnotationConfigApplicationContext:用于读取注解创建的容器

【思考题】实例化对象是读取完配置文件之后就进行了还是第一次使用这个对象的时候才创建的?
其实这两种方式都可以,具体时期如下

实例化对象的两个时期

ApplicationContext

  • 构建核心容器时,创建对象采取的策略是立即加载的方式,也就是说,只要读取完配置文件马上就通过反射实例化对象
  • 适用场景:
    像dao、service这层的代码,不需要类变量,使用单例模式即可,因此在程序最开始的时候创建最佳
  • 实际开发中该方式最多

BeanFactory

  • 构建核心容器时,创建对象采取的策略是延迟加载的方式,也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象
  • 适用场景:多例模式

Spring中的Bean对象

IAccountService:

public interface IAccountService {
    /**
     * 模拟保存账户
     */
    void saveAccount();
}

AccountServiceImpl:

public class AccountServiceImpl implements IAccountService {
    public AccountServiceImpl() {
        System.out.println("创建对象AccountServiceImpl");
    }
    public void  saveAccount(){
        System.out.println("service.AccountServiceImpl.saveAccount执行了");
    }

    public void  init(){
        System.out.println("service.AccountServiceImpl.saveAccount初始化了");
    }

    public void  destory(){
        System.out.println("service.AccountServiceImpl.saveAccount销毁了");
    }
}

1.创建Bean对象的三种方式

默认构造函数创建

在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。

<bean id="accountService" class="com.exapmle.spring.service.impl.AccountServiceImpl"></bean>
使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)

InstanceFactory:

public class InstanceFactory {
    public IAccountService getAccountService(){
        return new AccountServiceImpl();
    }
}
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)

StaticFactory:

public class StaticFactory {
    public static IAccountService getAccountService(){
        return new AccountServiceImpl();
    }
}
<bean id="accountService" class="com.exapmle.spring.factory.StaticFactory" factory-method="getAccountService"></bean>

2.Bean的作用范围调整

利用bean标签的scope属性:

  • singleton:单例的(默认值)
  • prototype:多例的
  • request:作用于web应用的请求范围,同一次请求创建一个实例
  • session:作用于web应用的会话范围,同一个session创建一个实例
  • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境是,退化为session
<bean id="accountService" class="com.exapmle.spring.service.impl.AccountServiceImpl" scope="prototype"></bean>
或者下面这种
<bean id="accountService" class="com.exapmle.spring.service.impl.AccountServiceImpl"></bean>
//获取核心容器对象
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取Bean对象
IAccountService as1 = (IAccountService)ac.getBean("accountService");
IAccountService as2 = (IAccountService)ac.getBean("accountService");
System.out.println(as1==as2);

当scope="prototype"时输出结果:

创建对象AccountServiceImpl
创建对象AccountServiceImpl
false

当scope="singleton"时输出结果:

创建对象AccountServiceImpl
true

3.Bean的生命周期

单例对象

  • 创建:当容器创建时
  • 存在:容器存在时
  • 销毁:容器销毁时
  • 总结:单例对象的生命周期和容器相同

多例对象

  • 创建:当使用对象时Spring创建该对象
  • 存在:对象使用过程中一直活着
  • 销毁:Spring并不知道对象的方法在那些地方、什么时候被使用,因此只能靠Java的垃圾回收机制(当对象长时间未使用,且没有别的对象引用的时候)

在Bean的声明里设置init-method和destroy-method属性为bean指定初始化和销毁方法,也可以是用@Bean注解的initMethod方法和destroyMethod方法:

<bean id="accountService" 
class="com.exapmle.spring.service.impl.AccountServiceImpl" 
init-method="init" destroy-method="destory"></bean>

或者:

@Configuration
public class MainConfig{
	@Bean(initMethod="init",destroyMethod="detory")
	public AccountServiceImpl accountService() {
		return new AccountServiceImpl();
	}
}

依赖注入DI(Dependency Injection)

【参考】https://www.jianshu.com/p/4cd44bd7be1b

“依赖注入”是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中

  • 谁依赖于谁:应用程序依赖于IoC容器;
  • 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源
  • 谁注入谁:IoC容器注入应用程序某个对象,应用程序依赖的对象
  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)

控制反转是目的,依赖注入是方式

完成注入的方式有三种:

  • 使用构造函数
  • 使用set方法
  • 使用注解方式

能注入的数据有三类:

  • 基本类型和String
  • 其他bean类型(在配置文件中或注解中配置的bean)
  • 复杂类型/集合类型

构造函数注入(使用类的默认构造方法,或者自己实现了有参的构造方法)

public class AccountServiceImpl implements IAccountService {
    private String name; //String类型
    private Integer age; //基础类型的包装类
    private Date birthday; //其他bean类型

    public AccountServiceImpl(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public void  saveAccount(){
        System.out.println("service的saveAccount执行了"+name+age+birthday);
    }
}
<bean id="accountService" class="com.exapmle.spring.service.impl.AccountServiceImpl">
    <constructor-arg name="name" value="test"></constructor-arg>
    <constructor-arg name="age" value="18"></constructor-arg>
    <constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>

<!--配置一个日期对象,读取权限定类名,反射创建一个对象,并且存入spring的核心容器中-->
<bean id="now" class="java.util.Date"></bean>

set方法注入(使用类的set方法)

public class AccountServiceImpl2 implements IAccountService {
    private String name; //String类型
    private Integer age; //基础类型的包装类
    private Date birthday; //其他bean类型

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public void  saveAccount(){
        System.out.println("service的saveAccount执行了"+name+age+birthday);
    }
}
<bean id="accountService2" class="com.exapmle.spring.service.impl.AccountServiceImpl2">
    <property name="name" value="test"></property>
    <property name="age" value="21"></property>
    <property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>

复杂类型的注入:

public class AccountServiceImpl3 implements IAccountService {
    private String[] myStrs;

    private List<String> myList;

    private Set<String> mySet;

    private Map<String, String> myMap;

    private Properties myProps;

    public void setMyStrs(String[] myStrs) {
        this.myStrs = myStrs;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void setMyProps(Properties myProps) {
        this.myProps = myProps;
    }

    public void  saveAccount(){
        System.out.println(Arrays.toString(myStrs));
        System.out.println(myList);
        System.out.println(mySet);
        System.out.println(myMap);
        System.out.println(myProps);
    }
}
    <bean id="accountService3" class="com.exapmle.spring.service.impl.AccountServiceImpl3">
        <property name="myStrs">
            <array>
                <value>A</value>
                <value>B</value>
                <value>C</value>
            </array>
        </property>
        <property name="myList">
            <list>
                <value>A</value>
                <value>B</value>
                <value>C</value>
            </list>
        </property>
        <property name="mySet">
            <set>
                <value>A</value>
                <value>B</value>
                <value>C</value>
            </set>
        </property>
        <property name="myMap">
            <map>
                <entry key="testA" value="A"></entry>
                <entry key="testB">
                    <value>B</value>
                </entry>
            </map>
        </property>
        <property name="myProps">
            <props>
                <prop key="testa">a</prop>
                <prop key="testb">b</prop>
            </props>
        </property>
    </bean>

注解的方式

@Autowired

基于注解的IOC配置

上面讲解的都是以xml的方式配置IOC容器,也可以基于注解来配置IOC容器,实现的功能都一样,都是要降低程序之间的耦合,只是配置的形式不一样。

注解按照作用分类

用于创建对象的:和xml中的<bean>标签功能一样

  • @Component:用于把当前类对象存入spring容器中
    属性:
    value:指定bean对象的id,默认是当前类名(首字母小写)
  • @Controller:一般用在表现层
  • @Service:一般用于业务层
  • @Repository:一般用于持久层

@Controller、@Service、@Repository注解的作用和属性与@Component注解是一样的,只是spring框架为了提供明确的三层使用的注解,因此再衍生出了这三个注解

用于注入数据的:和xml中的<property>标签功能一样

  • @Autowired:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。该注解可以用在成员变量和成员方法上
  • @Qualifier:按照类型注入的基础上再按照名称注入,不能单独用在类的属性上(要和@Autowired注解一起使用),但是可以单独用于方法参数注入
    属性:value用于指定注入bean的id
  • @Resource:直接按照指定的bean的id注入,可独立使用
    属性:name用于指定注入bean的id

以上按个注解只能注入其他Bean类型的数据,不能注入基本类型和String类型,此外,集合类型的注入只能通过xml的方式实现

  • @Value:用于注入基本类型和String类型
    属性:value用于指定数据的值,可以使用spring中的SpEL(spring的el表达式,写法:${表达式})

用于改变作用范围的:和<property>标签中的scope属性功能一样

  • @Scope:用于指定bean的作用范围
    属性:value用于指定范围取值,常用取值:singleton(默认值)、prototype

和声明周期相关的:<property>标签中的init-methoddestroy-method属性功能一样

  • @PreDestroy:用于指定销毁方法
  • @PostConstruct:用于指定初始化方法

其他注解

  • @Configuration:表明当前类是一个配置类,相当于xml的<beans>.标签
  • @Bean:作用在方法上,将该方法返回的实例对象注入到IOC容器中,bean的id默认为方法名,使用value属性可指定bean的id
    @Configuration和@Bean配合使用,配置类加方法的方式实现IOC的注入:
@Configuration
public class MainConfig {
    //类型是返回值类型,bean的id为方法名
    @Bean
    public Person person() {
    	//Person类的实现省略
        return new Person("ls", 22);
    }
}

使用如下:

public class MainTest {
    public static void main(String[] args) {
    	ConContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = (Person)applicationContext.getBean("person");
        System.out.println(person);
    }
}
  • @Primary:打上该注解的bean方法,会被首选为注入的bean。也就是说当IOC中有多个类型相同的类对象时,在用@Autowired注解注入属性的时候,会将打上了@Primary的bean注入给该变量
  • @ComponentScan:指定要扫描的包
    属性value指定要扫描的包名,只要指定包下的类包含相关注解(如@Controller、@Service)就会被扫描到,并注入IOC容器中。作用同xml中的<context:component-scan base-package="要扫描的包名"></context:component-scan >

示例

下面演示将xml的方式转成以注解的方式配置IOC,以下几个代码片段分别是bean.xml、dao层的接口、dao层接口的实现、service层的接口、service层接口的实现

【xml的方式】
bean.xml:

<?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">

    <!--把对象的创建交给spring管理-->
    <bean id="accountService" class="com.exapmle.spring.service.impl.AccountServiceImpl">
		<property name="accountDao" ref="accountDao"></property >
    </bean>

    <bean id="accountDao" class="com.exapmle.spring.dao.impl.AccountDaoImpl"></bean>
</beans>

IAccountDao:

public interface IAccountDao {
	void saveAccount();
}

AccountDaoImpl:

public class AccountDaoImpl implements IAccountDao {
	public void saveAccount(){
		System.out.println("保存账户的dao方法");
	}
}

IAccountService:

public interface IAccountService {
    void saveAccount();
}

AccountServiceImpl:

public class AccountServiceImpl implements IAccountService {
	private IAccountDao accountDao;

	//提供accountDao的get和set方法,此处省略
	
    public AccountServiceImpl() {
        System.out.println("创建对象AccountServiceImpl");
    }
    public void  saveAccount(){
        accountDao.saveAccount();
    }
}

【注解的方式】
1 引入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
</dependency>

2 在bean.xml中配置扫描路径,这里的xml命名空间和上面用到的有所不同,因为配置spring在创建容器时要扫描的包,这个配置所需要的标签不是在beans的约束中,而是在一个名为context名称空间和约束中

<?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">

    <!--告知spring在创建容器时要扫描的包-->
    <context:component-scan base-package="要扫描的包名"></context:component-scan >
</beans>

2 添加注解
AccountServiceImpl类上添加@Service(value="accountService")注解,value可省略
AccountDaoImpl类上添加@Repository(value="accountDao")注解,value可省略

3 测试

public static void main(String[] args) { 
	//获取核心容器对象
	ApplicationContext ac = new AnnotationConfigApplicationContext("bean.xml");
	//根据id获取Bean对象
	IAccountService as = (IAccountService)ac.getBean("accountService");
	//也可以加上第二个参数指定类类型,就不用使用强制类型转换
	IAccountDao adao = ac.getBean("accountDao", IAccountDao.class);
	
	//下面注释的这行代码是否能执行成功呢
	//as.saveAccount();
}

4 注入数据
AccountServiceImpl类的accountDao属性上添加@Autowired注解,删除AccountServiceImpl类中的get和set方法,这时候as.saveAccount();方法可以执行成功。

前面说到@Autowired注解用在属性上,是按照属性的类型自动注入的,这里简述下原理:

前面已经将AccountServiceImpl类和AccountDaoImpl类注入到了IOC容器中,此时容器中存在键值对(Map结构)<“accountService”,AccountServiceImpl的实例对象>和<“accountDao”,AccountDaoImpl的实例对象>,当要给accountDao属性注入数据时,到IOC容器中找键值对中的值,看哪个值的类型和accountDao属性的类型相同(这里把接口类型和实现类类型看做相同),找到唯一一个相同的则注入数据成功。

如果系统中还有一个类AccountDaoImpl2:

@Repository(value="accountDao2")
public class AccountDaoImpl2{
	public void saveAccount(){
		System.out.println("保存账户的dao方法");
	}
}

此时,IOC容器中还有一个键值对<“accountDao2”,AccountDaoImpl2的实例对象>,当给accountDao属性注入数据时,在IOC容器中找到的就有两个匹配的类型,那么,程序会继续按照accountDao属性名再这两个匹配的键值对中找,那个键值对的键和该属性名相同,就匹配成功,如果没有找到,则报错。

要解决这种类型匹配到多个的情况,第一种解决办法是添加@Service注解的时候指定bean的id,再将属性名改为和要注入的bean的id相同(不推荐这种做法);第二种解决办法就是使用@Autowired注解和@Qualifier(value=“指定要注入的bean的id”)注解结合;第三中解决办法就是使用@Resource(name=“指定要注入的bean的id”)

发布了243 篇原创文章 · 获赞 87 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/IT_10/article/details/103881107