0


Spring之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 本身,不提供给开发人员使用- ApplicationContext- BeanFactory 的子接口,提供了更多高级特性- 面向 Spring 的使用者- 几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory

ApplicationContext的主要实现类

在这里插入图片描述
类型名简介ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象FileSystemXmlApplicationContext通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象ConfigurableApplicationContextApplicationContext 的子接口,包含一些扩展方法 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类
publicclassUser{}
  • 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获取

publicclassTestUser{publicstaticvoidmain(String[] args){ApplicationContext context =newClassPathXmlApplicationContext("bean.xml");// 根据id获取beanUser user =(User) context.getBean("user");}}

方式二、根据类型获取

publicclassTestUser{publicstaticvoidmain(String[] args){ApplicationContext context =newClassPathXmlApplicationContext("bean.xml");User user = context.getBean(User.class);}}

方式三、根据id和类型获取bean

publicclassTestUser{publicstaticvoidmain(String[] args){ApplicationContext context =newClassPathXmlApplicationContext("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
@DatapublicclassStudent{privateInteger id;privateString name;privateInteger age;privateString sex;}
  • 配置bean时为属性赋值
<beanid="studentOne"class="com.xc.spring6.bean.Student"><!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 --><!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) --><!-- value属性:指定属性值 --><propertyname="id"value="1001"></property><propertyname="name"value="张三"></property><propertyname="age"value="23"></property><propertyname="sex"value="男"></property></bean>
  • 测试
@TestpublicvoidtestDIBySet(){ApplicationContext ac =newClassPathXmlApplicationContext("spring-di.xml");Student studentOne = ac.getBean("studentOne",Student.class);System.out.println(studentOne);}

3、依赖注入之构造器注入

  • 在Student类中添加有参构造
publicStudent(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属性:指定参数名
  • 测试
@TestpublicvoidtestDIByConstructor(){ApplicationContext ac =newClassPathXmlApplicationContext("spring-di.xml");Student studentOne = ac.getBean("studentTwo",Student.class);System.out.println(studentOne);}

4、特殊值处理

字面量赋值

<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 --><propertyname="name"value="张三"/>

null值

<propertyname="name"><null/></property>

xml实体

<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 --><!-- 解决方案一:使用XML实体来代替 --><propertyname="expression"value="a &lt; b"/>

CDATA节

<propertyname="expression"><!-- 解决方案二:使用CDATA节 --><!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 --><!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 --><!-- 所以CDATA节中写什么符号都随意 --><value><![CDATA[a < b]]></value></property>

5、为对象类型属性赋值

  • 员工类和部门类
// 部门累@DatapublicclassDept{privateString dname;}// 员工类@DatapublicclassEmp{//对象类型属性:员工属于某个部门privateDept dept;//员工名称privateString ename;//员工年龄privateInteger age;}

方式一:引用外部bean

<beanid="dept"class="com.xc.spring6.iocxml.ditest.Dept"><propertyname="dname"value="安保部"></property></bean>
  • 使用ref属性给Emp类的部门属性赋值对象
<beanid="emp"class="com.xc.spring6.iocxml.ditest.Emp"><!--注入对象类型属性
        private Dept dept;
    --><propertyname="dept"ref="dept"></property><!--普通属性注入--><propertyname="ename"value="lucy"></property><propertyname="age"value="50"></property></bean>

方式二:内部bean

<beanid="emp2"class="com.xc.spring6.iocxml.ditest.Emp"><!--普通属性注入--><propertyname="ename"value="mary"></property><propertyname="age"value="20"></property><!--内部bean--><propertyname="dept"><beanid="dept2"class="com.xc.spring6.iocxml.ditest.Dept"><propertyname="dname"value="财务部"></property></bean></property></bean>

方式三:级联属性赋值

  • name属性 = 对象.属性名
<beanid="dept3"class="com.xc.spring6.iocxml.ditest.Dept"><propertyname="dname"value="技术研发部"></property></bean><beanid="emp3"class="com.xc.spring6.iocxml.ditest.Emp"><propertyname="ename"value="tom"></property><propertyname="age"value="30"></property><propertyname="dept"ref="dept3"></property><propertyname="dept.dname"value="测试部"></property></bean>

6、为数组类型属性赋值

  • Emp员工类添加爱好属性数组
//爱好privateString[] loves;
<beanid="emp"class="com.xc.spring6.iocxml.ditest.Emp"><!--普通属性--><propertyname="ename"value="lucy"></property><propertyname="age"value="20"></property><!--对象类型属性--><propertyname="dept"ref="dept"></property><!--数组类型属性--><propertyname="loves"><array><value>吃饭</value><value>睡觉</value><value>敲代码</value></array></property></bean>

7、为集合类型属性赋值

  • Dept部门类添加员工集合
//一个部门有很多员工privateList<Emp> empList;
<beanid="empone"class="com.xc.spring6.iocxml.ditest.Emp"><propertyname="ename"value="lucy"></property><propertyname="age"value="20"></property></bean><beanid="emptwo"class="com.xc.spring6.iocxml.ditest.Emp"><propertyname="ename"value="mary"></property><propertyname="age"value="30"></property></bean><beanid="dept"class="com.xc.spring6.iocxml.ditest.Dept"><propertyname="dname"value="技术部"></property><propertyname="empList"><list><refbean="empone"></ref><refbean="emptwo"></ref></list></property></bean>

若为Set集合类型属性赋值,只需要将其中的list标签改为

set标签

即可

8、基于xml自动装配

  • 根据指定的策略,在IOC容器中匹配某一个bean
  • 自动为指定的bean中所依赖的类类型或接口类型属性赋值
  • 基于XML自动装配,底层使用set注入

bean类

publicclassUserServiceImplimplementsUserService{privateUserDao userDao;publicvoidsetUserDao(UserDao userDao){this.userDao = userDao;}@OverridepublicvoidaddUserService(){
        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")publicclassSpring6Config{}

加载配置类:

ApplicationContext context1 =newAnnotationConfigApplicationContext(Spring6Config.class);

2、@Autowired注入

  • 单独使用@Autowired注解,默认根据类型装配
  • 查看注解源码
@Target({ElementType.CONSTRUCTOR,ElementType.METHOD,ElementType.PARAMETER,ElementType.FIELD,ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceAutowired{booleanrequired()defaulttrue;}
  • 注解可以标注的位置- 构造方法上- 方法上- 形参上- 属性上- 注解上
  • 该注解有一个required属性- 默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错- 如果required属性设置为false,表示注入的Bean存在或者不存在都没关系
  • 先根据类型去找Bean,如果找到多个再根据名称确定一个(确定不了抛异常不唯一)

方式一:属性注入(

最常用

  • 基于注解自动装配,底层使用反射注入,故不需要set方法
@ServicepublicclassUserServiceImplimplementsUserService{@AutowiredprivateUserDao userDao;}

方式二、set注入

@ServicepublicclassUserServiceImplimplementsUserService{privateUserDao userDao;@AutowiredpublicvoidsetUserDao(UserDao userDao){this.userDao = userDao;}}

方式三、构造方法注入

@ServicepublicclassUserServiceImplimplementsUserService{privateUserDao userDao;@AutowiredpublicUserServiceImpl(UserDao userDao){this.userDao = userDao;}}

方式四、形参注入

@ServicepublicclassUserServiceImplimplementsUserService{privateUserDao userDao;publicUserServiceImpl(@AutowiredUserDao userDao){this.userDao = userDao;}}

方式五、只有一个构造函数,通过构造器注入不需要注解

  • 当有参数的构造方法只有一个时,@Autowired注解可以省略
@ServicepublicclassUserServiceImplimplementsUserService{privateUserDao userDao;publicUserServiceImpl(UserDao userDao){this.userDao = userDao;}}

方式六、@Autowired注解和@Qualifier注解联合

  • 如果UserDao接口有两个实现类
  • 通过@Autowired注入会抛异常(expected single matching bean but found 2:xx1,xx2)
  • 此时可以通过@Qualifier注解指定具体bean的名称
@ServicepublicclassUserServiceImplimplementsUserService{@Autowired@Qualifier("userDaoImpl")// 指定bean的名字privateUserDao 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注入

@ServicepublicclassUserServiceImplimplementsUserService{@Resource(name ="userDao")privateUserDao myUserDao;}

方式二、name未知注入

  • 当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名
@ServicepublicclassUserServiceImplimplementsUserService{@ResourceprivateUserDao userDao;}

方式三、name找不到的情况

  • 显然当通过name找不到的时候,自然会启动byType进行注入
@ServicepublicclassUserServiceImplimplementsUserService{@ResourceprivateUserDao userDaoNotFound ;}

四、原理-手写IoC

定义标记bean的@Bean注解和依赖注入的@Di注解

  • @Bean相当于@Component
  • @Di相当于@Autowired
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public@interfaceBean{}@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)public@interfaceDi{}

定义bean容器接口

publicinterfaceApplicationContext{ObjectgetBean(Class<?> clazz);}

注解bean容器接口和依赖注入的实现

publicclassAnnotationApplicationContextimplementsApplicationContext{//创建map集合,放bean对象privatefinalMap<Class<?>,Object> beanFactory =newHashMap<>();privatestaticString rootPath;//返回对象@OverridepublicObjectgetBean(Class<?> clazz){return beanFactory.get(clazz);}//创建有参数构造,传递包路径,设置包扫描规则//当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化publicAnnotationApplicationContext(String basePackage){// com.xctry{//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(newFile(filePath));}}catch(Exception e){thrownewRuntimeException(e);}//属性注入loadDi();}//包扫描过程,实例化privatevoidloadBean(File file)throwsException{//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 判断当前文件类型是否.classif(pathWithClass.contains(".class")){//4.5 如果是.class类型,把路径\替换成.  把.class去掉// com.xc.service.UserServiceImplString allName = pathWithClass.replaceAll("/",".").replace(".class","");//4.6 判断类上面是否有注解 @Bean,如果有实例化过程//4.6.1 获取类的ClassClass<?> clazz =Class.forName(allName);//4.6.2 判断不是接口if(!clazz.isInterface()){//4.6.3 判断类上面是否有注解 @Beanif(clazz.isAnnotationPresent(Bean.class)){//4.6.4 实例化Object instance = clazz.getConstructor().newInstance();//4.7 把对象实例化之后,放到map集合beanFactory//4.7.1 判断当前类如果有接口,让接口class作为map的keyif(clazz.getInterfaces().length >0){
                                    beanFactory.put(clazz.getInterfaces()[0], instance);}else{
                                    beanFactory.put(clazz, instance);}}}}}}}}//属性注入privatevoidloadDi(){//实例化对象在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();//获取对象ClassClass<?> 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){thrownewRuntimeException(e);}}}}}}

测试类

@BeanpublicclassUserDaoImplimplementsUserDao{@Overridepublicvoidadd(){System.out.println("dao.......");}}
@BeanpublicclassUserServiceImplimplementsUserService{@DiprivateUserDao userDao;@Overridepublicvoidout(){
        userDao.print();System.out.println("Service层执行结束");}}
publicclassSpringIocTest{@TestpublicvoidtestIoc(){ApplicationContext applicationContext =newAnnotationApplicationContext("com.xc");UserService userService =(UserService)applicationContext.getBean(UserService.class);
        userService.add();System.out.println("run success");}}

输出结果

service.......
dao.......
run success
标签: spring java 后端

本文转载自: https://blog.csdn.net/qq_35512802/article/details/131272763
版权归原作者 冬天vs不冷 所有, 如有侵权,请联系我们删除。

“Spring之IOC容器”的评论:

还没有评论