容器概述
ApplicationContext是Spring IoC容器实现的代表,它负责实例化,配置和组装Bean。容器通过读取配置元数据获取有关实例化、配置和组装哪些对象的说明 。配置元数据可以使用XML、Java注解或Java代码来呈现。它允许你处理应用程序的对象与其他对象之间的互相依赖关系。
配置元数据
使用xml的配置
简单、直观 适合入门
基于注解的配置: @Compont(@serivce @controller @repository) @Autowride
Spring 2.5 支持基于注解的元数据配置. SSM框架开发中的使用
基于Java的配置:
@Confiration @Bean @Import
从 Spring 3.0开始, 由Spring JavaConfig项目提供的功能已经成为Spring核心框架的一部分。因此,你可以使用Java配置来代替XML配置定义外部bean
从spring4.0开始支持springboot1.0 之后 springboot完全采用javaConfig的方式进行开发。
容器的实例化
对象在Spring容器创建完成的时候就已经创建完成,不是需要用的时候才创建(类似设计模式中的饿汉模式)
首先在spring-ioc.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.jony.beans.User" id="user"></bean>
</beans>
复制代码
其次在代码中加载spring-ioc.xml
// 加载spring容器
//ApplicationContext spring的顶层核心接口
// ClassPathXmlApplicationContext 根据项目路径的xml配置来实例化spring容器
// FileSystemXmlApplicationContext 根据磁盘路径的xml配置来实例化spring容器
// AnnotationConfigApplicationContext 根据javaconfig 来配置实例化spring容器
// 在容器实例化的时候 就会加载所有的bean
ApplicationContext ioc=new ClassPathXmlApplicationContext("spring-ioc.xml");
复制代码
编写测试代码
在测试代码中,我们可以以三种方式使用spring ioc帮我们注入的Bean对象。
package com.jont.ioc;
import com.jony.beans.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class IocTest {
@Test
public void test01(){
// 加载spring容器
//ApplicationContext spring的顶层核心接口
// ClassPathXmlApplicationContext 根据项目路径的xml配置来实例化spring容器
// FileSystemXmlApplicationContext 根据磁盘路径的xml配置来实例化spring容器
// AnnotationConfigApplicationContext 根据javaconfig 来配置实例化spring容器
// 在容器实例化的时候 就会加载所有的bean
ApplicationContext ioc=new ClassPathXmlApplicationContext("spring-ioc.xml");
//通过发射机制拿到User bean
User bean = ioc.getBean(User.class);
//通过id获得bean
User bean1= (User) ioc.getBean("user");
//通过id+类型获得bean
User bean2=ioc.getBean("user",User.class);
System.out.println(bean);
}
}
复制代码
spring配置文件的配置
按住ctrl进入查看beans
bean的概述
通过下图可以看到,beans中我们可以使用如下三种标签
1、bean:为导入一个类,让spring帮我们注入
同时也可以使用name来定义别名,如下:
<bean class="com.jony.beans.User" id="user" name="user2 user3,user4;user5"></bean>
复制代码
name之间的可以使用空格、逗号,分号来进行分割
2、import:假如有多个xml,可以使用import导入一起加载,如下就可以让spring-ioc2.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 class="com.jony.beans.User" id="user"></bean>
<import resource="spring-ioc2.xml"></import>
</beans>
复制代码
3、alias 别名,我们可以将已经定义好的bean,设置一个别名,如下
在代码中使用别名同样也可以使用注入的bean,同时原来的名字也可以使用的
依赖注入
基于setter方法的依赖注入
xml配置
<!--基于setter方法的依赖注入
1. 属性必须声明了set方法
2. name是根据set方法的名字来的 比如方法名字是: setIdxx ‐> name="idxx"
-->
<bean class="com.jony.beans.User" id="userioc">
<property name="id" value="1"></property>
<property name="userName" value="zhangsan"></property>
<property name="realName" value="张三"></property>
</bean>
复制代码
java代码测试
基于构造函数的依赖注入
将会调用自定义构造函数来实例化对象,就不会调用默认的无参构造函数
1、根据属性name注入
xml配置
java代码测试
2、根据下标位置注入
通过上图,我们可以发现,即使不填写name属性,bean的内容也是可以注入的,需要注意的是,顺序需要和构造方法的顺序一样才可以,否则就会匹配错误,如下:
3、设置index下标纠正错误
此处是根据构造函数的顺序进行注入的,按照顺序既可以成功正确注入
4、设置属性的type
设置type属性也可以进行参数设置,但是如果参数类型一致,就无法精准的匹配了。此处就不做演示了
总结:在注入的时候,最好还是使用name属性进行注入,这样即使顺序错误,也不用担心注入错误。
复杂数据依赖注入
准备基础类
创建含有不同类型的Person类
package com.jony.beans;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class Person {
private Integer id;
private String name;
private String gender;
private Date birthday;
private List<String> hobbies;
private Map<Integer,String> course;
private Wife wife;
public Person() {
}
public Person(Integer id, String name, String gender, Date birthday, List<String> hobbies, Map<Integer, String> course, Wife wife) {
this.id = id;
this.name = name;
this.gender = gender;
this.birthday = birthday;
this.hobbies = hobbies;
this.course = course;
this.wife = wife;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + ''' +
", gender='" + gender + ''' +
", birthday=" + birthday +
", hobbies=" + hobbies +
", course=" + course +
", wife=" + wife +
'}';
}
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 Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public Map<Integer, String> getCourse() {
return course;
}
public void setCourse(Map<Integer, String> course) {
this.course = course;
}
public Wife getWife() {
return wife;
}
public void setWife(Wife wife) {
this.wife = wife;
}
}
复制代码
创建Wife类
package com.jony.beans;
public class Wife {
private Integer age;
private String name;
@Override
public String toString() {
return "Wife{" +
"age=" + age +
", name='" + name + ''' +
'}';
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Wife() {
}
public Wife(Integer age, String name) {
this.age = age;
this.name = name;
}
}
复制代码
1、引入外部bean
xml配置
在应用类中设置ref,指向被引用类的id
<bean class="com.jony.beans.Person" id="person">
<property name="id" value="1"></property>
<!--null值-->
<property name="name">
<null></null>
</property>
<property name="gender" value=""></property>
<!--使用ref 引入外部bean-->
<property name="wife" ref="wife"></property>
</bean>
<bean class="com.jony.beans.Wife" id="wife">
<property name="name" value="迪丽热巴"></property>
<property name="age" value="24"></property>
</bean>
复制代码
java代码及执行结果
可以看到Person通过ref引用的外部类可以成功注入
2、引入内部bean
xml配置
我们直接在,bean中设置wife,然后引用class地址
<bean class="com.jony.beans.Person" id="person">
<property name="id" value="1"></property>
<!--null值-->
<property name="name">
<null></null>
</property>
<property name="gender" value=""></property>
<!--使用ref 引入外部bean-->
<property name="wife">
<bean class="com.jony.beans.Wife">
<property name="name" value="迪丽热巴"></property>
<property name="age" value="24"></property>
</bean>
</property>
</bean>
复制代码
3、list值
xml配置
给Person设置爱好,下面设置的是String值,当然也可以设置其他类型
<bean class="com.jony.beans.Person" id="person">
<property name="id" value="1"></property>
<!--null值-->
<property name="name">
<null></null>
</property>
<property name="gender" value=""></property>
<!--使用ref 引入外部bean-->
<property name="wife" ref="wife"></property>
<!--如果是基本数据类型,用value
如果是bean 对象,则用<bean>
List的泛型是比如:List<Wife> <bean>-->
<property name="hobbies">
<list>
<value>唱歌</value>
<value>跳舞</value>
</list>
</property>
</bean>
复制代码
java测试代码及结果
4、map值
xml代码
<!--map设置
可以直接设置key 以及value的值,同时也可以设置key-ref / value-ref
如果map value为object,我们就尅设置value-ref 为 wife-->
<property name="course">
<map>
<entry key="1" value="Java"></entry>
<entry key="2" value="Python"></entry>
<entry key="3" value-ref="wife"></entry>
</map>
</property>
复制代码
执行结果
5、使用p命名空间
可以使用p命名空间来简化基于setter属性注入 它不支持集合
xml配置如下
此时我没发现p命令为标红状态,我们只需要在p标签那 Alt+enter即可导入包
可以看到xml头部引入内如如下
然后我们把刚刚的课程3 value-ref设置为wife2,如下:
java 执行结果
3、使用c命名空间
xml配置
注意,与p命名空间不一样的是,c命名空间必须是构造函数
<!--使用c命名空间简化基于构造函数的xml-->
<bean class="com.jony.beans.Wife" id="wife3" c:age="23" c:name="一个美女"></bean>
复制代码
高级功能
1、depends-on属性
spring-ioc.xml中,默认加载顺序为从上到下进行顺序加载,通过使用depends-on属性,可以设置优先加载的bean,也就是控制bean的加载顺序
xml配置
java执行结果
可以看到,因为设置了depends-on=”wife”,因此wife先加载,然后再加载person。
2、懒加载 lazy-init
通过设置lazy-init 可以将bean设置为懒加载,只有使用的时候才会加载。
xml配置
<bean class="com.jony.beans.User" id="user" lazy-init="true">
<property name="id" value="1"></property>
<property name="realName" value="张三"></property>
</bean>
复制代码
java测试结果
通过下图,我们可以看到,在执行test方法的时候,spring-ioc配置文件已经加载完毕,因为User 设置了lazy-init,因此并未加载,当我们使用User bean的时候才进行加载。
3、自动注入
当一个对象中需要引用另外一个对象的时候,在之前的配置中我们都是通过property标签来进行手动配置的,其实在spring中还提供了一个非常强大的功能就是自动装配,可以按照我们指定的规则进行配置,配置的方式有以下几种:
3-1、byType 根据类型自动注入
按照类型进行装配,以属性的类型作为查找依据去容器中找到这个组件,如果有多个类型相同的bean对象,那么会报异常,如果找不到则装配null
xml配置
可以看到我们在Person bean中添加了 autowrite=”byType”这样,Person中的属性就会根据类型进行自动注入
<bean class="com.jony.beans.Person" id="person" depends-on="wife" autowire="byType"></bean>
<bean class="com.jony.beans.Wife" id="wife">
<property name="age" value="24"></property>
<property name="name" value="大美女"></property>
</bean>
复制代码
测试结果
我们并没有在xml配置 person的wife,但是通过自动注入,spring就会帮我们自动根据类型进行注入。
我们再次修改xml如下:
因为有了两个Wife,Person 的autowrite就会报错,因为同一个类型出现多次,spring不知道我们到底要装配哪个bean。如果有这种情况发生,我们就可以使用byName来进行处理
3-2、 byName 根据set方法的名字自动匹配
比如我们Person 中wife的set方法为 setWife,名字即为wife
xml配置
测试结果
可以看到spirng在自动注入的时候选择的是name为wife的bean
如果我们把Person的setWife 改为 setWife2,会怎么样呢?来测试一下
xml如下
setwife改为setWife2
执行结果
已经由大美女变为了小美女
3-3、constructor 安装构造器进行匹配
先按照有参构造器参数的类型进行装配,没有就直接装配null;如果按照类型找到了多个,那么就使用参数名作为id继续匹配,找到就装配,找不到就装配null
xml配置
修改Person 的构造方法
测试结果
我们再次修改Person的构造方法,构造方法传入wife2
执行测试方法
总结
通过构造函数进行自动注入的时候,会根据类中的构造函数传入的参数名称去xml中去进行匹配,如上面构造我们先传入wife,再传入wife2,执行的结果就不一样。
注意:
1、constructor首先会根据构造函数中的参数名称去进行匹配,如果未匹配到,会再次根据byName进行匹配,如果还未匹配到,会再次根据byType进行匹配,如果还未匹配到,则为null
2、构造函数如果是多个参数也无法匹配到。如我们在Person的构造方法中添加一个参数
再次运行代码,Person中的wife就为null。这个主要是第二个参数需要在IOC容器中存在才可以进行自动注入,假如我们再次修改构造函数如下:
3、我们将第二个参数改为User,user bean也在xml文件中进行注入,这样就可以通过构造函数进行注入了,测试如下:
因此构造函数会根据参数进行完整匹配注入,如果构造函数中的参数,在IOC容器中不存在,则无法完成自动注入。
4、如果构造函数里面的参数,在容器中通过类型匹配到了多个bean,可以通过给bean设置primary=”true”来优先注入(byType也遵循这个原则,上面通过byType,如果匹配到多个类型,通过设置这个参数,也可以解决编译报错),如下:
给第一个wife bean 设置primart=true
Bean的作用域
1、Singleton 单例作用域
spring默认在注入bean的时候是单例模式,目的是同一个对象不要在内存中创建多次,节省系统资源。
测试代码如下
通过下图可以看到,我们通过getBean创降了两个User Bean,但是在User中的无参构造方法仅执行了一次,这就代表我们这个User Bean是单例的,只创建了一次。
2、Prototype(原型/多例)的作用域
xml配置
可以看到我们在bean中添加了scope=”prototype”
<bean class="com.jony.beans.User" id="user" lazy-init="true" scope="prototype">
<property name="id" value="1"></property>
<property name="realName" value="张三"></property>
</bean>
复制代码
测试执行结果
可以看到通过设置了原型作用域,我们在创建一个新的对象的时候,就会new 一个新的bean.
其他作用域
后面这四个作用域,由于需要用到web服务,后面再进行叙述。
大家如果有兴趣,可以去看一下原文:docs.spring.io/spring-fram…
bean的特性
bean的生命周期
1、使用实现接口的方式来实现什么周期的回调
-
初始化方法: 实现接口: InitializingBean 重写afterPropertiesSet方法初始化会自动调用的方法
-
销毁的方法: 实现接口: DisposableBean 重写destroy 方法 销毁的时候自动调用方法
2、使用指定具体方法的方式实现生命周期的回调:
在对应的bean里面创建对应的两个方法
- init‐method=”init” destroy‐method=”destroy”
xml配置
java方法
为了将接口和xml区分开,将接口实现都设置标记1,配置实现的标记为2
3、bean什么时候销毁
IOC初始化需要使用如下三种方式创建,如果使用顶层核心接口,没有colse方法。
ClassPathXmlApplicationContext 根据项目路径的xml配置来实例化spring容器
FileSystemXmlApplicationContext 根据磁盘路径的xml配置来实例化spring容器
AnnotationConfigApplicationContext 根据javaconfig 来配置实例化spring容器
复制代码
在spring容器关闭的时候 close()
测试结果
通过以上,我们可以看到不论创建还是销毁都是接口的方法先执行,这样如果我们需要监控bean创建销毁的时候,就可以通过这两种方式来处理了。
spring创建第三方bean对象
在Spring中,很多对象都是单实例的,在日常的开发中,我们经常需要使用某些外部的单实例对象,例如数据库连接池,下面我们来讲解下如何在spring中创建第三方bean实例。比如我们引入数据库连接至,如下
数据库连接池
1、导入数据库连接池的pom文件
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
复制代码
2、编写配置文件
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root"></property>
<property name="password" value="root"></property>
<property name="url" value="jdbc:mysql://localhost:3306/jony"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"> </property>
</bean>
复制代码
3、测试
@Test
public void test04(){
DruidDataSource dataSource=ioc.getBean("datasource",DruidDataSource.class);
System.out.println(dataSource);
System.out.println(dataSource.getConnectCount());
}
复制代码
执行结果
spring引入外部文件
在实际项目中,我们会把相关的资源配置信息,全部写到一个资源文件中,这样在有修改的时候,就不必再去java代码中去修改。
1、在resource中添加配置文件
比如我配置数据库连接信息的文件,在resource中添加dbconfig.properties
username=root
password=123456
url=jdbc:mysql://localhost:3306/demo
driverClassName=com.mysql.jdbc.Driver
复制代码
2、让spring在注入的时候读取配置文件
核心是需要使用如下配置导入我们需要读取的配置文件
然后再xml开头导入需要的命名空间
<context:property-placeholder location="classpath:dbconfig.properties"/>
复制代码
3、执行结果如下
spring SpEL的使用
SpEL:Spring Expression Language,spring的表达式语言,支持运行时查询操作对象使用#{…}作为语法规则,所有的大括号中的字符都认为是SpEL.
spring 给我们提供了如下可以使用的表达式
1、xml配置
<bean class="com.jony.beans.User" id="user2">
<!--支持任何运算符 加减乘除等-->
<property name="id" value="#{12*2}"></property>
<!--可以引用其他bean的某个属性值-->
<property name="userName" value="#{address.province}"></property>
<!--引用其他bean-->
<property name="realName" value="#{address}"></property>
<!--调用静态方法-->
<property name="hobbies" value="#{T(java.util.UUID).randomUUID().toString().substring(0,4)}"></property>
<!--调用非静态方法-->
<property name="gender" value="#{address.getCity()}"></property>
</bean>
复制代码
注意:如果使用静态方法需要使用 T 来代表,如上面的 hobbies引用UUID.