关于Spring中IoC的一些理解

简介:梳理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方式顾名思义,就是调用类中的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>

或者使用注解:
在这里插入图片描述
这两种方式都可以使得我们拿到不同的对象:
在这里插入图片描述

6.bean的生命周期

转载:https://www.cnblogs.com/javazhiyin/p/10905294.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值