容器
IoC容器概述
控制反转
这是一种编程思想,指将对象的实例化,初始化权限以及控制对象之间的依赖关系交予第三方容器
在spring项目中,我们将java对象交予spring进行管理
我们通过在配置文件(或利用注解)的方式为spring标记了所需要加入IoC容器的类信息;spring通过对我们所标注的信息进行扫描解析获得关于类的定义,至此就完成了控制反转的操作,当我们需要使用某个类时,spring就会帮助我们利用之前得到的Bean定义的信息来完成对象的实例化,初始化等步骤,返回一个可以直接使用的对象。依赖注入
依赖注入实现了控制反转的思想
它指在spring创建对象的过程中,将对象依赖属性通过配置注入到实例中
我们可以通过set方法还有构造函数进行参数注入
基于XML管理Bean
实验环境
Spring项目配置文件
<!-- pom.xml配置文件 -->
<dependencies>
<!-- spring context依赖-->
<!-- 当引入Spring Context依赖之后,表示将Spring的基础依赖都引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>
<!--junit相关依赖 -->
<!-- 可以使用@Test注解进行测试 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.6.3</version>
</dependency>
</dependencies>
IoC容器配置文件
<!-- application.xml配置文件 -->
<!-- 使用构造函数进行参数注入 -->
<!-- <bean id="user" class="com.atli.pojo.User">
<constructor-arg name="name" value="li"></constructor-arg>
<constructor-arg name="age" value="12"></constructor-arg>
</bean> -->
<!-- 使用set方法进行参数注入 -->
<bean id="user" class="com.atli.pojo.User">
<property name="name" value="li"></property>
<property name="age" value="12"></property>
</bean>
User类
package com.atli.pojo;
// 这里使用了lombok
@Data
public class User {
private int age;
private String name;
// public User(String name, int age) {
// this.name = name;
// this.age = age;
// }
public void run(){
System.out.println("run");
}
}
测试类
public class UserTest {
@Test
public void test(){
//加载spring配置文件,对象创建
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
//通过id获取对象
User user = (User) context.getBean("user");
// 通过类型获取对象
// User user = (User) context.getBean("user");
// 通过类型和id获取对象
// User user = (User) context.getBean("user",User.class);
//使用对象调用方法
user.run();
System.out.println(user.name);
System.out.println(user.age);
}
}
获取Bean
根据id获取
由于id属性指定了bean的唯一信息,故适应bean标签的id属性可以精确指定目标对象
根据类型获取对象
对于是唯一类型的bean而言,使用该方法也能准确定位到目标对象,但是当出现了多个同类型的bean,则无法利用类型进行唯一性对象确定(列如使用父类|接口对其多个子类|实现类,进行Bean获取,在配置bean时使用了不同id对同一类进行定义)
对于bean的获取,关键在于要能够唯一的定位到某个具体的bean,不能有歧义
依赖注入
基本类型属性
使用set方法注入
语法:<property name="" value="" />
使用构造器注入
语法:<constructor-arg name="" value="" />
使用该方法必须要在类中编写构造器,若有多个构造器则spring会根据参数列表进行注入
特殊值处理
(1)null值
<property name="name">
<null></null>
</property>
(2)xml实体(可以看作是转义符)
-
xml也同java,c一样有一些保留符号,这些称为xml实体
-
对于xml实体,我们可以用转义字符来表示
' 是一个撇号:' & 是一个与字符:& " 是一个引号:" < 是一个小于号:< > 是一个大于号:>
<!-- 这里我需要给expression属性赋值为'a<b' -->
<property name="expression" value="a $lt; b"></property>
(3)CDATA节
在CDATA标记下,所有的标记、实体引用都会被忽略,而被当作字符数据
语法:<![CDATA[忽略检查的文本]>
<!-- 这里以CDATA节的方法实现上一个例子 -->
<property name="expression" >
<value><![CDATA[a<b]]></value>
</property>
特殊类型属性
前期准备
创建一个employee类作为department类的属性
// cat类,是employee类的map属性
@Data
public class Cat {
private String name;
public void speck(){
System.out.println(this.name+" speck");
}
}
// employee类,是Department类的list属性
@Data
public class Employee {
private String name;
private String[] hobbies;
private Map cat;
}
// department类
@Data
public class Department {
private String departmentName;
private List<Employee> employees;
}
<!-- 配置文件 -->
<!-- 准备好两个cat类的bean作为employee的属性 -->
<bean id="catOne" class="com.atli.pojo.Cat">
<!-- 注入普通属性 -->
<property name="name" value="xiaohua"></property>
</bean>
<bean id="catTow" class="com.atli.pojo.Cat">
<!-- 注入普通属性 -->
<property name="name" value="xiaohei"></property>
</bean>
<!-- 将两个catBean组装成一个map,供employee使用 -->
<util:map id="cats">
<entry key="1" value-ref="catOne"></entry>
<entry key="2" value-ref="catTow"></entry>
</util:map>
<!-- 第一个employeeBean -->
<bean id="employeeone" class="com.atli.pojo.Employee">
<property name="name" value="wang"></property>
<property name="hobbies">
<!-- 创建一个array并赋给hobbies -->
<array>
<value>"suijiao"</value>
<value>"chifan"</value>
<value>"dadoudou"</value>
</array>
</property>
<!-- 将cats的map赋值给cat -->
<property name="cat" ref="cats"></property>
</bean>
<!-- 第二个employeeBean -->
<bean id="employeeTow" class="com.atli.pojo.Employee">
<property name="name" value="zhang"></property>
<property name="hobbies">
<array>
<value>"xaing"</value>
<value>"yu"</value>
<value>"fan"</value>
</array>
</property>
<property name="cat" ref="cat"></property>
</bean>
<!-- 将两个employeeBean组装成一个list -->
<util:list id="employees">
<ref bean="employeeone"></ref>
<ref bean="employeeTow"></ref>
</util:list>
<!-- departmentBean -->
<bean id="department" class="com.atli.pojo.Department">
<property name="departmentName" value="kaifa"></property>
<property name="employees" ref="employees"></property>
</bean>
// 测试
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("application-di.xml");
// 获取Department实例
Department department = context.getBean("department", Department.class);
String departmentName = department.getDepartmentName();
// 获取department中所有的Employee,并封装在employees的list中
List<Employee> employees = department.getEmployees();
// 遍历employees的list
for (Employee employee : employees) {
String employeeName = employee.getName();
String[] hobbies = employee.getHobbies();
// 将employee的cat map封装到名为cats的map中
Map cats = employee.getCat();
System.out.printf(departmentName+":"+employeeName+":"+hobbies);
// 遍历cats中的每个key(编号),value(cat)
cats.forEach((k, v) -> {
System.out.print(k);
//获取cats中的单个cat对象
//注意,这里是不能直接将v当作是cat来对待,因为编译器无法确定类型。
Cat cat = (Cat) v;
// 调用cat的speck方法
cat.speck();
});
System.out.println();
}
}
对象类型
有三种方式,分别是引用外部bean、内部bean、级联属性赋值
- 引用外部bean
- 将属性类也通过
<bean>
标签声明到ioc容器中,再通过ref对该类进行引用
- 将属性类也通过
- 内部bean
- 直接将一个bean声明到另一个bean标签中
- 级联属性赋值
- 当将一个bean声明为另一个bean的属性时,可以直接用id.属性名进行赋值
<!-- 引用外部bean -->
<bean id="employee" class="com.atli.pojo.Employee">
<property name="name" value="xiang"></property>
</bean>
<bean id="department" class="com.atli.pojo.Department">
<property name="employee" ref="employee"></property>
</bean>
<!-- 内部bean -->
<!-- <bean id="department" class="com.atli.pojo.Department">
<property name="employee" ref="employee"></property>
<bean id="employee" class="com.atli.pojo.Employee">
<property name="name" value="xiang"></property>
</bean>
</bean> -->
<!-- 级联赋值 -->
<!-- <bean id="employee" class="com.atli.pojo.Employee">
<property name="name" value="xiang"></property>
</bean>
<bean id="department" class="com.atli.pojo.Department">
<property name="employee" ref="employee"></property>
<property name="employee.name" ref="yu"></property>
</bean> -->
这里关于对象类型的引入,本质上是需要在目标对象中引入其他对象的引用,即使用ref指定id来绑定引用
数组类型
语法
<bean id="" class="">
<property name="">
<array>
<value></value>
<value></value>
<value></value>
</array>
</property>
</bean>
List类型
这里要注意对于util命名空间的导入
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
语法
<util:list>
<!-- 引用类型 -->
<ref bean=""></ref>
<!-- 数值类型 -->
<!-- <value></value> -->
</util:list>
Map类型
这里同样的也需要util命名空间
语法
<util:map>
<!-- value为直接值 -->
<entry key="" value=""></entry>
<!-- value为对象 -->
<entry key="" value-ref=""></entry>
</util:map>
引用集合类型的bean
其实上述的list和map集合都是先创建了集合类型的bean再进行引入的
如果不创建集合bean又要对对应属性赋值的话,可以使用内部bean的写法
例子
<bean id="department" class="com.atli.pojo.Department">
<property name="departmentName" value="kaifa"></property>
<property name="employees" >
<list>
<ref bean="employeeone"></ref>
<ref bean="employeeTow"></ref>
</list>
</property>
</bean>
p命名空间注入
这里我以上面的department的bean来举例
<!-- 加入p命名空间 -->
xmlns:p="http://www.springframework.org/schema/p"
<!-- <bean id="department" class="com.atli.pojo.Department">
<property name="departmentName" value="kaifa"></property>
<property name="employees" ref="employees"></property>
</bean> -->
<!-- 通过在bean标签内,使用p:属性名|属性名-ref = "直接值|bean",来简化配置 -->
<bean id="department" class="com.atli.pojo.Department"
p:departmentName="kaifa" p:employees-ref="employees">
</bean>
引入外部属性文件
这里以使用mysql和druid连接池为例
<!-- pom.xml依赖配置 -->
<!--MYSQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<!-- 外部属性文件(数据库配置) -->
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/studb
jdbc.driver=com.mysql.cj.jdbc.Driver
<!-- 引入context的命名空间 -->
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 完成数据库信息注入 -->
<!-- 使用druid连接池需要new一个DruidDataSource对象,并提供数据库信息 -->
<!-- 而该类处于com.alibaba.druid.pool.DruidDataSource包下 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driver}"></property>
</bean>
bean作用域
bean的作用域实际上指的是bean实例的生命周期和可见范围
使用时只需要在bean标签中加上scope属性和对应值即可
singleton(单例):在整个应用中只存在一个bean实例,由Spring容器管理。默认作用域为singleton。
prototype(原型|多例):每次请求都会创建一个新的bean实例。
request(请求):每个HTTP请求都会创建一个新的bean实例,该作用域仅适用于WebApplicationContext环境。
session(会话):每个HTTP会话都会创建一个新的bean实例,该作用域同样仅适用于WebApplicationContext环境。
application(应用):整个Web应用中共享一个bean实例。
websocket(WebSocket):每个WebSocket连接创建一个新的bean实例,仅适用于WebSocket应用。
bean生命周期
- bean对象创建(调用无参构造器)
- 给bean对象设置属性
- bean的后置处理器(初始化之前)
- bean对象初始化(需在配置bean时指定初始化办法)
- bean的后置处理器(初始化之后)
- bean对象就绪可以使用
- bean对象销毁(需要在配置bean时指定销毁方法)
- IoC容器关闭
后置处理器
使用后置处理器需要继承BeanPostProcessor方法
当使用了后置处理器后,会对IoC容器中的所有Bean生效
public class MyBeanProcessor implements BeanPostProcessor{
// 初始化前的后置处理器
@Override
public Object postProcessBeforeInitialization(Object bean,String beanName){
// 需要进行的逻辑操作
return bean;
}
// 初始化后的后置处理器
@Override
public Object postProcessAfterInitialization(Object bean,String beanName){
// 需要进行的逻辑操作
return bean;
}
}
<!-- 将后置处理器装入IoC容器 -->
<bean id="myBeanProcessor" class = "com.atli.spring6.MyBeanProcessor">
FactoryBean
FactoryBean是Spring提供的整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean是得到的不是class属性中配置的这个类的对象而是getObject()方法的返回值。
// User类
public class User {
public void add(){
System.out.println("add");
}
}
// UserFactory类
public class UserFactory implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return null;
}
}
<!-- 配置文件 -->
<bean id="users" class="com.atli.spring6.UserFactory" ></bean>
基于xml自动装配
这里,我模拟三层架构,将dao注入到service,再将service注入到controller
// Dao
public class UserDao {
public void work(){
System.out.println("work");
}
}
// service
public class UserService {
private UserDao userDao;
public void work(){
userDao.work()
}
}
// controller
public class UserController {
private UserService userService;
public void work(){
userService.work();
}
}
<!-- 配置文件 -->
<bean id="userDao" class="com.atli.dao.UserDao" autowire="byType"></bean>
<bean id="userService" class="com.atli.service.UserService" autowire="byType"></bean>
<bean id="userController" class="com.atli.controller.UserController" autowire="byType"></bean>
这里我使用autowire="byType"
来进行指定,因为此处的这三个类的类型是唯一的,如果使用byName来指定则要注意名称的唯一性,并且id名要和java代码声明的变量名相同
基于注解管理Bean
实例1
这里模拟三层架构的调用
// Dao
@Repository
public class UserDao {
public void work(){
System.out.println("work");
}
}
// service
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void work(){
userDao.work()
}
}
// controller
@Controller
public class UserController {
@Autowired
private UserService userService;
public void work(){
userService.work();
}
}
例子2
使用配置类配置将Druid连接池对象存储到IoC容器
//标注当前类是配置类,替代application.xml
@Configuration
//引入jdbc.properties文件
@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})
// 配置扫描路径
@ComponentScan(basePackages = {"com.atli.components"})
public class MyConfiguration {
//如果第三方类进行IoC管理,无法直接使用@Component相关注解
//解决方案: xml方式可以使用<bean标签
//解决方案: 配置类方式,可以使用方法返回值+@Bean注解
@Bean
public DataSource createDataSource(@Value("${jdbc.user}") String username,
@Value("${jdbc.password}")String password,
@Value("${jdbc.url}")String url,
@Value("${jdbc.driver}")String driverClassName){
//使用Java代码实例化
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setUrl(url);
dataSource.setDriverClassName(driverClassName);
//返回结果即可
return dataSource;
}
}
// 配置类
//这是全注解开发不用xml
@Configuration//标记配置类
@ComponentScan("com.atli")//标记包扫描
public class SpringConfig {
}
bean的标记
spring提供了四种标记bean的注解,这些注解都基于@Component
,用这些注解标记的bean就相当于在xml文件中配置了<bean>
标签
在这些标记中可以添加value属性来指定bean的名称,默认为类名且首字母小写
@Component
- 用于标记普通bean
@Repository
- 用于标记Dao层bean
@Service
- 用于标记Service层bean
@Controller
- 用于标记Controller层bean
bean扫描
在xml文件中指定扫描配置可以指定包,也可以指定排除包中的某些类
开启关于注解的bean扫描需要context命名空间
<!-- 基本扫描 -->
<context:component-scan base-package="" />
<!-- 排除某个包 -->
<!-- type指定排除规则,expression指定表达式 -->
<context:exclude-filter type="" expression="" />
<!-- 扩展扫描组件 -->
<context:include-filter type="" expression="" />
@Autowired
注解
使用该注解时,对于注入bean最好使用该bean的接口,而不是实现类,否则可能会出现无法确定注入某一个实例
使用该注解可以实现bean的自动装配,默认情况下是通过类名进行匹配
若存在多例,需要通过id名称进行装配则需要加上@Qualifier
注解
该注解可以在成员变量,构造方法,普通方法形参,set方法使用
对于自动装配,实际上就是spring根据在ioc容器中的类型或者id来对进行标记的成员进行属性注入
@Resource
注解
和@Autowired
类似,该注解默认情况下是通过id进行匹配
@Value
注解
该注解用于读取外部配置文件的值
@Value("${key}")
完全注解开发(配置类)
通过在java程序中创建配置类可以做到完全注解开发
@Configuration
指定一个类为配置类,可以添加配置注解,代替配置xml文件@ComponentScan(basePackages={"包"})
替代<context:component-scan >
实现注解扫描@PropertySource("classpath:配置文件地址")
代替<context:property-placeholder>
标签实现读取外部配置文件