Java面试题,Spring Bean的注册与依赖注入

news/2024/11/29 8:38:15/

Spring Bean的注册与依赖注入

  • 一、XML文件中,将Bean创建到Spring容器
    • 1. 基本类型注册
    • 2. 类装配
    • 3. 有参构造方法装配
    • 4. 扩展注入
    • 5. Bean的作用域
    • 6. Bean的其他配置
  • 二、配置类中,将Bean创建到Spring容器
    • 1. 在mapper、service、controller中创建,等着被componentScan扫描到Spring容器中
    • 2. 在JavaConfig配置类中创建Bean
  • 三、Spring的三种装配机制(XML、JavaConfig、自动装配)
    • 1. 在xml中显式装配
    • 2. 在JavaConfig配置类中显式装配
    • 3. 自动装配
      • 3.1 xml方式自动装配
      • 3.1 使用注解自动装配
  • 四、Bean创建的原理
    • 1. UserService实例化,生成普通对象
    • 2. 使用@Autowired,对普通对象的属性进行依赖注入
    • 3. 初始化(执行UserService中的所有方法)
    • 4. 初始化后(AOP),创建代理对象
    • 5. 将普通对象或者代理对象放入,Map<beanName,Bean对象>
  • 五、创建Bean过程中,推断所使用的构造方法
    • 情形1. 没有写出构造器,默认存在一个无参构造器
    • 情形2. 写一个有参构造器,那么构造器就只有这个有参构造器
    • 情形3. 写一个无参构造器和一个有参构造器,那么就存在两个构造器
    • 情形4. 编写一个有参构造器,那么就存在一个有参构造器
    • 情形5. 编写如下两个有参构造器
    • 情形6. 那么有没有办法使用指定的构造方法?通过@Autowired实现
    • 7. 谁向有参构造器public UserService(OrderService orderService)中传入参数?
    • 8. Spring怎样从Map<beanName,Bean对象>中,找到合适的Bean对象传入有参构造器的参数中?
    • 9. 循环依赖
  • 六、依赖注入
    • 1. 先根据类型type,再根据id
    • 2. 依赖注入的三种方式(属性、set方法、有参构造)

一、XML文件中,将Bean创建到Spring容器

1. 基本类型注册

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student{private String name;private Address address;private String[] books;private List<String> hobbys;private Map<String String> card;private Set<String> games;private String wife;private Properties info;
}
<bean id="student" class="com.kuang.pojo.Student"><!--1. 普通注入--><property name="name" value="狂神说"/><!--2. 数组注入--><property name="books"><array><value>红楼梦</value><value>西游记</value><value>水浒传</value></array></property><!--3. List注入--><property name="hobbys"><list><value>看书</value><value>听歌</value><value>打球</value></list></property><!--4. Map注入--><property name="card"><map><entry key=“身份证” value=“12315454512312”/><entry key=“银行卡” value=“15465415123156”/></map></property><!--5. 数组注入--><property name="games"><set><value>红楼梦</value><value>西游记</value><value>水浒传</value></set></property><!--6. 空值注入--><property name="wife"><null/></property><!--7. Properties注入--><property name="info"><props><prop key="driver">20190525</prop><prop key="url">男/prop><prop key="username">root</prop><prop key="password">123456</prop></props></property>
</bean>

2. 类装配

<!--1. 类装配--><!--csBean类有两个属性:title和author--><bean name="cdBean" class="com.my.spring.bean.CDBean"><property name="title" value="The World!!"/><property name="author" value="Mr.D"/></bean>    <!--csPlayer类有一个属性:cdBean--><!--对csPlayer的属性csBean进行依赖注入,称为Bean装配,或者依赖关系注入--><bean name="cdPlayer" class="com.my.spring.service.impl.CDPlayerImpl"><property name="cdBean" ref="cdBean"/></bean>

3. 有参构造方法装配

<!--2. 有参构造方法1--><bean id="hello" class="com.kuang.pojo.Hello"><constructor-arg index="0" value="方法1"/></bean>
<!--3. 有参构造方法2--><bean id="hello" class="com.kuang.pojo.Hello"><constructor-arg type="java.lang.String" value="方法2"/></bean>
<!--4. 有参构造方法3--><bean id="hello" class="com.kuang.pojo.Hello"><constructor-arg name="name" value="方法3"/></bean>

4. 扩展注入

  • p命名空间注入
1. 先在beans框架中加入支持:xmlns:p="http://www/springframework.org/schema/p"
<!--p命名空间注入,可以直接注入属性的值,property-->
<bean id="user" class="com.kuang.pojo.User" p:name="小李" p:age="10"/>
  • c命名空间注入
1. 先在beans框架中加入支持:xmlns:c="http://www/springframework.org/schema/c"
2. 其次使用c注入的Bean必须存在有参构造器
<!--c命名空间注入,通过构造器注入,construct-args-->
<bean id="user" class="com.kuang.pojo.User" c:name="小李" c:age="10"/>

5. Bean的作用域

  1. 单例模式(Spring默认机制):从Spring容器中get的每个对象都是同一个对象。
<bean id="user" class="com.kuang.pojo.User" scope="singleton"/>
ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
User user1 = context.getBean("user");
User user2 = context.getBean("user");
System.out.println(user1==user2);	//true
  1. 原型模式:每次从容器中get的时候,都会产生一个新对象
<bean id="user" class="com.kuang.pojo.User" scope="prototype"/>
ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
User user1 = context.getBean("user");
User user2 = context.getBean("user");
System.out.println(user1==user2);	//false

6. Bean的其他配置

6.1. 别名

<!--别名,如果添加了别名,我们可以使用别名获取这个对象-->
<alias name="user" alias="userNew"><!--id:bean的唯一标识符,也就是相当于我们学的对象名class:bean对象所对应的全限定名:包名+类型name:也是别名,而且那么可以同时取多个别名
--><bean id="user" class="com.kuang.pojo.user" name="user1,user2,user3"><property name="name" value="狂神说"/></bean>

6.2 Import(一般用于团队开发使用,他可以将多个配置文件,导入合并为一个)
注意:如果导入的文件中,bean重名了,那么就会把重名的bean合并成一个,所以不会因为不同的bean.xml存在重名而发生冲突

<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>

二、配置类中,将Bean创建到Spring容器

1. 在mapper、service、controller中创建,等着被componentScan扫描到Spring容器中

/*在Spring容器中创建一个bean<bean id="orderService" class="com.kuang.Service.OrderService">
*/
@Component
public class OrderService{
}

2. 在JavaConfig配置类中创建Bean

/*在Spring容器中创建一个bean<bean id="orderService1" class="com.kuang.Service.OrderService">
*/
@Configuration
@ComponentScan
public class AppConfig{@Beanpublic OrderService orderService1(){return new OrderService();}
}

三、Spring的三种装配机制(XML、JavaConfig、自动装配)

1. 在xml中显式装配

<!--1. 类装配--><!--csBean类有两个属性:title和author--><bean name="cdBean" class="com.my.spring.bean.CDBean"><property name="title" value="The World!!"/><property name="author" value="Mr.D"/></bean>    <!--csPlayer类有一个属性:cdBean--><!--对csPlayer的属性csBean进行依赖注入,称为Bean装配,或者依赖关系注入--><bean name="cdPlayer" class="com.my.spring.service.impl.CDPlayerImpl"><property name="cdBean" ref="cdBean"/></bean>

2. 在JavaConfig配置类中显式装配

  • 实体类
@Component
public class User {private String name;public String getName() {return name;}@Value("黑心白莲") //属性注入值public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +'}';}
}
  • 配置类
// @Configuration代表这是一个配置类,就和我们之前看的beans.xml
@Configuration
@ComponentScan
public class KuangConfig {/* @bean == bean标签方法名字 == bean标签中id属性方法返回值 == bean标签中的class属性*/@Beanpublic User user(){return new User(); }
}
  • 测试类
public class MyTest {public static void main(String[] args) {//如果完全使用了配置类方式去做,我们就只能通过 AnnotationConfig 上下文来获取容器,通过配置类的class对象加载!ApplicationContext context = new AnnotationConfigApplicationContext(KuangConfig.class);User user = context.getBean("user", User.class);System.out.println(user.getName());}
}

3. 自动装配

3.1 xml方式自动装配

byName: 会自动在容器上下文查找,和自己对象set方法后面的值对应的bean id(通过id匹配)

//实体类
@Data
public class Pepole {private String name;private Books books;private Hobbies hobbies;
    <bean id="books" class="com.lmy.pojo.Books"/><bean id="hobbies" class="com.lmy.pojo.Hobbies"/><bean id="pepole" class="com.lmy.pojo.Pepole" autowire="byName"><property name="name" value="zhangSan"/></bean>

byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean(通过class匹配)
注意:使用autowire byType首先需要保证:同一类型的bean对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。

    <bean id="books" class="com.lmy.pojo.Books"/><bean id="hobbies" class="com.lmy.pojo.Hobbies"/><bean id="pepole" class="com.lmy.pojo.Pepole" autowire="byType"><property name="name" value="zhangSan"/></bean>

3.1 使用注解自动装配

注解开发注意事项
@Autowied装配方式
按类型装配(默认使用的装配方式)。
按名称装配(结合@Qualifier注解使用)。

public class Pepole {private String name;
//1. 通过名字装配@Autowired@Qualifier("books1")private Books books;
//2. 通过类型装配@Autowiredprivate Hobbies hobbies;
}
	<!--开启属性注解支持!--><context:annotation-config/><bean id="books2" class="com.lmy.pojo.Books"/><bean id="books1" class="com.lmy.pojo.Books"/><bean id="hobbies" class="com.lmy.pojo.Hobbies"/><bean id="pepole" class="com.lmy.pojo.Pepole"/>

@Resource装配方式
@Resource指定按type自动装配

    @Resource(type = Books.class)private Books books;

@Resource指定按name自动装配:

    @Resource(name = "books")private Books books;

@Autowired与@Resource区别
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、Bean创建的原理

这段代码中@mapper,实际上执行了Spring创建Bean对象并放入Spring容器的过程

@mapper
public class UserService{@Autowiredprivate OrderService orderService;public void test(){System.out.println(orderService);}
}
  1. UserService实例化(无参构造方法),生成普通对象
  2. 对普通对象依赖注入- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -【根据@Autowired判断是否对属性注入依赖】
  3. 初始化- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -【执行所有方法】
  4. 初始化后(AOP),生成代理对象 - - - - - - - - - - - - - - - - - - - - - - - - - - - - -【切面编程】
  5. 将普通对象或者代理对象放入,Map<beanName,Bean对象>(单例池) -【即注册Bean对象】

1. UserService实例化,生成普通对象

这里的UserService实例化,可不是说让你自己在main()方法里面手动做一个UserService userService的实例化。而是说你在public class UserService“头顶”上加了注解@Mapper的时候,Spring“背地里”进行了第一步:将UserService实例化了

2. 使用@Autowired,对普通对象的属性进行依赖注入

  • 在UserService对象中有一个属性OrderService,Spring也对该属性进行了实例化OrderService orderService。但是实例化出来的普通对象orderService是没有值的,通过@Autowired的依赖注入,向普通对象orderService注入,普通对象orderService就有了值,

3. 初始化(执行UserService中的所有方法)

//初始化,执行UserService中的所有方法,全都成功后才能将Bean对象创建并放入Map
if (userService instanceof InitializingBean){((InitializingBean) userService).afterPropertiesSet();
}

为什么要执行初始化这个步骤呢?换句话说,为什么要执行所有方法呢?

//1. 通过执行方法来为创建的实例的属性admin注入值为xxx
@Override
public void afterPropertiesSet() throws Exception{this.admin = "xxx";
}//2. 通过执行方法来检验创建的实例是否满足
public void afterPropertiesSet() throws Exception{if(sqlSession == null){throw new NullPointerException();}
}/*
3. 个人项目经历:每个属性都要get/set方法,若是缺失了某个属性的get/set方法,
Spring启动就会报错,这也说明了初始化执行了Bean对象的所有方法
*/

4. 初始化后(AOP),创建代理对象

AOP就是对注入依赖后的普通对象进行切面编程,从而生成代理对象。至于什么是切面编程AOP,读者就自行搜索一下吧,因为我也只是一知半解。

5. 将普通对象或者代理对象放入,Map<beanName,Bean对象>

  • 我们先看到回到上面Spring创建Bean对象的过程,会发现有普通对象和代理对象。两者的区别,大家可以认为是普通对象经过了“加工”,变成了更多功能的代理对象。
  • 现在我们要将普通对象或者代理对象放入Map并将代理对象放入Map<beanName,Bean对象>中。
  • 如果没有进行AOP生成代理对象,那么这时候就是将普通对象放入Map中,普通对象成为Bean对象;如果进行了AOP生成代理对象,那么这时候就是将代理对象放入Map中,代理对象成为Bean对象;
  • 将普通对象或者代理对象放入Map这个动作完成了,才是真正意义上生成了Bean对象,也就是注册了Bean

五、创建Bean过程中,推断所使用的构造方法

情形1. 没有写出构造器,默认存在一个无参构造器

@Service
public class UserService{private OrderService orderService;public void test(){				//初始化会自动执行全部方法,所以test方法会被自动执行System.out.println(orderService);}
}

执行UserService userService = (UserService) applicationContext.getBean(“UserService”);
输出:null

情形2. 写一个有参构造器,那么构造器就只有这个有参构造器

@Service
public class UserService{private OrderService orderService;public UserService(OrderService orderService){this.orderService = orderService;System.out.println(1);}public void test(){System.out.println(orderService);}
}

执行UserService userService = (UserService) applicationContext.getBean(“UserService”);
输出:1、#{orderService指针编号}

情形3. 写一个无参构造器和一个有参构造器,那么就存在两个构造器

@Service
public class UserService{private OrderService orderService;public UserService(){System.out.println(0);}public UserService(OrderService orderService){this.orderService = orderService;System.out.println(1);}public void test(){System.out.println(orderService);}
}

执行UserService userService = (UserService) applicationContext.getBean(“UserService”);
输出:0、null

情形4. 编写一个有参构造器,那么就存在一个有参构造器

@Service
public class UserService{private OrderService orderService;public UserService(OrderService orderService1,OrderService orderService2){this.orderService = orderService;System.out.println(2);}public void test(){System.out.println(orderService);}
}

执行UserService userService = (UserService) applicationContext.getBean(“UserService”);
输出:2、#{orderService指针编号}

情形5. 编写如下两个有参构造器

@Service
public class UserService{private OrderService orderService;public UserService(OrderService orderService){this.orderService = orderService;System.out.println(1);}public UserService(OrderService orderService1,OrderService orderService2){this.orderService = orderService;System.out.println(2);}public void test(){System.out.println(orderService);}
}

执行UserService userService = (UserService) applicationContext.getBean(“UserService”);
报错!Caused by:com.zhouyu.service.UserService.< init >()

总结

  • 如果没有重写构造方法,那么创建Bean对象就会用无参构造方法;
  • 如果重写了一个有参构造方法,那么创建Bean对象就会用该有参构造方法;
  • 如果重写了两个有参构造方法,那么创建Bean对象不知道用哪个有参构造方法,就会去寻找无参构造方法,如果没有无参构造方法就报错。如果存在无参构造方法就执行无参构造方法。

情形6. 那么有没有办法使用指定的构造方法?通过@Autowired实现

@Service
public class UserService{private OrderService orderService;@Autowired		//指定使用该构造方法,即使存在无参构造器,也要使用这个有参构造器public UserService(OrderService orderService){this.orderService = orderService;System.out.println(1);}public UserService(OrderService orderService1,OrderService orderService2){this.orderService = orderService;System.out.println(2);}public void test(){System.out.println(orderService);}
}

执行UserService userService = (UserService) applicationContext.getBean(“UserService”);
输出:1、#{orderService指针编号}

7. 谁向有参构造器public UserService(OrderService orderService)中传入参数?

  • 当我们创建Bean对象userService时,需要用到有参构造方法,Spring就会在Map中寻找Bean对象OrderService,并且注入到构造方法的参数orderService中。
  • 如果将@Component去掉,那么OrderService就无法成为Bean对象,那么Spring也拿不出Bean对象来传给UserService的有参构造方法的参数orderService,那么orderService就为空。但是Spring规定了不能向有参构造方法的参数传null。所以这种情况运行就会出现报错:No qualifying bean of type ‘com.zhouyu.service.OrderService’

8. Spring怎样从Map<beanName,Bean对象>中,找到合适的Bean对象传入有参构造器的参数中?

  • 如果通过参数的类OrderService找出beanName唯一,那就直接将Bean对象注入orderService。
  • 如果通过参数的类OrderService找出beanName不唯一,那就通过参数名orderService找出唯一的beanName,然后将Bean对象注入orderService参数
  • 如果这样都找不到,那就报错!
  • 如下这三个orderService,orderService1,orderService2分别是可选的beanName,将构造器public UserService(OrderService orderService)的参数orderService改成上述之一即可不报错:expected single matching bean but found 3:orderService,orderService1,orderService2
//1. 注册了一个类型为OrderService,名字为orderService的Bean
public class OrderService{
}//2. 注册了一个类型为OrderService,名字为orderService1的Bean
public class AppConfig{@Beanpublic OrderService orderService1(){}
}

9. 循环依赖

@Component
public class OrderService{private UserService userService;public OrderService(UserService userService){this.userService = userService;}
}
@Service
public class UserService{private OrderService orderService;public UserService(OrderService orderService){this.orderService = orderService;}
}
报错:Is there an unresolvable circular reference?

我们来看一下过程:当我要将UserService创建为Bean对象时,那么就需要传入OrderService的Bean对象给orderService。那么OrderService要提前被Spring创建为Bean对象是不是?那我们看看OrderService要提前被Spring创建为Bean对象也是用到有参构造器,需要传入UserService的Bean对象,那么UserService要提前被Spring创建为Bean对象…这样就陷入了循环了(circular reference)

六、依赖注入

1. 先根据类型type,再根据id

  1. 为Spring容器中注册以下两个类
/*在Spring容器中创建一个bean<bean id="orderService1" class="com.kuang.Service.OrderService"><bean id="orderService2" class="com.kuang.Service.OrderService">*/
@Configuration
@ComponentScan
public class AppConfig{@Beanpublic OrderService orderService1(){return new OrderService();}@Beanpublic OrderService orderService2(){return new OrderService();}}

2. 依赖注入的三种方式(属性、set方法、有参构造)

public class UserService{//1. 属性注入,先根据OrderService类名,再根据orderService1参数名寻找BeanName@Autowiredprivate OrderService orderService1;//2. set方法注入,先根据OrderService类名,再根据orderService参数名寻找BeanName@Autowiredpublic void setOrderService(OrderService orderService1){this.orderService1 = orderService1;}//3. 有参构造器方法注入,先根据OrderService类名,再根据orderService参数名寻找BeanNamepublic UserService(OrderService orderService1){this.orderService = orderService1;}
}

http://www.ppmy.cn/news/18968.html

相关文章

ROS2机器人编程简述humble-第三章-PERCEPTION AND ACTUATION MODELS .1

书中&#xff0c;第三章主题&#xff1a;First Behavior: Avoiding Obstacles with Finite States Machines本节旨在应用到现在为止所展示的一切来创建看似“聪明”的行为。这个练习将介绍的许多东西结合起来&#xff0c;并展示使用ROS2编程机器人的效率。此外&#xff0c;将解…

Python爬虫之Scrapy框架系列(5)——项目实战【某瓣Top250电影所有信息的txt文本存储】

上篇文章已经成功解析提取到豆瓣Top250电影想要的所有数据。下一步就是将其交给管道进行存储。 目录&#xff1a;1. 编写items.py文件&#xff08;定义结构化数据字段&#xff09;2. 爬虫文件里将数据一一对应字段名&#xff1a;3. 将数据返回给管道&#xff1a;4. 编写pipelin…

acwing基础课——快速幂

由数据范围反推算法复杂度以及算法内容 - AcWing 常用代码模板4——数学知识 - AcWing 基本思想&#xff1a; 求一个数的n次时&#xff0c;我们的时间复杂度为O(n),当n特别大时&#xff0c;效率会很低可能超时&#xff0c;此时我们就需要运用到快速幂&#xff0c;将我们的时间…

JavaScript 输出

JavaScript 输出 很遗憾&#xff0c;在javascript中我们找不到输出和打印的函数。 但是我们可以使用其他的方式来显示需要的数据: 我们可以使用 console.log() 在浏览器控制台上面输出内容我们可以使用innerHTML 把内容写入到 HTML 元素。我们可以使用 document.write() 方法…

【进击的算法】基础算法——动态规划

&#x1f37f;本文主题&#xff1a;动态规划 &#x1f388;更多算法&#xff1a;回溯算法 &#x1f495;我的主页&#xff1a;蓝色学者的主页 文章目录一、前言二、概念2.1概念一&#xff1a;状态转移2.2概念二&#xff1a;Dp数组三、例题3.1斐波那契数列3.1.1题目描述3.1.2状态…

客快物流大数据项目(一百零七):物流信息查询服务接口开发解决方案

文章目录 物流信息查询服务接口开发解决方案 一、业务需求 二、系统架构演变 1、​​​​​​​集中式架构 2、​​​​​​​​​​​​​​垂直拆分 3、分布式服务 ​​​​​​​4、面向服务架构&#xff08;SOA&#xff09; 5、微服务架构 ​​​​​​​三、技术…

Tomcat启动的两个问题

文章目录小结问题及解决Address already in use: JVM_BindNo buffer space available &#xff0c;tomcat启动报错参考小结 Tomcat服务碰到两个常见的问题&#xff0c;进行了解决。 问题及解决 Address already in use: JVM_Bind 端口被占用的问题经常会碰到&#xff0c;最…

Allegro如何添加平衡铜操作指导

Allegro如何添加平衡铜操作指导 PCB在加工的时候,工厂会添加平衡铜,Allegro支持自动加上平衡铜,如下图 具体操作如下 选择Manufacture点击Thieving