文章目录
- 一、IOC容器
- 1、控制反转(ioc)
- 2、依赖注入
- 3、IoC容器在Spring的实现
- 二、基于XML管理Bean
- 1、获取bean
- 方式一、根据id获取
- 方式二、根据类型获取
- 方式三、根据id和类型获取bean
- 2、依赖注入之setter注入
- 3、依赖注入之构造器注入
- 4、特殊值处理
- 5、为对象类型属性赋值
- 方式一:引用外部bean
- 方式二:内部bean
- 方式三:级联属性赋值
- 6、为数组类型属性赋值
- 7、为集合类型属性赋值
- 8、基于xml自动装配
- 三、基于注解管理Bean(☆)
- 1、配置类替换bean.xml
- 2、@Autowired注入
- 方式一:属性注入(`最常用`)
- 方式二、set注入
- 方式三、构造方法注入
- 方式四、形参注入
- 方式五、只有一个构造函数,通过构造器注入不需要注解
- 方式六、@Autowired注解和@Qualifier注解联合
- 3、@Resource注入
- 方式一、根据name注入
- 方式二、name未知注入
- 方式三、name找不到的情况
- 四、原理-手写IoC
Spring Core(核心容器)
spring core提供了IOC,DI,Bean配置装载创建的核心实现
核心概念: Beans、BeanFactory、BeanDefinitions、ApplicationContext
- spring-core :IOC和DI的基本实现
- spring-beans:BeanFactory和Bean的装配管理(BeanFactory)
- spring-context:Spring context上下文,即IOC容器(AppliactionContext)
- spring-expression:spring表达式语言
一、IOC容器
1、控制反转(ioc)
- 控制反转是一种
思想
- 控制反转是为了降低程序耦合度,提高程序扩展力
- 控制反转,反转的是什么?
- 将
对象的创建
权利交出去,交给第三方容器负责 - 将
对象和对象之间关系
的维护权交出去,交给第三方容器负责
- 将
- 控制反转这种思想如何实现呢?
- DI(Dependency Injection):依赖注入
2、依赖注入
DI(Dependency Injection):依赖注入,依赖注入实现了控制反转的思想
- 指Spring创建对象的过程中,将对象
依赖属性
通过配置进行注入
- 依赖注入常见的实现方式包括两种:
- set注入
- 构造注入
- Bean管理说的是:Bean对象的
创建
,以及Bean对象中属性的赋值
(或者叫做Bean对象之间关系的维护)
所以结论是:IOC 就是一种控制反转的思想, 而 DI 是对IoC的一种具体实现
3、IoC容器在Spring的实现
- Spring 的 IoC 容器就是 IoC思想的一个落地的产品实现
- IoC容器中管理的组件也叫做 bean
- 在创建 bean 之前,首先需要创建IoC 容器
- Spring 提供了IoC 容器的两种实现方式:
- BeanFactory
- 这是 IoC 容器的
基本实现
- 是 Spring 内部使用的接口
- 面向 Spring 本身,不提供给开发人员使用
- 这是 IoC 容器的
- ApplicationContext
BeanFactory 的子接口,提供了更多高级特性
- 面向 Spring 的使用者
- 几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory
- BeanFactory
ApplicationContext的主要实现类
类型名 | 简介 |
---|---|
ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 |
ConfigurableApplicationContext | ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力 |
WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中 |
二、基于XML管理Bean
搭建项目
- 添加依赖
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.3</version>
</dependency>
- 引入java类
public class User {
}
- beans.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">
<!--1 获取bean演示,user对象创建-->
<bean id="user" class="com.xc.spring6.iocxml.bean.User"></bean>
</beans>
1、获取bean
方式一、根据id获取
public class TestUser {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
// 根据id获取bean
User user = (User) context.getBean("user");
}
}
方式二、根据类型获取
public class TestUser {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean(User.class);
}
}
方式三、根据id和类型获取bean
public class TestUser {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean("user",User.class);
}
}
注意
- 当根据类型获取bean时,要求IOC容器中
指定类型
的bean有且只能有一个 - 当IOC容器中一共配置了两个
<bean id="user" class="com.xc.spring6.iocxml.bean.User"></bean>
<bean id="user1" class="com.xc.spring6.iocxml.bean.User"></bean>
根据类型获取时会抛出异常:NoUniqueBeanDefinitionException(没有唯一Bean定义异常)
扩展
- 如果组件类实现了接口,
根据接口类型
可以获取 bean 吗?(可以
,前提是bean唯一) - 如果一个接口有
多个实现类
,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?(不行
,因为bean不唯一)
2、依赖注入之setter注入
- 创建学生类Student
@Data
public class Student {
private Integer id;
private String name;
private Integer age;
private String sex;
}
- 配置bean时为属性赋值
<bean id="studentOne" class="com.xc.spring6.bean.Student">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) -->
<!-- value属性:指定属性值 -->
<property name="id" value="1001"></property>
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
<property name="sex" value="男"></property>
</bean>
- 测试
@Test
public void testDIBySet(){
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml");
Student studentOne = ac.getBean("studentOne", Student.class);
System.out.println(studentOne);
}
3、依赖注入之构造器注入
- 在Student类中添加有参构造
public Student(Integer id, String name, Integer age, String sex) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
}
- 配置bean
<bean id="studentTwo" class="com.xc.spring6.bean.Student">
<constructor-arg value="1002"></constructor-arg>
<constructor-arg value="李四"></constructor-arg>
<constructor-arg value="33"></constructor-arg>
<constructor-arg value="女"></constructor-arg>
</bean>
注意:
constructor-arg标签还有两个属性可以进一步描述构造器参数
- index属性:指定参数所在位置的索引(从0开始)
- name属性:指定参数名
- 测试
@Test
public void testDIByConstructor(){
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml");
Student studentOne = ac.getBean("studentTwo", Student.class);
System.out.println(studentOne);
}
4、特殊值处理
字面量赋值
<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 -->
<property name="name" value="张三"/>
null值
<property name="name">
<null />
</property>
xml实体
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a < b"/>
CDATA节
<property name="expression">
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<value><![CDATA[a < b]]></value>
</property>
5、为对象类型属性赋值
- 员工类和部门类
// 部门累
@Data
public class Dept {
private String dname;
}
// 员工类
@Data
public class Emp {
//对象类型属性:员工属于某个部门
private Dept dept;
//员工名称
private String ename;
//员工年龄
private Integer age;
}
方式一:引用外部bean
<bean id="dept" class="com.xc.spring6.iocxml.ditest.Dept">
<property name="dname" value="安保部"></property>
</bean>
- 使用
ref属性
给Emp类的部门属性赋值对象
<bean id="emp" class="com.xc.spring6.iocxml.ditest.Emp">
<!--注入对象类型属性
private Dept dept;
-->
<property name="dept" ref="dept"></property>
<!--普通属性注入-->
<property name="ename" value="lucy"></property>
<property name="age" value="50"></property>
</bean>
方式二:内部bean
<bean id="emp2" class="com.xc.spring6.iocxml.ditest.Emp">
<!--普通属性注入-->
<property name="ename" value="mary"></property>
<property name="age" value="20"></property>
<!--内部bean-->
<property name="dept">
<bean id="dept2" class="com.xc.spring6.iocxml.ditest.Dept">
<property name="dname" value="财务部"></property>
</bean>
</property>
</bean>
方式三:级联属性赋值
- name属性 = 对象.属性名
<bean id="dept3" class="com.xc.spring6.iocxml.ditest.Dept">
<property name="dname" value="技术研发部"></property>
</bean>
<bean id="emp3" class="com.xc.spring6.iocxml.ditest.Emp">
<property name="ename" value="tom"></property>
<property name="age" value="30"></property>
<property name="dept" ref="dept3"></property>
<property name="dept.dname" value="测试部"></property>
</bean>
6、为数组类型属性赋值
- Emp员工类添加爱好属性数组
//爱好
private String[] loves;
<bean id="emp" class="com.xc.spring6.iocxml.ditest.Emp">
<!--普通属性-->
<property name="ename" value="lucy"></property>
<property name="age" value="20"></property>
<!--对象类型属性-->
<property name="dept" ref="dept"></property>
<!--数组类型属性-->
<property name="loves">
<array>
<value>吃饭</value>
<value>睡觉</value>
<value>敲代码</value>
</array>
</property>
</bean>
7、为集合类型属性赋值
- Dept部门类添加员工集合
//一个部门有很多员工
private List<Emp> empList;
<bean id="empone" class="com.xc.spring6.iocxml.ditest.Emp">
<property name="ename" value="lucy"></property>
<property name="age" value="20"></property>
</bean>
<bean id="emptwo" class="com.xc.spring6.iocxml.ditest.Emp">
<property name="ename" value="mary"></property>
<property name="age" value="30"></property>
</bean>
<bean id="dept" class="com.xc.spring6.iocxml.ditest.Dept">
<property name="dname" value="技术部"></property>
<property name="empList">
<list>
<ref bean="empone"></ref>
<ref bean="emptwo"></ref>
</list>
</property>
</bean>
若为Set集合类型属性赋值,只需要将其中的list标签改为
set标签
即可
8、基于xml自动装配
- 根据指定的策略,在IOC容器中匹配某一个bean
- 自动为指定的bean中所依赖的类类型或接口类型属性赋值
- 基于XML自动装配,底层使用set注入
bean类
public class UserServiceImpl implements UserService{
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUserService() {
userDao.addUserDao();
}
}
配置bean.xml
- 自动装配方式:byType
- byType:根据
类型匹配
IOC容器中的某个兼容类型的bean,为属性自动赋值- 若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null
- 若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException
<bean id="userService" class="com.xc.spring6.autowire.service.impl.UserServiceImpl"
autowire="byType"></bean>
<bean id="userDao" class="com.xc.spring6.autowire.dao.impl.UserDaoImpl"></bean>
- 自动装配方式:byName
- byName:将自动装配的属性的
属性名
,作为bean的id在IOC容器中匹配相对应的bean进行赋值
三、基于注解管理Bean(☆)
1、配置类替换bean.xml
@Configuration
//@ComponentScan({"com.xc.spring6.controller", "com.xc.spring6.service","com.xc.spring6.dao"})
@ComponentScan("com.xc.spring6")
public class Spring6Config {
}
2、@Autowired注入
- 单独使用@Autowired注解,
默认根据类型装配
- 查看注解源码
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD,
ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
- 注解可以标注的位置
- 构造方法上
- 方法上
- 形参上
- 属性上
- 注解上
- 该注解有一个required属性
- 默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错
- 如果required属性设置为false,表示注入的Bean存在或者不存在都没关系
方式一:属性注入(最常用
)
- 基于注解自动装配,底层使用反射注入,故不需要set方法
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
}
方式二、set注入
@Service
public class UserServiceImpl implements UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
方式三、构造方法注入
@Service
public class UserServiceImpl implements UserService {
private UserDao userDao;
@Autowired
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
}
方式四、形参注入
@Service
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserServiceImpl(@Autowired UserDao userDao) {
this.userDao = userDao;
}
}
方式五、只有一个构造函数,通过构造器注入不需要注解
- 当有参数的构造方法只有一个时,@Autowired注解可以省略
@Service
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
}
方式六、@Autowired注解和@Qualifier注解联合
- 如果UserDao接口有
两个实现类
- 通过@Autowired注入会抛异常(expected single matching bean but found 2:xx1,xx2)
- 此时可以通过@Qualifier注解指定具体bean的名称
@Service
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier("userDaoImpl") // 指定bean的名字
private UserDao userDao;
}
3、@Resource注入
@Resource注解也可以完成属性注入。那它和@Autowired注解有什么区别?
- @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分
- 所以该注解是标准注解,更加具有通用性
- JSR-250标准中制定的注解类型。JSR是Java规范提案
- @Autowired注解是Spring框架自己的
- @Resource注解默认根据名称装配byName,通过name找不到的话会自动启动通过类型byType装配
- @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用
- @Resource注解用在属性上、setter方法上
- @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上
@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖
如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
方式一、根据name注入
@Service
public class UserServiceImpl implements UserService {
@Resource(name = "userDao")
private UserDao myUserDao;
}
方式二、name未知注入
- 当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
}
方式三、name找不到的情况
- 显然当通过name找不到的时候,自然会启动byType进行注入
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDaoNotFound ;
}
四、原理-手写IoC
定义标记bean的@Bean注解和依赖注入的@Di注解
- @Bean相当于@Component
- @Di相当于@Autowired
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}
定义bean容器接口
public interface ApplicationContext {
Object getBean(Class<?> clazz);
}
注解bean容器接口和依赖注入的实现
public class AnnotationApplicationContext implements ApplicationContext {
//创建map集合,放bean对象
private final Map<Class<?>, Object> beanFactory = new HashMap<>();
private static String rootPath;
//返回对象
@Override
public Object getBean(Class<?> clazz) {
return beanFactory.get(clazz);
}
//创建有参数构造,传递包路径,设置包扫描规则
//当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化
public AnnotationApplicationContext(String basePackage) {
// com.xc
try {
//1 把.替换成\
String packagePath = basePackage.replaceAll("\\.", "/");
//2 获取包绝对路径
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
//获取包前面路径部分,字符串截取
rootPath = filePath.substring(0, filePath.length() - packagePath.length());
//包扫描
loadBean(new File(filePath));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
//属性注入
loadDi();
}
//包扫描过程,实例化
private void loadBean(File file) throws Exception {
//1 判断当前是否文件夹
if (file.isDirectory()) {
//2 获取文件夹里面所有内容
File[] childrenFiles = file.listFiles();
//3 判断文件夹里面为空,直接返回
if (childrenFiles == null || childrenFiles.length == 0) {
return;
}
//4 如果文件夹里面不为空,遍历文件夹所有内容
for (File child : childrenFiles) {
//4.1 遍历得到每个File对象,继续判断,如果还是文件夹,递归
if (child.isDirectory()) {
//递归
loadBean(child);
} else {
//4.2 遍历得到File对象不是文件夹,是文件,
//4.3 得到包路径+类名称部分-字符串截取
String pathWithClass = child.getAbsolutePath().substring(rootPath.length());
//4.4 判断当前文件类型是否.class
if (pathWithClass.contains(".class")) {
//4.5 如果是.class类型,把路径\替换成. 把.class去掉
// com.xc.service.UserServiceImpl
String allName = pathWithClass.replaceAll("/", ".").replace(".class", "");
//4.6 判断类上面是否有注解 @Bean,如果有实例化过程
//4.6.1 获取类的Class
Class<?> clazz = Class.forName(allName);
//4.6.2 判断不是接口
if (!clazz.isInterface()) {
//4.6.3 判断类上面是否有注解 @Bean
if (clazz.isAnnotationPresent(Bean.class)) {
//4.6.4 实例化
Object instance = clazz.getConstructor().newInstance();
//4.7 把对象实例化之后,放到map集合beanFactory
//4.7.1 判断当前类如果有接口,让接口class作为map的key
if (clazz.getInterfaces().length > 0) {
beanFactory.put(clazz.getInterfaces()[0], instance);
} else {
beanFactory.put(clazz, instance);
}
}
}
}
}
}
}
}
//属性注入
private void loadDi() {
//实例化对象在beanFactory的map集合里面
//1 遍历beanFactory的map集合
Set<Map.Entry<Class<?>, Object>> entries = beanFactory.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
//2 获取map集合每个对象(value),每个对象属性获取到
Object obj = entry.getValue();
//获取对象Class
Class<?> clazz = obj.getClass();
//获取每个对象属性获取到
Field[] declaredFields = clazz.getDeclaredFields();
//3 遍历得到每个对象属性数组,得到每个属性
for (Field field : declaredFields) {
//4 判断属性上面是否有@Di注解
if (field.isAnnotationPresent(Di.class)) {
//如果私有属性,设置可以设置值
field.setAccessible(true);
//5 如果有@Di注解,把对象进行设置(注入)
try {
field.set(obj, beanFactory.get(field.getType()));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
测试类
@Bean
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("dao.......");
}
}
@Bean
public class UserServiceImpl implements UserService {
@Di
private UserDao userDao;
@Override
public void out() {
userDao.print();
System.out.println("Service层执行结束");
}
}
public class SpringIocTest {
@Test
public void testIoc() {
ApplicationContext applicationContext = new AnnotationApplicationContext("com.xc");
UserService userService = (UserService)applicationContext.getBean(UserService.class);
userService.add();
System.out.println("run success");
}
}
输出结果
service.......
dao.......
run success