基于XML配置方式组件管理
1.组件信息声明配置
定义XML配置文件,声明组件类信息
1.1 基于无参构造函数
先准备一个普通的类,里面默认包含无参构造
package com.ztong.ioc_01;
public class HappyComponent {
//默认包含无参构造
public void dowork(){
}
}
写一个xml配置文件,配置无参构造的组件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="happyComponent" class="com.ztong.ioc_01.HappyComponent"/>
</beans>
-
id 表示组件的标识名
-
class 是 组件的类的全限定符
默认是单例模式,如果class 是相同的两个组件,那么就会实例化两个组件对象
1.2 静态工厂方法实例化
先来一个工厂类
package com.ztong.ioc_01;
public class ClientService {
private static ClientService service = new ClientService();
private ClientService(){
}
public static ClientService createInstance(){
return service;
}
}
在xml文件中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 无参构造-->
<bean id="happyComponent" class="com.ztong.ioc_01.HappyComponent"/>
<!-- 静态工厂-->
<bean id="clientService" class="com.ztong.ioc_01.ClientService" factory-method="createInstance"/>
</beans>
静态工厂的bean 标签属性含义
-
id 组件标识名
-
class =“工厂类的全限定符”
-
factory-method = "静态工厂方法"
1.3 非静态工厂方法实例化(实例工厂)
先准备一个工厂类,里面的方法是非静态的
package com.ztong.ioc_01;
public class DefaultServiceLocator {
private static ClientServiceImpl clientService = new ClientServiceImpl();
public ClientServiceImpl createClientServiceImpl(){
return clientService;
}
}
在xml中,配置bean 需要两步
先配置工厂类的组件信息。因为非静态方法,需要实例化对象去调用
第二步是用 factory-bean 指定工厂类
factory-method 指定非静态方法
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 非静态工厂-->
<!-- 配置工厂类的组件信息-->
<bean id="defaultServiceLocator" class="com.ztong.ioc_01.DefaultServiceLocator"/>
<!-- 通过指定工厂对象和非静态方法 来配置组件信息-->
<bean id="clientService2" factory-bean="defaultServiceLocator" factory-method="createClientServiceImpl"/>
</beans>
2.组件依赖注入配置
通过配置文件实现IOC容器中 Bean之间的引用,或者说给类的属性赋值
这里由两种方式:一是通过构造函数,二是通过setter方法
2.1 基于构造函数的依赖注入
首先准备两个类UserService 和 UserDao,在UserService中用到了UserDao,所以要给UserService注入UserDao
public class UserDao {
}
public class UserService {
private UserDao userDao;
private String name;
public UserService(String name, UserDao userDao){
this.name = name;
this.userDao = userDao;
}
}
首先需要先声明这两个bean,在声明UserDao的时候用的是自闭和标签,在声明UserService时,标签内要使用 constructor-arg 标签来注入 属性 name 和 UserDao
constructor-arg 标签的属性
-
name 就是要注入的属性的名字
-
value 是 要注入普通属性的值
-
ref 是要注入的对象属性的 bean id
-
index 是要注入的属性的索引
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.ztong.ioc_02.UserDao"/>
<bean id="userService" class="com.ztong.ioc_02.UserService">
<constructor-arg name="name" value="wzt"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
</beans>
构造方法只有一个参数时,可以只有 value 或 ref
当有多个参数,只写value 或 ref ,要按照参数的顺序进行注入
如果不按顺序,就可以用name 或 index 来指定注入的是哪一个属性
2.2 基于setter 方法的依赖注入
同样准备两个类,MovieFinder 和 SimpleMovieLister,在后者中注入前者,通过 setter方法
package com.ztong.ioc_02;
public class MovieFinder {
}
public class SimpleMovieLister {
private MovieFinder movieFinder;
private String movieName;
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void setMovieName(String movieName) {
this.movieName = movieName;
}
}
同样还是先声明bean,不同的是在SimpleMovieLister的bean中,用的标签是property
property的属性
-
name 去掉set后的方法名(注意首字母要小写)
-
ref
-
value
<bean id="simpleMovieLister" class="com.ztong.ioc_02.SimpleMovieLister">
<property name="movieFinder" ref="movieFinder"/>
<property name="movieName" value="jlczzwc"/>
</bean>
3 .IOC容器的创建
先准备一个类,并把这个类注册到 ioc容器中
package com.ztong.ioc_03;
public class SadComponent {
public void dowork(){
System.out.println("I am sad");
}
}
这里使用无参构造来注册
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="sadComponent" class="com.ztong.ioc_03.SadComponent"/>
</beans>
创建IOC容器可以直接通过实例化四个IOC容器实现类即可
这里选用的是 ClassPathXmlApplicationContext,有两种方式:
-
直接在创建的时候传入要读取的配置文件
-
先实例化对象,再传入配置文件,再刷新
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringIocTest {
public void creareIOC(){
//方式一:直接传入配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-03.xml");
//方式二:先创建,再传配置文件,再刷新
ClassPathXmlApplicationContext applicationContext1 = new ClassPathXmlApplicationContext();
//传入配置文件用setConfigLocations方法
applicationContext1.setConfigLocations("spring-03.xml");
//刷新
applicationContext1.refresh();
}
}
注意:刷新是必不可少的,因为方式一中在源码里也进行了刷新
创建完ioc后,来获取bean组件,也有三种方式,都是调用 ioc容器的getBean方法
-
直接传入 bean 的id
-
传入bean 的id 和 bean 的类型
-
传入bean的类型
public void creareIOC(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-03.xml");
//方式一:传入beanid,返回的类型是 Object,需要强制类型转换
SadComponent sadComponent = (SadComponent) applicationContext.getBean("sadComponent");
//方式二:传入beanid 和bean的类型,直接返回对应的类型
SadComponent sadComponent1 = applicationContext.getBean("sadComponent", SadComponent.class);
//方式三:传入bean的类型
SadComponent sadComponent2 = applicationContext.getBean(SadComponent.class);
}
需要注意的是方式三,必须确保ioc容器内只能有一个该类型的bean,否则就会报
NoUniqueBeanDefinitionException 一场
方式二和方式三中,也可以传入该类型实现的接口,但是在xml文件里只能是类
在底层是通过 instanceOf来判断传入的接口和配置的类的关系
4 .组件的生命周期方法及作用域
4.1生命周期方法
我们可以在组件类中定义方法,当ioc容器实例化和销毁的时候调用这两个方法。那么这两个方法就类似于生命周期方法。可以在这两个方法中完成一些初始化和释放资源的操作。
首先定义一个组件类,和两个方法
这两个方法必须是 public 并且返回值是void
package com.ztong.ioc_04;
public class JavaBean {
//初始化方法
public void start(){
System.out.println("JavaBean.start");
}
//销毁方法
public void end(){
System.out.println("JavaBean.end");
}
}
在ioc容器中注册bean的时候,在标签中加入如下属性
-
init-method 注册初始化方法,值是方法名
-
destroy-mothod 注册销毁方法,值是方法名
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="javaBean" class="com.ztong.ioc_04.JavaBean" init-method="start" destroy-method="end"/>
</beans>
需要注意当ioc容器销毁时才会调用 销毁方法,所以要主动调用ioc容器的close方法
4.2作用域
当在bean标签中声明组件时,是将组件的信息配置给Spring-IOC容器。
在IoC容器中,这些bean标签对应的信息转成Spring内部的 BeanDefinition对象,在这个对象内,包含传进来的这些信息(id,class,init-method等)
SpringIOC容器根据BigDefinition对象反射创建Bean的对象实例
在传入 BigDefinition 的属性中 有一个属性 scope 能够决定去创建几个 bean对象
在ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext 中 Scope有两个值
-
singleton 创建的bean对象为单例,是在IOC容器初始化时创建
-
prototype 创建的bean对象为多例,是在获取bean时创建
在 WebApplicationContext 中还会有另外两个值
-
request 请求范围内有效的实例,每次请求时创建
-
session 会话范围内有效的实例,每次会话时创建
默认值是 singleton
<bean id="javaScope" class="com.ztong.ioc_04.JavaScope" scope="prototype"/>
<!--默认值是 singleton-->
5 .FactoryBean
在创建bean对象时,有一种方式是 通过工厂模式创建,在工厂类中,写一个工厂方法返回要创建的对象。在ioc容器中,去创建工厂类的bean就行了,factory-method的值是工厂方法。
FactoryBean 是一个标准化工厂组件接口,用于配置复杂的bean对象,可以将创建过程存储在 getObject方法中,不用去写工厂方法和 factory-method。
FactoryBean<T> 提供三个方法
-
T getObject() 返回该工厂创建对象的实例,该返回值会被储存到IOC容器,我们可以在这个方法中进行一些复杂的操作
-
boolean isSingleton() 如果FactoryBean返回的是单例,该方法返回true,否则返回false
-
Class<?> getObjectType 返回getObject方法返回的对象类型,如果不知道类型,则返回null
FactoryBean的使用场景是:创建代理类、整合第三方框架、实例化复杂对象等
例如Mybatis框架
正常创建流程是:
-
先 创建SqlSessionFactoryBuilder 对象
-
调用getResourceAsStream() 方法读取配置文件
-
调用SqlSessionFactoryBuilder 对象的 builder方法,获得SqlSeqqsionFactory对象
-
调用SqlSeqqsionFactory 的 openSession 方法,获得 SqlSession对象
MyBatis 整合到spring有一个类是SqlSessionFactoryBean,这个类实现了FactoryBean接口,把这些步骤封装到 getObject,我们在springioc配置文件中,只需要配置 SqlSessionFactoryBean,就能拿到想要的SqlSession对象
下面看看如何使用FactoryBean
首先准备一个要创建的类
package com.ztone.ioc_05;
public class LearnComponent{
private String name;
public void setName(String name) {
this.name = name;
}
}
然后创建FactoryBean实现类,需要实现FactoryBean接口,并且传入要创建类的泛型,重写两个方法
package com.ztone.ioc_05;
import org.springframework.beans.factory.FactoryBean;
public class MyFactoryBean implements FactoryBean<LearnComponent> {
@Override
public LearnComponent getObject() throws Exception {
//可以用new的方式创建对象,返回该对象
LearnComponent learnComponent = new LearnComponent();
return learnComponent;
}
@Override
public Class<?> getObjectType() {
//返回要创建类的类型
return LearnComponent.class;
}
}
在配置文件中,创建配置MyFactoryBean 即可
<bean id="learnComponent" class="com.ztone.ioc_05.MyFactoryBean"/>
注意点:id是要创建的类的id,即LearnComponent,而MyFactoryBean的id 是 &id
在这个标签中 用property 给属性赋值时,是给MyFactoryBean的属性赋值,如果想给LearnComponent的属性赋值,可以在MyFactoryBean的getObject方法中,调用 对应属性的set方法,把bean中获得的值传入
FactoryBean 和 BeanFactory的区别:
-
FactoryBean 是Spring中的一种特殊的bean,可以在getObject() 工厂方法自定义的逻辑创建Bean,是一种能够生产其他bean的bean
-
BeanFactory 是Spring框架的基础,它是顶级接口,定义了容器的基本行为,例如管理bean的生命周期,配置文件的加载解析,bean的装配和依赖注入等。
6. Spring 三层架构案例
-
导入依赖,mysql、druid、spring-jdbc
<!-- 数据库驱动和连接池--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <!-- spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.6</version> </dependency>
-
准备实体类Student
package com.ztone.pojo; public class Student { private Integer id; private String name; private String gender; private Integer age; private String classes; public Integer getId() {return id;} public void setId(Integer id) {this.id = id;} public String getName() {return name;} public void setName(String name) { this.name = name;} public String getGender() {return gender;} public void setGender(String gender) { this.gender = gender;} public Integer getAge() { return age;} public void setAge(Integer age) { this.age = age;} public String getClasses() { return classes;} public void setClasses(String classes) {this.classes = classes; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", gender='" + gender + '\'' + ", age=" + age + ", classes='" + classes + '\'' + '}'; } }
-
准备controller,service,dao层
package com.ztone.controller; import com.ztone.pojo.Student; import com.ztone.service.StudentService; import java.util.List; public class StudentController { private StudentService studentService; public void setStudentService(StudentService studentService) { this.studentService = studentService; } public void searchStudent(){ List<Student> students = studentService.queryAllStudent(); System.out.println(students); } }
package com.ztone.service; import com.ztone.pojo.Student; import java.util.List; public interface StudentService { List<Student> queryAllStudent(); } ---------------------------------------- package com.ztone.service; import com.ztone.dao.StudentDao; import com.ztone.pojo.Student; import java.util.List; public class StudentServiceImpl implements StudentService{ private StudentDao studentDao; @Override public List<Student> queryAllStudent() { return studentDao.queryAllStudent(); } public void setStudentDao(StudentDao studentDao) { this.studentDao = studentDao; } }
package com.ztone.dao; import com.ztone.pojo.Student; import java.util.List; public interface StudentDao { List<Student> queryAllStudent(); } ---------------------------------------- package com.ztone.dao; import com.ztone.pojo.Student; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import java.util.List; public class StudentDaoImpl implements StudentDao{ private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public List<Student> queryAllStudent() { String sql = "select id,name,gender,age,class as classes from students"; List<Student> students = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class)); return students; } }
在StudentDaoImpl中,调用JdbcTemplate的query方法去执行sql语句,返回一个集合,然后在service中调用dao,在controller中调用service
-
在xml中配置bean
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--导入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置数据连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${url}"/> <property name="driverClassName" value="${driver}"/> <property name="username" value="${username1}"/> <property name="password" value="${password}"/> </bean> <!-- 配置jdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置三层架构--> <bean id="studentDao" class="com.ztone.dao.StudentDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <bean id="studentService" class="com.ztone.service.StudentServiceImpl"> <property name="studentDao" ref="studentDao"/> </bean> <bean id="studentController" class="com.ztone.controller.StudentController"> <property name="studentService" ref="studentService"/> </bean> </beans>
遇到的问题:
-
配置dataSource的 driver,name只能是 driverClassName
-
配置dataSource的 username时,value的名字不能是 username,因为username是spring的key中的一个关键字,在使用 username时,就是调用了这个关键字,而不是读取jdbc.properties文件中的username属性值
-
配置三层架构的bean时,property标签的name属性必须是 对应的set方法的方法名
-