Spring IOC 笔记

spring原理学习笔记-1 8/27 2020

IOC:Inverse Of Control

控制反转

读作 “反转控制”,更好理解,不是什么技术,而是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。
正控:若要使用某个对象,需要自己去负责对象的创建
反控:若要使用某个对象,只需要从 Spring 容器中获取需要使用的对象,不关心对象的创建过程,也就是把创建对象的控制权反转给了Spring框架

好莱坞法则: Don’t call me ,I’ll call you

简而言之:
spring 的IOC就是它负责了对象的创建的时机,你只需要在配置或者注解中将创建对象时的内容,其创建和调用都由spring 托管。
spring内由IOCcontainer容器实现。

ApplicationContext 是 BeanFactory 的子接口之一,换句话说:BeanFactory 是 Spring IoC 容器所定义的最底层接口,而 ApplicationContext 是其最高级接口之一,并对 BeanFactory 功能做了许多的扩展,所以在绝大部分的工作场景下,都会使用 ApplicationContext 作为 Spring IoC 容器。

【getBean】 对应了多个方法来获取配置给 Spring IoC 容器的 Bean。
① 按照类型拿 bean:
bean = (Bean) factory.getBean(Bean.class);
注意: 要求在 Spring 中只配置了一个这种类型的实例,否则报错。(如果有多个那 Spring 就懵了,不知道该获取哪一个)
② 按照 bean 的名字拿 bean:
bean = (Bean) factory.getBean(“beanName”);
注意: 这种方法不太安全,IDE 不会检查其安全性(关联性)
③ 按照名字和类型拿 bean:(推荐)
bean = (Bean) factory.getBean(“beanName”, Bean.class);
【isSingleton】 用于判断是否单例,如果判断为真,其意思是该 Bean 在容器中是作为一个唯一单例存在的。而【isPrototype】则相反,如果判断为真,意思是当你从容器中获取 Bean,容器就为你生成一个新的实例。
注意: 在默认情况下,【isSingleton】为 ture,而【isPrototype】为 false
关于 type 的匹配,这是一个按 Java 类型匹配的方式
【getAliases】方法是获取别名的方法
这就是 Spring IoC 最底层的设计,所有关于 Spring IoC 的容器将会遵守它所定义的方法。

ApplicationContext

根据 ApplicationContext 的类继承关系图,可以看到 ApplicationContext 接口扩展了许许多多的接口,因此它的功能十分强大,所以在实际应用中常常会使用到的是 ApplicationContext 接口,因为 BeanFactory 的方法和功能较少,而 ApplicationContext 的方法和功能较多。

通过上一篇 IoC 的例子,我们来认识一个 ApplicationContext 的子类——ClassPathXmlApplicationContext。

先在这里提一下三种注入方式:
1.constructor
2.Set
3.API
先在【src】目录下创建一个 【bean.xml】 文件:

1
2
3
4
5
6
7
8
9
10
11
<?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">
<!-- 通过 xml 方式装配 bean -->
<bean name="source" class="pojo.Source">
<property name="fruit" value="橙子"/>
<property name="sugar" value="多糖"/>
<property name="size" value="超大杯"/>
</bean>
</beans>

这里定义了一个 bean ,property会用id对应字段set方法注入。value 和 ref的区别在于:property的value直接注入写下的值。ref寻找spring容器中生成的对应对象来注入, 这样 Spring IoC 容器在初始化的时候就能找到它们,然后使用 ClassPathXmlApplicationContext 容器就可以将其初始化:

1
2
3
4
5
6
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Source source = (Source) context.getBean("source", Source.class);

System.out.println(source.getFruit());
System.out.println(source.getSugar());
System.out.println(source.getSize());

这样就会使用 Application 的实现类 ClassPathXmlApplicationContext 去初始化 Spring IoC 容器,然后开发者就可以通过 IoC 容器来获取资源了啦!

关于 Spring Bean 的装配以及一些细节,会在下一篇文章中讲到
ApplicationContext 常见实现类:

1.ClassPathXmlApplicationContext:
读取classpath中的资源

1
2
3
4
5
6
7
8
9
10
11
12
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
2:FileSystemXmlApplicationContext:-
//读取指定路径的资源

ApplicationContext ac = new FileSystemXmlApplicationContext("c:/applicationContext.xml");
//3.XmlWebApplicationContext:
//需要在Web的环境下才可以运行

XmlWebApplicationContext ac = new XmlWebApplicationContext(); // 这时并没有初始化容器
ac.setServletContext(servletContext); // 需要指定ServletContext对象
ac.setConfigLocation("/WEB-INF/applicationContext.xml"); // 指定配置文件路径,开头的斜线表示Web应用的根目录
ac.refresh(); // 初始化容器

BeanFactory 和 ApplicationContext 的区别:

BeanFactory: 是Spring中最底层的接口,只提供了最简单的IoC功能,负责配置,创建和管理bean。
在应用中,一般不使用 BeanFactory,而推荐使用ApplicationContext(应用上下文),原因如下。
ApplicationContext:
1.继承了 BeanFactory,拥有了基本的 IoC 功能;
2.除此之外,ApplicationContext 还提供了以下功能:
① 支持国际化;
② 支持消息机制;
③ 支持统一的资源加载;
④ 支持AOP功能;
Spring IoC 的容器的初始化和依赖注入

虽然 Spring IoC 容器的生成十分的复杂,但是大体了解一下 Spring IoC 初始化的过程还是必要的。这对于理解 Spring 的一系列行为是很有帮助的。

注意: Bean 的定义和初始化在 Spring IoC 容器是两大步骤,它是先定义,然后初始化和依赖注入的。

Bean 的定义分为 3 步:
1.Resource 定位
Spring IoC 容器先根据开发者的配置,进行资源的定位,在 Spring 的开发中,通过 XML 或者注解都是十分常见的方式,定位的内容是由开发者提供的。
2.BeanDefinition 的载入
这个时候只是将 Resource 定位到的信息,保存到 Bean 定义(BeanDefinition)中,此时并不会创建 Bean 的实例
3.BeanDefinition 的注册
这个过程就是将 BeanDefinition 的信息发布到 Spring IoC 容器中
注意:此时仍然没有对应的 Bean 的实例。
做完了以上 3 步,Bean 就在 Spring IoC 容器中被定义了,而没有被初始化,更没有完成依赖注入,也就是没有注入其配置的资源给 Bean,那么它还不能完全使用。

对于初始化和依赖注入,Spring Bean 还有一个配置选项——【lazy-init】,其含义就是是否初始化 Spring Bean。在没有任何配置的情况下,它的默认值为 default,实际值为 false,也就是 Spring IoC 默认会自动初始化 Bean。如果将其设置为 true,那么只有当我们使用 Spring IoC 容器的 getBean 方法获取它时,它才会进行 Bean 的初始化,完成依赖注入。

IoC 是如何实现的

最后我们说说IoC是如何实现的。想象一下如果我们自己来实现这个依赖注入的功能,我们怎么来做? :

读取标注或者配置文件,看看JuiceMaker依赖的是哪个Source,拿到类名
使用反射的API,基于类名实例化对应的对象实例
将对象实例,通过构造函数或者 setter,传递给 JuiceMaker
我们发现其实自己来实现也不是很难,Spring实际也就是这么做的。这么看的话其实IoC就是一个工厂模式的升级版!当然要做一个成熟的IoC框架,还是非常多细致的工作要做,Spring不仅提供了一个已经成为业界标准的Java IoC框架,还提供了更多强大的功能,所以大家就别去造轮子啦!
首先:第一个问题,参与者都有谁? 1)对象
2)IOC/DI容器
3)某个对象的外部资源
第二问题:依赖,谁依赖谁?为什么需要依赖? 依赖嘛,很好理解的,对象依赖于IOC/DI容器,至于为什么要依赖呢?对象需要IOC/DI容器来提供对象需要的外部资源。
第三个问题:注入,谁注入谁?又注入了什么呢? 显而易见是IOC/DI容器注入对象,注入了what呢?肯定注入的是某个需要的东西那就是注入对象所需要的资源,肯定不会注入无关紧要的内容,你说呢?
第四个问题:控制反转,谁控制谁?控制什么?为什么叫反转呢?存在正转吗? 控制反转,控制什么?肯定是IOC/DI容器控制对象,主要是控制对象实例的创建,反转是相对于正向而言的,那么什么算是正向的呢?考虑一下常规情况下的应用程序,如果要在A里面使用C,你会怎么做呢?当然是直接去创建C的对象,也就是说,是在A类中主动去获取所需要的外部资源C,这种情况被称为正向的。那么什么是反向呢?就是A类不再主动去获取C,而是被动等待,等待IoC/DI的容器获取一个C的实例,然后反向的注入到A类中。
第五个问题:控制反转和依赖注入式同一个概念吗? 依赖注入和控制反转是对同一件事情的不同描述,从某个方面讲,就是它们描述的角度不同。依赖注入是从应用程序的角度在描述,可以把依赖注入描述完整点:应用程序依赖容器创建并注入它所需要的外部资源;而控制反转是从容器的角度在描述,描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。

4种注入方式
了解了这些基本的概念,弄明白她们之间的联系和区别,能够帮助我们更好的理解,接着小编来重点介绍一下依赖注入,在spring ioc中有四种依赖注入,分别是:

a.接口注入

b.setter方法注入

c.构造方法注入

d.注解方式注入
接着小编对这三种注入方式一一进行讲解,通过demo的讲解,希望能够帮助小伙伴们更好的理解,不足之处还请多多指教。

接口注入

public class ClassA {
private InterfaceB clzB;
public void doSomething() {
Ojbect obj = Class.forName(Config.BImplementation).newInstance();
clzB = (InterfaceB)obj;
clzB.doIt();
}
……
解释一下上述的代码部分,ClassA依赖于InterfaceB的实现,我们如何获得InterfaceB的实现实例呢?传统的方法是在代码中创建 InterfaceB实现类的实例,并将赋予clzB.这样一来,ClassA在编译期即依赖于InterfaceB的实现。为了将调用者与实现者在编译期分离,于是有了上面的代码。我们根据预先在配置文件中设定的实现类的类名(Config.BImplementation),动态加载实现类,并通过InterfaceB强制转型后为ClassA所用,这就是接口注入的一个最原始的雏形。

setter方法注入

setter注入模式在实际开发中有非常广泛的应用,setter方法更加直观,我们来看一下spring的配置文件:


<!-- 使用spring管理对象的创建,还有对象的依赖关系 -->    
<bean id="userDao4Mysql" class="com.tgb.spring.dao.UserDao4MysqlImpl"/>    

<bean id="userDao4Oracle" class="com.tgb.spring.dao.UserDao4OracleImpl"/>    

<bean id="userManager" class="com.tgb.spring.manager.UserManagerImpl">    
      <!-- (1)userManager使用了userDao,Ioc是自动创建相应的UserDao实现,都是由容器管理-->    
      <!-- (2)在UserManager中提供构造函数,让spring将UserDao实现注入(DI)过来 -->    
      <!-- (3)让spring管理我们对象的创建和依赖关系,必须将依赖关系配置到spring的核心配置文件中 -->    

    <property name="userDao" ref="userDao4Oracle"></property>    
</bean>    

接着我们来看一下,setter表示依赖关系的写法

import com.tgb.spring.dao.UserDao;

public class UserManagerImpl implements UserManager{

private UserDao userDao;    

//使用设值方式赋值    
public void setUserDao(UserDao userDao) {    
    this.userDao = userDao;    
}    

@Override    
public void addUser(String userName, String password) {    

    userDao.addUser(userName, password);    
}    

}
构造器注入

构造器注入,即通过构造函数完成依赖关系的设定。我们看一下spring的配置文件:


    <!-- 使用spring管理对象的创建,还有对象的依赖关系 -->    
    <bean id="userDao4Mysql" class="com.tgb.spring.dao.UserDao4MysqlImpl"/>    

    <bean id="userDao4Oracle" class="com.tgb.spring.dao.UserDao4OracleImpl"/>    

    <bean id="userManager" class="com.tgb.spring.manager.UserManagerImpl">    
        <!-- (1)userManager使用了userDao,Ioc是自动创建相应的UserDao实现,都是由容器管理-->    
        <!-- (2)在UserManager中提供构造函数,让spring将UserDao实现注入(DI)过来 -->    
        <!-- (3)让spring管理我们对象的创建和依赖关系,必须将依赖关系配置到spring的核心配置文件中 -->    

        <constructor-arg ref="userDao4Oracle"/>    
    </bean>    

</beans>    

我们再来看一下,构造器表示依赖关系的写法,代码如下所示:

import com.tgb.spring.dao.UserDao;

public class UserManagerImpl implements UserManager{    

    private UserDao userDao;    

    //使用构造方式赋值    
    public UserManagerImpl(UserDao userDao) {    
        this.userDao = userDao;    
    }    

    @Override    
    public void addUser(String userName, String password) {    

        userDao.addUser(userName, password);    
    }    
}    

使用字段(Filed)注入(用注解方式)

在Spring中,注入依赖对象可以采用手工装配或自动装配,在实际应用开发中建议使用手工装配,因为自动装配会产生许多未知情况,开发人员无法预见最终的装配结果。

手工装配依赖对象又分为两种方式:

一种是在XML文件中,通过在bean节点下配置;如上面讲到的使用属性的setter方法注入依赖对象和使用构造器方法注入依赖对象都是这种方式。

另一种就是在java代码中使用注解的方式进行装配,在代码中加入@Resource或者@Autowired、

Autowired是自动注入,自动从spring的上下文找到合适的bean来注入
Resource用来指定名称注入
Qualifier和Autowired配合使用,指定bean的名称,如
@Autowired
@Qualifier(“userDAO”)
private UserDAO userDAO;
怎样使用注解的方式来为某个bena注入依赖对象呢?

首先,我们需要在Spring容器的配置文件applicationContext.Xml文件中配置以下信息,该信心是一个Spring配置文件的模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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"

xmlns:p="http://www.springframework.org/schema/p"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-2.5.xsd

">

</beans>

注意:只有配置了红色部分的代码才可以引入注解的命名空间,否则报错。

以上的配置隐式的注册了多个对注释进行解析的处理器:AutowiredAnnotationBeanPostProcessor、

CommonAnnotationBeanPostProcessor、

PersistenceAnnotationBeanPostProcessor等。

其次,在配置文件中打开context:annotation-config节点,告诉Spring容器可以用注解的方式注入依赖对象;其在配置文件中的代码如下:

1
2
3
4
5
6
7
8
9
<beans>  

……

<context:annotation-config></context:annotation-config>

……

</beans>

第三,在配置文件中配置bean对象,如下:

1
2
3
<bean id="userDao" class="com.springtest.dao.impl.UserDAOImpl"></bean>  

<bean id="userBiz" class="com.springtest.biz.impl.UserBizImpl"></bean>

第四,在需要依赖注入的BIZ类中,声明一个依赖对象,不用生成该依赖对象的setter方法,并且为该对象添加注解:

1
2
3
4
5
6
7
public class UserBizImpl implements UserBiz {  
@Resource(name="userDao")
private UserDAO userDao = null;
public void addUser() {
this.userDao.addUser();
}
}

其中,在Java代码中可以使用@Autowired或@Resource注解方式进行Spring的依赖注入。两者的区别是:@Autowired默认按类型装配,@Resource默认按名称装配,当找不到与名称匹配的bean时,才会按类型装配。

比如:我们用@Autowired为上面的代码UserDAO接口的实例对象进行注解,它会到Spring容器中去寻找与UserDAO对象相匹配的类型,如果找到该类型则将该类型注入到userdao字段中;

如果用@Resource进行依赖注入,它先会根据指定的name属性去Spring容器中寻找与该名称匹配的类型,例如:@Resource(name=”userDao”),如果没有找到该名称,则会按照类型去寻找,找到之后,会对字段userDao进行注入。

通常我们使用@Resource。

使用注解注入依赖对象不用再在代码中写依赖对象的setter方法或者该类的构造方法,并且不用再配置文件中配置大量的依赖对象,使代码更加简洁,清晰,易于维护。

在Spring IOC编程的实际开发中推荐使用注解的方式进行依赖注入。

依赖注入—自动装配:略(不推荐)

总结:

接口注入:

接口注入模式因为具备侵入性,它要求组件必须与特定的接口相关联,因此并不被看好,实际使用有限。

Setter 注入:

对于习惯了传统 javabean 开发的程序员,通过 setter 方法设定依赖关系更加直观。如果依赖关系较为复杂,那么构造子注入模式的构造函数也会相当庞大,而此时设值注入模式则更为简洁。如果用到了第三方类库,可能要求我们的组件提供一个默认的构造函数,此时构造子注入模式也不适用。

构造器注入:

在构造期间完成一个完整的、合法的对象。所有依赖关系在构造函数中集中呈现。依赖关系在构造时由容器一次性设定,组件被创建之后一直处于相对“不变”的稳定状态。只有组件的创建者关心其内部依赖关系,对调用者而言,该依赖关系处于“黑盒”之中。

注解注入:

使用注解注入依赖对象不用再在代码中写依赖对象的setter方法或者该类的构造方法,并且不用再配置文件中配置大量的依赖对象,使代码更加简洁,清晰,易于维护。

在Spring IOC编程的实际开发中推荐使用注解的方式进行依赖注入。
使用纯注解方式配置Spring容器并获取bean的过程

  纯注解方法的话,那就舍弃xml配置文件。  

创建配置类

  如果我们使用纯注解方式实现IoC,那么需要创建一个配置类来代替上面这个xml配置文件,然后就可以不用创建这个xml配置文件了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.woshilan.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import cn.woshilan.pojo.Person;

@Configuration
public class MyConfiguration {

@Bean
public Person person() {
return new Person();
}

}

  解释一下上面这个MyConfiguration类:

  1、创建的配置类,类名随意,但是需要使用@Configuration注解,标识其为配置类,这个类的功能相当于之前的xml配置文件。

  2、public Person person() 这个方法,使用了@Bean注解,这个方法的功能等同于xml中的标签,方法名对应标签中的id,方法返回值类型对应中的class。在方法体中完成对象的实例化即可。

  如果设置bean的id,即不使用方法名作为bean的id,可以在@Bean中直接设置:@Bean(“newId”)

测试代码

  注意,在测试的时候,因为没有使用xml配置文件的方式,所以不是使用ClassPathXmlApplicationContext,而是使用AnnotationConfigApplicationContext类,需要接受一个参数,参数就是我们的配置类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cn.woshilan.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import cn.woshilan.config.MyConfiguration;
import cn.woshilan.pojo.Person;

public class TestIOC {
public static void main(String[] args) {
// 这里使用的是AnnotationConfigApplicationContext
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);

// 此时的getBean(),第一个参数是MyConfiguration中的某个方法名,因为配置类中的方法名对应<bean>中的id
Person bean = context.getBean("person", Person.class);

System.out.println(bean);
}
}

  上面就实现了使用注解方式来完成让Spring管理bean。

配置注解扫描package
  前面我们创建了MyConiguration配置类来代替xml配置文件,然后在类中可以定义很多的function来创建对象,每一个function其实是对应一个xml中的一个标签的。

  这就意味着,创建每一个bean都需要去Myconfiguration配置类中创建一个function,这样其实并不方便,所以可以使用包扫描的方式,包扫描的方式就是在MyConfiguration类上使用@ComponentScan注解指定要扫描哪些package,如果那些package中的class上面有@Component、@Service、@Repository、@Controller这几个注解中的一个,那么spring就会为其创建bean,bean的id默认是类名首字母小写,但也支持自定义。

  注意,上面列举了4个注解,@Component、@Service、@Repository、@Controller的功能一样,都是标识让Spring管理这些类创建的bean,但是他们的语义是有区别的,所以用法也不相同:

  @Component注解,用来普通的javabean上;最纯粹的

  @Service注解,用在Service层,一般是service.impl包下的类上面;

  @Repository注解,用在数据访问层,也就是DAO层上

  @Controller注解是用在spring mvc中的控制器上面。

创建包含注解的Person类

  创建Person类,包名为cn.woshilan.pojo。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.woshilan.pojo;

import org.springframework.stereotype.Component;

@Component // 使用这个注解,bean的id默认是类名首字母小写
// @Component("person111") 可以手动设置bean的id
public class Person {

private int id;
private String name;
private String addr;

// 隐藏了有参和无参构造方法、setter、getter、toString
}

使用注解配置扫描package

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package cn.woshilan.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({"cn.woshilan.pojo", "cn.woshilan.demo"}) // 指定扫描哪些package
public class MyConfiguration {

}
```  
  

进行测试
```java
package cn.woshilan.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import cn.woshilan.config.MyConfiguration;
import cn.woshilan.pojo.Person;

public class TestIOC {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);

Person bean = context.getBean("person", Person.class);

System.out.println(bean);
}
}

使用xml配置注解扫描package

  如果要使用配置文件来指定扫描注解的package,需要使用context这个xmlns,下面这个xml配置文件实现了上面MyConfiguration的功能:

1
2
3
4
5
6
7
8
9
10
11
12
<?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这个xmlns,然后使用<context:component-scan>指定需要扫描哪些package -->
<context:component-scan base-package="cn.woshilan.pojo, cn.woshilan.demo"></context:component-scan>
</beans>

使用注解实现注入
  前面介绍了使用注解来让Spring容器来管理bean对象,创建bean的时候,默认都是调用无参构造方法。如果我们为对象的属性赋值,那么就需要进行注入了。

对普通数据类型的属性注入

  这里的普通数据类型是指几种基本数据类型、以及String,对他们赋值,可以直接使用@Value注解实现。比如下面对Person类的各个属性进行赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cn.woshilan.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Person {

@Value("999")
private int id;

@Value("Jenny")
private String name;

@Value("America")
private String addr;

// 隐藏了有参和无参构造方法、setter、getter、toString
}

  上面的@Value注解接收一个字符串类型的值,Spring会根据属性的类型,自动将字符串类型的值转换为属性类型的数据。

  另外,@Value不仅支持直接指定属性值,使用@Value(“${key}”)可以读取外部properties配置文件中的配置项key的值,如果要读取外部properties配置文件的值,需要借助xml配置Spring,指定外部properties配置文件。

  作为测试,在classpath(src)下创建一个config.properties,添加一项内容:

1
name=hello world
  修改Spring配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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:property-placeholder location="classpath:config.properties" />

<!-- 需要使用context这个xmlns,然后使用<context:component-scan>指定需要扫描哪些package -->
<context:component-scan base-package="cn.woshilan.pojo, cn.woshilan.demo"></context:component-scan>
</beans>

  为Person类中name属性指定值为外部配置文件的name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cn.woshilan.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Person {

@Value("999")
private int id;

@Value("${name}") // 使用外部配置文件中name的值
private String name;

@Value("America")
private String addr;

// 隐藏了有参和无参构造方法、setter、getter、toString
}

  测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package cn.woshilan.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.woshilan.pojo.Person;

public class TestIOC {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");


Person bean = context.getBean("person", Person.class);

System.out.println(bean);
// Person [id=999, name=hello world, addr=America]
}
}
```  

对引用数据类型的属性进行注入——构造方法和setter注入

  对引用数据类型的属性进行注入时,有两种方式:构造方法注入和setter注入,如果使用xml配置文件形式,分别是用<constructor-args>和<property>标签。

  而如果是使用注解的话,那么就有两个注解可以实现这个功能:@Autowired@Resource,他们都能实现依赖注入,但是他们也有区别:

  @Resource是Java内置的注解,不能写在方法上,只能写在属性字段上。

  @Autowired是spring提供的注解,即可以写在属性字段上,也可以写在构造方法上,还可以写在setter上。
```java
package cn.woshilan.pojo;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Company {

@Resource // @Resource只能写在属性之前
private Person person1;

@Autowired // @Autowired可以写在属性前
private Person person;

public Company() {
super();
}

@Autowired // @Autowired也可以写在构造方法前,会自动根据所需参数类型进行注入
public Company(Person person1, Person person) {
super();
this.person1 = person1;
this.person = person;
}

@Autowired // @Autowired还可以写在setter上
public void setPerson(Person person) {
this.person = person;
}

public void setPerson1(Person person1) {
this.person1 = person1;
}

@Override
public String toString() {
return "Company [person1=" + person1 + ", person=" + person + "]";
}
}

  上面使用@Autowired和@Resource来进行注入的时候,是自动注入的。会根据byName方式,查看spring容器中是否有一个bean的id,该和属性名称相同的,如果有的话,就将这个bean注入到属性中。如果没有的话,就根据byType进行注入,即根据容器中的bean的type进行匹配,这个时候,如果有多个type相同的bean,就会报错,需要使用@Qualifier(“bean_id”)明确指定注入哪一个bean。

集合数据类型的注入

  对于集合类型,不建议使用注解方式注入,建议使用xml配置文件方式。

IoC的相关设置
  除了对bean的注入,还有其他的bean的设置也很重要,比如懒加载、作用域、初始化和销毁时调用的方法。

懒加载

  懒加载在xml配置中是设置bean的lazy-init属性值,true为设置为懒加载,false为设置为spring容器创建时创建bean,默认不是懒加载。

  可以使用@Lazy注解来设置懒加载,可以在配置类中创建对象的方法上使用,还可以在class的上面设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

package cn.woshilan.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

import cn.woshilan.pojo.Person;

@Configuration
public class MyConfiguration {

@Bean
@Lazy
public Person person111() {
return new Person(8888, "xyz", "beijing");
}
}

还可以在class上设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package cn.woshilan.pojo;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
@Lazy
public class Person {
private int id;
private String name;
private String addr;

// 隐藏了有参和无参构造方法、setter、getter、toString
}
```  



作用域

  作用域在xml中是设置scope属性,同样的,在注解中,提供了@Scope与之对应。和@Lazy使用相同,可以在配置类和javabean上使用。

```java
@Component
@Scope("singleton")
public class Person {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cn.woshilan.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import cn.woshilan.pojo.Person;

@Configuration
@ComponentScan("cn.woshilan.pojo")
public class MyConfiguration {

@Bean
@Scope("prototype")
public Person person() {
return new Person();
}
}

初始化和销毁回调程序

  如果是xml配置,可以使用init-method和destroy-method来设置初始话bean和销毁bean的时候调用。

  如果使用注解,可以实现两个接口,并重写两个方法即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cn.woshilan.pojo;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class Person implements InitializingBean, DisposableBean{

@Override
public void afterPropertiesSet() throws Exception {
System.out.println("初始化时被调用");
}

@Override
public void destroy() throws Exception {
System.out.println("被销毁时调用");
}
}

  如果不实现InitializingBean, DisposableBean接口,可以使用下面这种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.woshilan.pojo;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.stereotype.Component;

@Component
public class Person{

@PostConstruct
public void onInit() {
System.out.println("初始化时被调用");
}

@PreDestroy
public void onDestroy() {
System.out.println("被销毁时调用");
}

}

  还可以在配置类中进行设置,首先删除Person中的注解,然后修改配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cn.woshilan.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import cn.woshilan.pojo.Person;

@Configuration
@ComponentScan("cn.woshilan.pojo")
public class MyConfiguration {

@Bean(initMethod="onInit", destroyMethod="onDestroy")
public Person person() {
return new Person();
}
}

评论