Spring基于xml配置bean的练习
1. 项目基本配置
本文最后更新时间:2022年2月26号
远程物理机上面部署的node99,我创建了一个目录/root/liwldev/java/web/spring
创建项目SpringXML:mvn archetype:generate -DgroupId=com.liwl.dev -DartifactId=SpringXML -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
使用vscode远程连接node99开始练习
2. set设值注入
在pom.xml添加以下内容:
4.0.0
com.liwl.dev
SpringXML
jar
1.0-SNAPSHOT
SpringXML
http://maven.apache.org
5.3.15
junit
junit
3.8.1
test
junit
junit
3.8.1
test
org.springframework
spring-core
${spring.version}
org.springframework
spring-context
${spring.version}
org.apache.maven.plugins
maven-compiler-plugin
3.8.0
17
17
UTF8
在目录/root/liwldev/java/web/spring/SpringXML/src/main下创建resources目录,在该目录下再创建beans.xml,添加以下内容
在目录/root/liwldev/java/web/spring/SpringXML/src/main/java/com/liwl/dev下创建Person.java和PersonImpl.java文件
Person.java源码文件内容如下:
package com.liwl.dev;
public interface Person {
}
PersonImpl.java源码文件内容如下:
package com.liwl.dev;
public class PersonImpl01 implements Person {
private String name;
private int age;
public PersonImpl01(){
System.out.println("[PersonImpl01] 无参构造方法调用");
}
public PersonImpl01(String name,int age){
this.name = name;
this.age = age;
System.out.println("[PersonImpl01] 有参构造方法调用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "name:" + name + "\nage:" + age;
}
}
在beans.xml里面配置bean,内容如下:
在目录/root/liwldev/java/web/spring/SpringXML/src/test/java/com/liwl/dev下创建MyTest.java,内容如下:
package com.liwl.dev;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
PersonImpl01 liwl = (PersonImpl01) context.getBean("liwl");
System.out.println(liwl);
context.close();
}
}
F5运行MyTest.java,运行结果
[PersonImpl01] 无参构造方法调用
name:liwanliang
age:30
至此 ,一个使用spring框架xml配置bean的过程便完成了。
3. 构造方法注入
第二节中通过xml配置的标签property,这个是 使用了set设值方法配置的bean。本节练习使用构造方法配置bean。
修改beans.xml,内容如下:
F5运行MyTest.java,运行结果
[PersonImpl01] 有参构造方法调用
name:liwanliang
age:30
从结果能够看出,spring在创建PersonImpl的对象bean时,调用了有参构造函数。
4. 引用类型的注入
第二节中通过标签property配置的只是bean的基本数据类型属性。本节练习通过beans.xml注入引用类型。
先创建School.java和SchoolImpl.java源文件
School.java源文件
package com.liwl.dev;
public interface School {
}
SchoolImpl.java
package com.liwl.dev;
public class SchoolImpl implements School {
private String name;
private String address;
public SchoolImpl(){
System.out.println("[SchoolImpl] 无参构造方法调用");
}
public SchoolImpl(String name,String address){
this.name = name;
this.address = address;
System.out.println("[SchoolImpl] 有参构造函数调用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "name:" + name + "\naddress:" + address;
}
}
在beans.xml里面配置bean,如下:
F5运行MyTest.java,运行结果
[PersonImpl01] 无参构造方法调用
[SchoolImpl] 无参构造方法调用
name:liwanliang
age:30
school:name:jiangnan
address:wuxi
可以看到在beans.xml里面配置了,以此将SchoolImpl的bean对象注入到PersonImpl里面。
5. import标签
本节主要是对xml配置标签import的练习。
当一个项目变得复杂的时候,有必要进行模块化处理。xml同样可以分为多个xml配置文件,并且在主配置文件里面通过import标签引入。
修改beans.xml,内容如下:
创建person.xml,内容如下:
创建school.xml,内容如下:
F5运行MyTest.java,运行结果
[PersonImpl01] 无参构造方法调用
[SchoolImpl] 无参构造方法调用
name:liwanliang
age:30
school:name:jiangnan
address:wuxi
6. autowire标签
本节接第四节。之前通过property注入了基本数据类型和引用数据类型。这种方式主要是手动来注入。
假如一个类要注入很多bean,通过手动注入的方式不仅繁琐,而且容易出错。spring的xml配置提供了autowire来实现自动注入。
autowire=byName自动注入
修改beans.xml,内容如下:
F5运行MyTest.java,运行结果
[PersonImpl01] 无参构造方法调用
[SchoolImpl] 无参构造方法调用
name:liwanliang
age:30
school:name:jiangnan
address:wuxi
这里通过autowirel="byName"来替代了,实现了SchoolImpl的bean对象的注入。
工作原理就是spring会在扫描PersonImpl的bean时,发现了school的setter和getter,因此会从容器中去id=school的bean注入到id=liwl,这样就不需要我们手动配置了,避免了很多错误。
autowrie=byType自动注入
Spring会通过xml配置的autowire=byType来为配置了该标签的bean自动注入属性。比如PersonImpl了有school属性时,如果不手动配置而通过自动配置,除了能够通过autowire=byName来自动注入以外,通过byType注入表示Spring会扫描PersonImpl发现需要SchooImpl的bean,然后从容器里面找到已经注册的SchoolImpl的bean注入到PersonImpl内
修改beans.xml,内容如下
F5运行MyTest.java,运行结果
[PersonImpl01] 无参构造方法调用
[SchoolImpl] 无参构造方法调用
name:liwanliang
age:30
school:name:jiangnan
address:wuxi
7. primary和autowire-candidata标签
如果Spring容器里面已经注册了同源类的多个bean,在依赖注入的时候,使用autowire=byType时,Spring无法分辨出该加载哪一个,所以会报错。
xml配置文件可以通过primary标签和autowire-candidata标签来确定哪些bean具有优先权,也就是优先注入。
比如在beans.xml里面配置两个SchoolImpl的bean,把PersonImpl的bean就注入类型设置为autowire=byType,如下:
F5运行MyTest.java,运行报错
WARNING: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'liwl' defined in class path resource [beans.xml]: Unsatisfied dependency expressed through bean property 'school'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.liwl.dev.School' available: expected single matching bean but found 2: school01,school02
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'liwl' defined in class path resource [beans.xml]: Unsatisfied dependency expressed through bean property 'school'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.liwl.dev.School' available: expected single matching bean but found 2: school01,school02
从报错信息可以看出,Spring在为id=liwl的bean注入依赖时,在容器里面发现了2个SchoolImpl实现类的bean,它无法确认注入哪个,因此报错。
此时我们需要通过primary或者autowire-candidata来设置
使用primary标签
修改beans.xml,如下:
F5运行MyTest.java,运行结果:
[PersonImpl]:
name:liwanliang
age:30
school:SchoolImpl_name:jiangnan01,SchoolImpl_address:wuxi01
修改beans.xml,如下:
F5运行MyTest.java,运行结果:
[PersonImpl]:
name:liwanliang
age:30
school:SchoolImpl_name:jiangnan02,SchoolImpl_address:wuxi02
总结:
- primary默认是false。当两个同源的bean不显式设置primary=true时,都是false。
- 要优先注入某个bean,需要设置primary=true
使用autowire-candidata标签
修改beans.xml,如下:
F5运行MyTest.java,运行结果
[PersonImpl]:
name:liwanliang
age:30
school:SchoolImpl_name:jiangnan02,SchoolImpl_address:wuxi02
修改beans.xml,如下:
运行结果:
[PersonImpl]:
name:liwanliang
age:30
school:SchoolImpl_name:jiangnan01,SchoolImpl_address:wuxi01
总结:
- 如果没有显式设置
autowire-candidate="false",那么默认autowire-candidate="true" - 当要默认屏蔽同源的bean时,需要设置
autowire-candidate="false"
8. abstract和parent标签
当多个bean需要注入同一个依赖bean时,可以在xml配置文件中通过abstract和parent标签配置需要注入的bean。
创建PersonImpl02.java
package com.liwl.dev;
public class PersonImpl02 implements Person {
private String name;
private int age;
private School school;
public PersonImpl02(){
System.out.println("[PersonImpl02] 无参构造方法调用");
}
public PersonImpl02(String name,int age,School school){
this.name = name;
this.age = age;
this.school = school;
System.out.println("[PersonImpl02] 有参构造方法调用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return "[PersonImpl02]:" + "\nname:" + name + "\nage:" + age + "\nschool:" + school;
}
}
修改beans.xml
修改MyTest.java
package com.liwl.dev;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
PersonImpl01 liwl01 = (PersonImpl01) context.getBean("liwl01");
System.out.println(liwl01);
PersonImpl02 liwl02 = (PersonImpl02) context.getBean("liwl02");
System.out.println(liwl02);
context.close();
}
}
修改beans.xml
运行结果
[PersonImpl01]:
name:liwl01
age:30
school:SchoolImpl_name:jiangnan,SchoolImpl_address:wuxi
[PersonImpl02]:
name:liwl
age:30
school:SchoolImpl_name:jiangnan,SchoolImpl_address:wuxi
总结:
- 定义一个模板bean,不使用class标签,而使用abstract标签。其他bean通过parent标签依赖此bean
- 模板bean可以设置属性默认值,依赖于模板bean的bean可以单独设置属性默认值,单独设置后覆盖模板bean设置的值
9. bean的四种构造方式
10. lazy-init标签
默认情况下,spring在启动时创建xml配置文件里面所有的bean。当一个复杂的系统有很多bean需要加载的时候 ,会导致spring启动项目很慢。
修改beans.xml
修改MyTest.java
package com.liwl.dev;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
context.close();
}
}
运行结果
[PersonImpl01] 无参构造方法调用
[SchoolImpl] 无参构造方法调用
[PersonImpl02] 无参构造方法调用
[PersonImpl02] 对象销毁
[PersonImpl01] 对象销毁
[SchoolImpl] 对象销毁
修改beans.xml
运行MyTest.java
[PersonImpl02] 无参构造方法调用
[SchoolImpl] 无参构造方法调用
[PersonImpl02] 对象销毁
[SchoolImpl] 对象销毁
11. bean的创建和销毁顺序
先看无依赖的bean的创建和销毁顺序:
修改beans.xml,如下:
修改Person.java
package com.liwl.dev;
import org.springframework.beans.factory.DisposableBean;
public interface Person extends DisposableBean{
}
修改PersonImpl01.java
package com.liwl.dev;
public class PersonImpl01 implements Person {
private String name;
private int age;
private School school;
public PersonImpl01(){
System.out.println("[PersonImpl01] 无参构造方法调用");
}
public PersonImpl01(String name,int age,School school){
this.name = name;
this.age = age;
this.school = school;
System.out.println("[PersonImpl01] 有参构造方法调用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return "[PersonImpl01]:" + "\nname:" + name + "\nage:" + age + "\nschool:" + school;
}
@Override
public void destroy() throws Exception {
System.out.println("[PersonImpl01] 对象销毁");
}
}
修改PersonImpl02.java
package com.liwl.dev;
public class PersonImpl02 implements Person {
private String name;
private int age;
private School school;
public PersonImpl02(){
System.out.println("[PersonImpl02] 无参构造方法调用");
}
public PersonImpl02(String name,int age,School school){
this.name = name;
this.age = age;
this.school = school
System.out.println("[PersonImpl02] 有参构造方法调用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return "[PersonImpl02]:" + "\nname:" + name + "\nage:" + age + "\nschool:" + school;
}
@Override
public void destroy() throws Exception {
System.out.println("[PersonImpl02] 对象销毁");
}
}
修改MyTest.java
package com.liwl.dev;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
PersonImpl01 liwl01 = (PersonImpl01) context.getBean("liwl01");
System.out.println(liwl01);
PersonImpl02 liwl02 = (PersonImpl02) context.getBean("liwl02");
System.out.println(liwl02);
context.close();
}
}
F5运行MyTest.java,运行结果
[PersonImpl01] 无参构造方法调用
[PersonImpl02] 无参构造方法调用
[PersonImpl01]:
name:liwl01
age:30
school:null
[PersonImpl02]:
name:liwl02
age:30
school:null
[PersonImpl02] 对象销毁
[PersonImpl01] 对象销毁
能够看到:PersonImpl01先于PersonImpl02创建,后于PersonImpl02销毁
继续验证上面结论。调整beans.xml,如下:
运行结果
[PersonImpl02] 无参构造方法调用
[PersonImpl01] 无参构造方法调用
[PersonImpl01]:
name:liwl01
age:30
school:null
[PersonImpl02]:
name:liwl02
age:30
school:null
[PersonImpl01] 对象销毁
[PersonImpl02] 对象销毁
总结:无依赖的两个bean,其在Spring里面创建的顺序:先配置先创建,后配置先销毁
再来看有依赖的bean的创建和销毁顺序:
修改beans.xml,如下:
创建Company.java
package com.liwl.dev;
import org.springframework.beans.factory.DisposableBean;
public interface Company extends DisposableBean{
}
创建CompanyImpl.java
package com.liwl.dev;
public class CompanyImpl implements Company{
private String name;
private String address;
public CompanyImpl(){
System.out.println("[CompanyImpl] 无参构造方法调用");
}
public CompanyImpl(String name,String address){
System.out.println("[CompanyImpl] 有参构造方法调用");
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "CompanyImpl_name:" + name + ",CompanyImpl_address:" + address;
}
@Override
public void destroy() throws Exception {
System.out.println("[CompanyImpl] 对象销毁");
}
}
修改SchoolImpl.java,使其依赖Company对象
package com.liwl.dev;
public class SchoolImpl implements School {
private String name;
private String address;
private Company company;
public SchoolImpl(){
System.out.println("[SchoolImpl] 无参构造方法调用");
}
public SchoolImpl(String name,String address,Company company){
this.name = name;
this.address = address;
System.out.println("[SchoolImpl] 有参构造函数调用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
@Override
public String toString() {
return "SchoolImpl_name:" + name + ",SchoolImpl_address:" + address + ",SchoolImpl_company:" + company;
}
@Override
public void destroy() throws Exception {
System.out.println("[SchoolImpl] 对象销毁");
}
}
F5运行MyTest.java,运行结果
[CompanyImpl] 无参构造方法调用
[SchoolImpl] 无参构造方法调用
[PersonImpl01] 无参构造方法调用
[PersonImpl01]:
name:liwl
age:30
school:SchoolImpl_name:jiangnan,SchoolImpl_address:wuxiSchoolImpl_company:CompanyImpl_name:nsccwx,CompanyImpl_address:wuxi
[PersonImpl01] 对象销毁
[SchoolImpl] 对象销毁
[CompanyImpl] 对象销毁
总结:
- 依赖关系:PersonImpl依赖SchoolImpl,SchoolImpl依赖CompanyImpl
- bean创建顺序:CompanyImpl先于SchoolImpl,SchooImpl先于PersonImpl
- bean销毁顺序:PersonImpl先于SchoolImpl,SchoolImpl先于CompanyImpl
调整beans.xml里面配置bean的顺序,再次验证一下
运行结果
[PersonImpl01] 无参构造方法调用
[SchoolImpl] 无参构造方法调用
[CompanyImpl] 无参构造方法调用
[PersonImpl01]:
name:liwl
age:30
school:SchoolImpl_name:jiangnan,SchoolImpl_address:wuxiSchoolImpl_company:CompanyImpl_name:nsccwx,CompanyImpl_address:wuxi
[PersonImpl01] 对象销毁
[SchoolImpl] 对象销毁
[CompanyImpl] 对象销毁
调整beans.xml,如下:
运行结果
[CompanyImpl] 无参构造方法调用
[SchoolImpl] 有参构造函数调用
[PersonImpl01] 有参构造方法调用
[PersonImpl01]:
name:liwl
age:30
school:SchoolImpl_name:jiangnan,SchoolImpl_address:wuxi,SchoolImpl_company:CompanyImpl_name:nsccwx,CompanyImpl_address:wuxi
[PersonImpl01] 对象销毁
[SchoolImpl] 对象销毁
[CompanyImpl] 对象销毁
总结:
- 构造方法注入时,有强依赖。被依赖的bean先创建,后销毁
- 使用自动注入时,虽然有依赖,但是也是按照配置的顺序进行创建和销毁