目录
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>
在注入属性值时,value
和ref
都表示“值”,其中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开始顺序编号!至于参数的值的配置,依然需要根据值的类型选择使用value
或ref
来配置!
练习:假设存在Student
类,类中有String name
、Integer age
、Date 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
属性,并不配置value
或ref
属性,而是在子级添加<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方式为ValueBean
的name
属性注入值,首先,还是要添加对应的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:什么时候需要定义构造方法
通常,如果需要自定义构造方法,可能是因为:
-
创建对象的同时,快速的为属性赋值;
-
限制对象的创建过程,例如在单例模式的设计中,将构造方法声明为私有权限;
-
强制要求传入某些数据。
附2:关于集合的类型
在Collection
接口的子级有List
和Set
这2种接口类型的集合!
List
集合是序列的,存入到该集合中的元素是可重复的!
Set
集合是散列的,存入到该集合中的元素是不可重复的,是否重复的判断标准是:2个对象的equals()
对比结果为true
,且hashCode()
的返回值相同,则视为同1个数据!
另外,还在Map
类型的集合,是用于存储键值对的集合,即在集合中的每个数据都是有Key
和Value
的,并且,Key
的特征与Set
集合中存储数据的方式相同!
如果这篇文章有帮助到您,请简单给个赞吧,谢谢~