简介:梳理Spring中IoC的相关知识。
目录
1.IoC基本概念
IoC(Inversion of Control),控制反转。原先我们创建对象和管理对象(初始化等)的时候,都是通过在本类中,使用构造方法来创建,而现在,我们将对象的创建和管理,统一交给Spring工厂(容器),当我们想要对象时,就让Spring容器给我们一个。
我们会把对象称为一个bean,所以Spring工厂,也可以被称为生产bean的工厂,即BeanFactory。这些bean被放入Spring中,等待被创建,所以这个工厂也可以形象得称为bean容器。
既然Spring要接管对象的管理,那么我们就得告诉他,需要生成的对象的是怎样数据类型,要不要初始化一些属性,以及该对象的id等。否则空有机器,没有原料,工厂也无法生产。
下面的内容,就是阐明一下如何告诉Spring这些信息。
2. 装配Bean信息到Spring中
大体可以分为2种装配方式:基于xml配置文件和基于注解
2.1装配Bean基于xml
Spring会去扫描配置文件,然后根据配置文件,就可以生产对象了。所以我们要把要生产的对象的信息,放入配置文件中。
文件的其他部分这里不具体展开,我们只看装配的部分。
假如现在有一个用户类User,想要让工厂能够生产该对象,只需要在xml中写上:
<bean id="user" class="com.hello.pojo.User"></bean>
- id用来标识这个对象(bean)。之所以要id,是因为有可能有多个该类型的对象,无法区分,所以只能通过id来区别,如果对工厂多次请求同一个id,那么得到的将会是同一个对象(默认是同一个,也可修改)。
- class的值为该类的全限定类名。
这里Spring工厂创建对象的方式,默认使用的类的无参构造函数。所以如果把无参构造弄没了,就会抛出异常。
通过上面的方式,已经可以得到对象了,但还不能对对象进行一些初始化的设置,如果想得到含有特定初始值属性的对象,就需要把你想设置的默认的属性和属性值,也告诉Spring。这种属性注入的行为,有一个专有名词,叫做
DI(Dependency Injection)——依赖注入:
我们以前对对象进行属性的设置,最常用的莫过于构造方法设置和Setter方法设置,所以Spring这里,我们也从这2个方面来看。
先假设User类有3个属性:String name和int age,Book book;
<bean id="user" class="com.hello.pojo.User">
<constructor-arg name="name" value="Tom"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="book" ref="myBook"></constructor-arg>
</bean>
<bean id="myBook" class="com.hello.pojo.Book">
<constructor-arg name="bname" value="十万个为什么"></constructor-arg>
</bean>
这种方式相当于使用了如下构造函数:
public User(String name, int age) {
this.name = name;
this.age = age;
}
配置文件中的name就是属性名,value就是属性值,如果属性值不是基本数据类型或String,则使用ref来引用bean对象。所以name属性必须要和构造函数中的形参一致。
但是,有很多时候,我们不知道类中的属性名,到底是什么。所以还有一种方式,也可以完成这种注入:
<bean id="user" class="com.hello.pojo.User">
<constructor-arg index="0" type="java.lang.String" value="Tom"></constructor-arg>
<constructor-arg index="1" type="int" value="18"></constructor-arg>
<constructor-arg index="2" type="com.bdqn.pojo.Book" ref="myBook"></constructor-arg>
</bean>
<bean id="myBook" class="com.hello.pojo.Book">
<constructor-arg name="bname" value="十万个为什么"></constructor-arg>
</bean>
index表示参数列表的索引值,type表示该数据的数据类型,value依然表示具体的属性值。
Setter方式顾名思义,就是调用类中的Setter函数来设置属性。格式如下:
<bean id="user" class="com.hello.pojo.User">
<property name="name" value="Jerry"></property>
<property name="age" value="28"></property>
<property name="book" ref="myBook"></property>
</bean>
<bean id="myBook" class="com.hello.pojo.Book">
<property name="bname" value="海底两万里"></property>
</bean>
标签即属性标签,name是属性名,value是属性值。这种方式默认调用的是无参的构造方法,然后再调用相应的Setter方法来注入属性,所以类里一定要提供Setter方法,并且取名要规范,不然xml中的name会报错。
除了上述两种方式,还有p命名空间
和SpEL
的注入方式,这2种其实就是对xml文件的写法做出了简化操作,这里不具体展开。
p命名空间:
对“setter方法注入”进行简化,替换<property name="属性名">,而是在
<bean p:属性名="普通值" p:属性名-ref="引用值">
p命名空间使用前提,必须添加命名空间
SpEL:
对<property>进行统一编程,所有的内容都使用value
<property name="" value="#{表达式}">
#{123}、#{'jack'} : 数字、字符串
#{beanId} :另一个bean引用
#{beanId.propName} :操作数据
#{beanId.toString()} :执行方法
#{T(类).字段|方法} :静态方法或字段
上面说的属性包括基本数据类型,String和一般引用类型,如果想要注入集合类型,写法上还需要做出一些改变:
集合的注入都是给<property>添加子标签
数组:<array>
List:<list>
Set:<set>
Map:<map> ,map存放k 键值对,使用<entry>描述
例子如下:
<bean id="user" class="com.hello.pojo.User" >
<property name="arrayData">
<array>
<value>Tom<alue>
<value>Jerry<alue>
</array>
</property>
<property name="listData">
<list>
<value>Tom</value>
<value>Jerry</value>
</list>
</property>
<property name="setData">
<set>
<value>Tom</value>
<value>Jerry</value>
</set>
</property>
<property name="mapData">
<map>
<entry key="jack" value="杰克"></entry>
<entry>
<key><value>rose</value></key>
<value>肉丝</value>
</entry>
</map>
</property>
</bean>
2.2装配Bean基于注解
用xml文件的方式装配明显过于繁琐,日常开发中,使用注解是更常用的开发方式。
@Component:我们以前是通过标签的class属性,把对象的数据类型告诉工厂,而现在,就可以在类的上方标注该注解,从而达到一样的效果,而如果想把id的生成部分,也放在注解中,只需使用@Component(“id”)。
web开发中,提供了3个@Component注解衍生注解(功能一样)
@Repository:数据层
@Service:业务层
@Controller:控制层
关于DI,也有相应的注解来代替xml配置:
基本数据类型和String,我们使用@Value()就可以了。
引用值的话,
方法1:使用@Autowired,按类型注入,Spring会自动找到匹配类型的bean来返回(比如这里就会找bean标签中class属性为Book的),但如果工厂中有多个同类型的bean,则会抛出异常。此时就需要方法2。
@Autowired
private Book book;
方法2:使用@Autowired和@Qualifier(“id”)共同注解,这样就可以得到准确的对象了:
@Autowired
@Qualifier("myBook1")
private Book book;
方法2的这种写法比较麻烦,也可以直接写@Resource(name = “id”)来代替:
@Resource(name = "myBook1")
private Book book;
注:注解方式默认通过无参构造来创建对象。
3. 从Spring中获得对象
前面说了那么多,都相当于给Spring工厂提供原料,下面我们来看真正的生产是如何进行的(即如何从容器中获得一个baen)。
Spring中工厂是一个抽象的接口——BeanFactory。它还有一个更强大的子接口——ApplicationContext,这也是我们要使用的接口。从这个名字也可以看出,这个接口是用来获得上下文并创建对象的
。这里所谓的上下文,就是我们上面说的“原料”,该接口实现类对象从配置文件中获得上下文(原料),然后再进行生产。
该接口有2个常用实现类:
ClassPathXmlApplicationContext:从类路径中加载xml。
FileSystemXmlApplicationContext:从盘符加载xml。
我们通常使用第一个。
所以,我们想生产的第一步,就是实例出一个真正的工厂,并且把xml文件传进去。之后我们就可以使用该工厂对象来创建对象了~
String path = "配置文件的类路径(src下的路径)";
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext(path);
想要从容器中生产对象,有2种方式:
方式1:通过类型:
User u1 = applicationContext.getBean(User.class);
System.out.println(u1);
这种方式很明显,有一个缺陷。当工厂中有多个该类型的bean,使用该方式就会抛出异常,因为容器无法确定要返回哪一个bean给你。
方式2:通过id:
User u1 = (User) applicationContext.getBean("user");
System.out.println(u1);
这种方式就能准确的获得bean对象。但是,由于该方法返回值是Object,所以还需要强转。
如果两个都传,那就既可以准确获取,又不用强转。
User u1 = applicationContext.getBean("user",User.class);
System.out.println(u1);
小总结
可见,使用Spring来创建对象最麻烦的地方在于属性的注入。通过使用Spring,我们通过注解的方式,就可以轻松得到被初始化的对象。举一个例子:
User类:
@Component("user")
public class User {
@Value("Tom")
private String name;
@Value("18")
private int age;
@Resource(name = "myBook1")
private Book book;
//省略构造方法和Getter/Setter方法..
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", book=" + book +
'}';
}
}
xml中的Book类:
<bean id="myBook1" class="com.hello.pojo.Book">
<property name="bname" value="海底两万里"></property>
</bean>
<bean id="myBook2" class="com.hello.pojo.Book">
<property name="bname" value="海底一万里"></property>
</bean>
测试类:
public class Demo {
@Test
public void f() {
String path = "beans.xml";
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext(path);
User u1 = applicationContext.getBean("user",User.class);
System.out.println(u1);
}
}
打印结果:
这里可以看出,我们想要两个不同参数的Book对象,是通过xml文件配置的,如果想要通过注解的话,可以这样写:
@Configuration
public class Book {
private String bname;
@Bean
public Book myBook1(){
return new Book("海底两万里");
}
@Bean
public Book myBook2(){
return new Book("鲁兵逊漂流记");
}
}
不使用@Component把Book放入工厂中,而是使用@Configuration把Book变成配置类,再在方法上加@Bean注解,这样就是 方法
和bean
产生了一一对应的关系。该bean的id就是方法的名字,class就是该方法返回的数据类型。
4.bean实例化的方式
之前使用的实例化,或无参或有参,都是通过构造方法来实例化对象的。在实际开发中,也很有可能会使用静态工厂或工厂来创建对象:
- 静态工厂
<bean id="" class="工厂的全限名" factory-method="生产对象的静态方法">
- 实例工厂
<bean id="" factory-bean="工厂id" factory-method="生产对象的方法">
<bean id="工厂id" class="工厂的全限名">
5.bean的作用域
标签的scope属性,可以设置bean的作用域。常见的属性值有:
singleton(默认值)和prototype。
bean对象默认是单例的,每次从工厂获得该bean,得到的都是同一个对象:
String path = ".....xml";
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext(path);
User u1 = applicationContext.getBean("user",User.class);
User u2 = applicationContext.getBean("user",User.class);
System.out.println(u1==u2); // true
但如果我们改成prototype,那么每次就会得到一个新的对象。
可以在xml中添加:
<bean id="user" class="com.hello.pojo.User" scope="prototype"></bean>
或者使用注解:
这两种方式都可以使得我们拿到不同的对象: