Java - Spring 框架 SpringFramework 详解(二)

本文详细介绍了Spring框架中的IoC(控制反转)概念,包括通过SET方式、构造方法注入属性值,以及如何注入集合类型的值如List、Set、数组、Map等。此外,还讲解了Spring表达式在注入属性时的应用,以及如何注入Properties类型的属性值。文章通过实例展示了如何在代码中实现这些功能,并提供了相关的测试用例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. Spring IoC

1.1. 什么是IoC

Spring IoC表示控制反转,即在传统模式下,是由程序员编写代码创建并管理对象,例如User user = new User();,当使用Spring框架之后,则将对象的创建、管理的“权力”交给了框架,则表示控制权是给了框架,所以,称之为控制反转!

Spring框架是通过DI(做法、手段)实现了IoC(效果、目标),DI(Dependency Injection)表示依赖注入

1.2. 通过SET方式注入属性的值

在对象的运行过程中,需要使用另一个对象,在编写代码时,在一个类中需要声明另一个类的对象,则表现出了依赖关系!例如在UserLoginServlet中声明了UserDao类型的属性,则称之为UserLoginServlet依赖于UserDao

使用Spring框架,不仅可以管理某个类的对象,还可以在创建对象的同时,为该类中声明的属性进行赋值,这种赋值操作就称之为注入

假设存在:

public class User {
	public String name;
}

希望通过Spring获取该User类的对象时,对象的name属性已经有值!首先,需要为name属性添加规范的SET方法:

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

然后,在Spring的配置文件中,为配置User类的<bean>节点添加子级的<property>节点:

<!-- name属性:需要被注入值的属性名 -->
<!-- value属性:需要注入的值 -->
<property name="name" value="David"></property>

练习:在User类中添加Integer age属性,表示年龄,添加String from属性,表示来自哪里,为这2个属性注入值!

注意:在配置<property>节点时,name属性的值,其实,并不是类中的属性名,而是类中的方法名调整后的属性名,即将方法名称左侧的set去掉,将字母改为小写后名称!或者说,在Spring框架工作时,会根据配置的<property>中的name值,将首字母大写,并在左侧拼接上set,形成方法名,并调用该方法!

另外,可能还有一些类中的属性,并不是可以直接写出来的直接值,例如存在UserLoginServlet,其中有UserDao属性,如果需要为这个属性注入值,就需要使用ref属性引用另一个Bean:

<bean id="userDao" class="cn.tedu.spring.UserDao"></bean>
	
<bean id="userLoginServlet" class="cn.tedu.spring.UserLoginServlet">
	<!-- ref属性:引用另一个bean,该属性值是另一个bean的id -->
	<property name="userDao" ref="userDao"></property>
</bean>

在注入属性值时,valueref都表示“值”,其中value用于可以直接写出来的值,例如字符串类型的值、数值、布尔值,而ref用于引用另一个Bean,当使用ref属性时,取值为另一个<bean>节点的id

1.3. 通过构造方法注入属性的值【不常用】

假设存在:

public class Person {
	public String from;
}

如果需要为以上from属性注入值,并且不使用SET方式来注入,则可以先添加带参数的构造方法,在构造方法中,为from属性赋值:

public class Person {
	public String from;

	public Person(String from) {
		super();
		this.from = from;
	}
}

在Spring的配置文件中:

<bean id="person" class="cn.tedu.spring.Person">
	<!-- constructor-arg:配置构造方法的参数 -->
	<!-- index属性:配置的是构造方法中的第几个参数,从0开始顺序编号 -->
	<constructor-arg index="0" value="Guangzhou"></constructor-arg>
</bean>

以上使用的<constructor-arg>节点就是用于配置构造方法的参数的,如果构造方法中有多个参数,则需要多个该节点,每个节点配置1个参数!在该节点的index表示配置第几个参数,取值从0开始顺序编号!至于参数的值的配置,依然需要根据值的类型选择使用valueref来配置!

练习:假设存在Student类,类中有String nameInteger ageDate createdTime这3个属性,请通过构造方法注入值的方式,为这3个属性注入值!

答案:关于Student类:

public class Student {
	
	public String name;
	public Integer age;
	public Date createdTime;
	
	public Student(String name, Integer age, Date createdTime) {
		super();
		this.name = name;
		this.age = age;
		this.createdTime = createdTime;
	}

	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + ", createdTime=" + createdTime + "]";
	}
	
}

答案:关于Spring配置:

<bean id="date" class="java.util.Date">
</bean>
<bean id="student" class="cn.tedu.spring.Student">
	<constructor-arg index="0" value="Henry" />
	<constructor-arg index="1" value="28" />
	<constructor-arg index="2" ref="date" />
</bean>

2. 注入集合类型的值

2.1. 注入List类型的值【不常用】

假设存在SampleBean的类,在类中有List<String> names属性,用于存储多个用户的名称,需要为该集合类型的属性注入值!

首先,需要为该属性添加SET方法:

public class SampleBean {
		
	// Lucy, Jack, Kate, Tom
	public List<String> names;

	public void setNames(List<String> names) {
		this.names = names;
	}

}

在配置时,由于使用的是SET方式注入,所以,依赖在<bean>节点下添加子级的<property>节点,只不过这次在<property>中只配置name属性,并不配置valueref属性,而是在子级添加<list>节点,并在<list>的子级添加若干个<value>节点以配置若干个值:

<bean id="sampleBean" class="cn.tedu.spring.SampleBean">
	<property name="names">
		<list>
			<value>Lucy</value>
			<value>Jack</value>
			<value>Kate</value>
			<value>Tom</value>
		</list>
	</property>
</bean>
2.2. 注入SET集合类型的属性值【不常用】

假设在以上类中存在Set集合类型的属性,并需要注入值:

// Beijing, Shanghai, Guangzhou, Shenzhen
public Set<String> cities;

先在类中添加该属性对应的SET方法:

public void setCities(Set<String> cities) {
	this.cities = cities;
}

然后,在Spring配置文件中添加配置:

<property name="cities">
	<set>
		<value>Beijing</value>
		<value>Shanghai</value>
		<value>Guangzhou</value>
		<value>Shenzhen</value>
	</set>
</property>

在Spring框架中处理Set类型的数据时,使用的是LinkedHashSet,这种Set是使用链表的结构存储数据的,所以,查询出来的数据的顺序与添加时是保持一致的!

2.3. 注入数组类型的属性值【不常用】

假设在以上类中存在数组类型的属性,并需要注入值:

// JavaOOP, JavaSE, MySQL, JDBC
public String[] skills;

先在类中添加该属性对应的SET方法:

public void setSkills(String[] skills) {
	this.skills = skills;
}

然后,在Spring配置文件中添加配置:

<property name="skills">
	<array>
		<value>JavaOOP</value>
		<value>JavaSE</value>
		<value>MySQL</value>
		<value>JDBC</value>
	</array>
</property>

在配置集合类型的属性时,配置List集合类型的属性,与配置数组类型的属性时,使用的节点结构可以随意,即:使用<list>节点,或使用<array>节点均可!

2.4. 注入Map集合类型的属性值【不常用】

假设在以上类中存在Map类型的属性,并需要注入值:

// username=Billy, age=23, password=javaee
public Map<String, Object> session;

先在类中添加该属性对应的SET方法:

public void setSession(Map<String, Object> session) {
	this.session = session;
}

然后,在Spring配置文件中添加配置:

<property name="session">
	<map>
		<entry key="username" value="Billy" />
		<entry key="age" value="23" />
		<entry key="password" value="javaee" />
	</map>
</property>
2.5. 使用util:xx系列节点【不常用】

假设在以上类中存在List集合类型的属性,并需要注入值:

// Eclipse, MySQL, Office, Intellij IDEA
public List<String> tools;

先在类中添加该属性对应的SET方法:

public void setTools(List<String> tools) {
	this.tools = tools;
}

然后,在Spring配置文件中,先添加一个与<bean>节点同级别的<util:list>节点:

<util:list id="tools">
	<value>Eclipse</value>
	<value>MySQL</value>
	<value>Office</value>
	<value>Intellij IDEA</value>
</util:list>

然后,需要注入属性值时,通过ref引用到以上配置的节点即可:

<property name="tools" ref="tools" />

使用这种做法的配置可以将一部分代码从原本的<bean>的子级分离出来,虽然可能不太直观,但是,在代码篇幅较长的情况下,可以利于管理代码,另外,使用这种做法还可以将配置的集合的值进行复用(重复使用)!

另外,还有<util:set><util:map>节点可以配置Set集合和Map集合的值!

2.6. 注入Properties类型的属性值【常用】

假设在src/main/resources存在jdbc.properties文件,文件中有如下配置:

url=jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=utf-8
driver=com.mysql.jdbc.Driver
username=root
password=123456

需要将这些配置读取到程序中,则需要在类中声明Properties类型的属性:

// 值来自src/main/resources/jdbc.properties
public Properties jdbcConfig;

要使用SET方式注入值,还是添加SET方法:

public void setJdbcConfig(Properties jdbcConfig) {
	this.jdbcConfig = jdbcConfig;
}

在Spring的配置文件中,首先,使用<util:properties>节点读取以上jdbc.properties配置文件:

<!-- util:properties节点可以用于读取.properties类型的配置文件 -->
<!-- 该节点就是一个Properties类型的对象 -->
<!-- location属性:读取哪个文件 -->
<util:properties id="jdbcConfig"
	location="classpath:jdbc.properties" />

然后,在类的<bean>节点子级,添加<property>节点注入属性的值:

<property name="jdbcConfig" ref="jdbcConfig" />

当然,也可以不使用.properties文件,而是直接将各属性的配置直接注入到<property>节点的下级,例如:

<property name="jdbcConfig">
	<props>
		<prop key="username">administrator</prop>
		<prop key="password">12345678</prop>
	</props>
</property>

通常,还是推荐将这类配置写在.properties文件中,而不推荐以上做法!

3. Spring表达式

在配置Spring的配置文件时,可以通过Spring表达式实现获取另一个bean的某个属性值

假设存在ValueBean类,其中的String name属性的值需要来自User类中的name属性,如果使用SET方式为ValueBeanname属性注入值,首先,还是要添加对应的SET方法:

public class ValueBean {
		
	// 值:User类的对象中的name属性值
	public String name;

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

}

然后,在Spring的配置文件中进行配置:

<bean id="valueBean" class="cn.tedu.spring.ValueBean">
	<property name="name" value="#{user.name}" />
</bean>

Spring表达式的基本格式是使用#{}格式的占位符,需要使用value属性进行配置!

如果值来自另一个bean的属性,则Spring表达式的格式是:

#{bean-id.属性名}

当然,值也可以是另一个bean的List集合中的某个元素,例如:

// 值:SampleBean的names中的第3个值
public String username;

则注入值时的配置为:

<property name="username" value="#{sampleBean.names[2]}" />

所以,如果要获取另一个bean中的List集合中的某个元素,Spring表达式的格式是:

#{bean-id.属性名[索引]}

由于在Spring中注入值时,Set集合的实现类是LinkedHashSet,是以链表的形式存储数据的,所以,也可以取出这种集合中的第x个元素,取值方式与从List集合中取出某个的值的做法完全相同!

另外,还可以获取某个Map类型的属性中的值,例如:

// 值:SampleBean的session的password
public String password;

则配置为:

<property name="password" value="#{sampleBean.session.password}" />

所以,获取Map中的值的Spring表达式的格式为:

#{bean-id.Map类型的属性名.Key}

另外,还可以写成:

#{bean-id.Map类型的属性名['Key']}

通过以上方式,还可以获取Properties类型的属性的某个值!

附:
单元测试代码:

package cn.tedu.spring;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.Arrays;

public class TestGetBean {
    private ClassPathXmlApplicationContext ac;
    @Before
    public void onBefore () {
        // 加载spring 配置文件,获取spring容器
        ac = new ClassPathXmlApplicationContext("spring.xml");
    }

    @Test
    public void testGetBean () {
        // 通过spring 容器获取对象
        User user = (User) ac.getBean("user");
        // 测试
        System.out.println("user => " + user);
        System.out.println("user => name = " + user.name);
        System.out.println("user => age = " + user.age);
    }

    @Test
    public void testUserLoginServlet () {
        // 通过spring 容器获取对象
        UserLoginServlet userLoginServlet = (UserLoginServlet) ac.getBean("userLoginServlet");

        // 测试
        System.out.println("userLoginServlet => " + userLoginServlet);
        System.out.println("userLoginServlet => userDao = " + userLoginServlet.userDao);
    }

    @Test
    public void testPerson () {
        // 通过spring 容器获取对象
        Person person = ac.getBean("person", Person.class);
        // 测试
        System.out.println("person => " + person);
        System.out.println("person => from = " + person.from);
    }

    @Test
    public void testStudent () {
        // 通过spring 容器获取对象
        Student student = ac.getBean("student", Student.class);
        // 测试
        System.out.println("student => " + student);
        System.out.println("student => name = " + student.name);
        System.out.println("student => age = " + student.age);
        System.out.println("student => createdTime = " + student.createdTime);
    }

    @Test
    public void testSamleBean () {
        // 通过spring 容器获取对象
        SampleBean sampleBean = ac.getBean("sampleBean", SampleBean.class);
        // 测试
        System.out.println("sampleBean => " + sampleBean);
        System.out.println("sampleBean.names => " + sampleBean.names);
        System.out.println(sampleBean.names.getClass());
        System.out.println("sampleBean.cities => " + sampleBean.cities);
        System.out.println(sampleBean.cities.getClass());
        System.out.println("sampleBean.skills => " + Arrays.toString(sampleBean.skills));
        System.out.println("sampleBean.session => " + sampleBean.session);
        System.out.println("sampleBean.tools => " + sampleBean.tools);
        System.out.println("sampleBean.jdbcConfig => " + sampleBean.jdbcConfig);
        System.out.println("url => " + sampleBean.jdbcConfig.get("url"));
        System.out.println("driver => " + sampleBean.jdbcConfig.get("driver"));
        System.out.println("username => " + sampleBean.jdbcConfig.get("username"));
        System.out.println("password => " + sampleBean.jdbcConfig.get("password"));
    }

    @Test
    public void testValueBean () {
        // 通过spring 容器获取对象
        ValueBean valueBean = ac.getBean("valueBean", ValueBean.class);
        // 测试
        System.out.println("valueBean.name => " + valueBean.name);
        System.out.println("valueBean.username => " + valueBean.username);
        System.out.println("valueBean.city => " + valueBean.city);
    }

    @After
    public void onAfter () {
        // 释放资源
        ac.close();
    }
}

附1:什么时候需要定义构造方法

通常,如果需要自定义构造方法,可能是因为:

  1. 创建对象的同时,快速的为属性赋值;

  2. 限制对象的创建过程,例如在单例模式的设计中,将构造方法声明为私有权限;

  3. 强制要求传入某些数据。

附2:关于集合的类型

Collection接口的子级有ListSet这2种接口类型的集合!

List集合是序列的,存入到该集合中的元素是可重复的!

Set集合是散列的,存入到该集合中的元素是不可重复的,是否重复的判断标准是:2个对象的equals()对比结果为true,且hashCode()的返回值相同,则视为同1个数据!

另外,还在Map类型的集合,是用于存储键值对的集合,即在集合中的每个数据都是有KeyValue的,并且,Key的特征与Set集合中存储数据的方式相同!

如果这篇文章有帮助到您,请简单给个赞吧,谢谢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值