面试阿里,字节,美团必看的Spring的Bean管理详解

常雕
• 阅读 5137

IOC容器

工厂只负责创建对象,而Spring当然不仅仅是一个对象工厂,其核心是一个对象容器,其具备控制反转的能力,所以也称为IOC容器。

帮助我们存放对象,并且管理对象,包括:创建、销毁、装配,这样就将原本由程序自己完成的工作,交给了框架来完成,称为IOC容器。

学习的核心也就在于如何将对象放在Spring中,以及如何从Spring中取出来。

Spring有两个容器接口:

ApplicationContext
BeanFactory
ApplicationContext是BeanFactory的子接口,它们都可以作为Spring的容器。

区别:

BeanFactory采取懒加载方式(会有延迟),在获取对象时才会实例化
ApplicationContext在工厂初始化时立即实例化对象
BeanFactory作为顶级接口主要面向于Spring框架本身,仅提供了基本的容器功能,如DI
ApplicationContext时BeanFactory的子接口,意味着功能比BeanFactory更多,诸如国际化(根据用户的地理位置对产品的表现形式进行一些变化,例如文字、样式),注解配置,XML配置等等,因此ApplicationContext使用更多
ApplicationContext的个实现类的区别:
ClassPath表示从类路径获取配置文件
FileSystem表示从文件系统获取配置文件

Spring Bean管理

Bean的实例化(初始化,相当于我们之前的new)

1.创建一个maven项目
2.配置maven

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <!-- Maven会自动下载所有Spring核心容器和aop的依赖-->
    </dependencies>

3.在resource下创建一个名为applicationContext.xml的配置文件
面试阿里,字节,美团必看的Spring的Bean管理详解

自动生成:在这里配置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">

</beans>

4.创建contoller、service对应的包
面试阿里,字节,美团必看的Spring的Bean管理详解

准备工作已完成,下面书写Bean实例化的方式

方式一 使用无参构造器

使用该方式时,Bean类中必须要有无参构造

<!--使用无参构造实例化Bean-->
    <bean id="userService" class="cx.service.UserServiceImpl" />

测试

public class UserController {
    private UserService service;
    public UserController() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //通过指定文件路径加载Spring配置文件,一般很少用
        //FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("D:\\ideaProject\\SpringDemo2\\src\\main\\resources\\applicationContext.xml");
        
        //根据id或name获取
        service = (UserService) context.getBean("userService");
        //通过接口类来获取
        //service = context.getBean(UserService.class);
        //通过具体类来获取
        //service = context.getBean(UserServiceImpl.class);
    }
    public void say(){
        service.say();
    }

    public static void main(String[] args) {
        new UserController().say();
    }
}

方式二 使用静态工厂方式

创建一个工厂类

public class factory {
    public static UserService getService(){
        System.out.println("执行了静态工厂方法");
        return new UserServiceImpl();
    }
}

xml配置

<!--使用静态工厂方式-->
    <bean id="userService2" class="cx.service.factory" factory-method="getService"/>

方式三 使用实例工厂方式

xml文件

<!--通过实例工厂方式  工厂Bean-->
    <bean id="factory" class="cx.service.factory" />
    <!--配置userService 调用factory 指定getService2 指定使用的工厂对象时哪一个-->
    <bean id="userService3" factory-bean="factory" factory-method="getService2" />

调用:

service = (UserService) context.getBean("userService3");

首先需配置工厂类,然后通过factory-bean调用刚刚命名的那个Bean,写入id,用factory-method指定要使用的工厂的方法。

Bean的命名

配置Bean时,可以使用 id 或者 name 属性给bean命名。 id 和 name 属性作用上一样,推荐使用id。

id取值要求严格些,必须满足XML的命名规范。id是唯一的,配置文件中不允许出现两个id相同的bean。

name取值比较随意,甚至可以用数字开头。在配置文件中允许出现多个name相同的bean,在用getBean()返回实例时,最后的一个Bean将被返回。

注意:在spring5中name和id一样也不允许有重复的名称。

如果没有id,name,则默认使用类的全名作为name,如 ,<bean class="com.cx.service.UserService"/>可以使用 getBean(“com.cx.service.UserService”)返回该实例。

如果存在多个id和name都没有指定,且类都一样的,如:

则可以通过getBean(“完整类名#索引”)来获得,如:getBean(“com.cx.service.UserService#1”),索引从0开始,若要获取第一个则可以忽略索引,直接写类名。

name中可以使用分号(“;”)、空格(“ ”)或逗号(“,”)来给这个Bean添加多个名称(相当于别名 alias 的作用)。如:name=“a b c d”等同于 name=“a,b,c,d” 这样写相当于有 1 2 3 4(4个)个标识符标识当前bean ,而id中的任何字符都被作为一个整体 ;

如果既配置了 id ,也配置了 name ,则两个都生效。当然也不能重复;

当注解中出现与xml配置中相同的id或相同name时,优先使用xml中的配置

Bean的作用域

类别 说明
面试阿里,字节,美团必看的Spring的Bean管理详解

作用域就是指作用范围:

单例则表示对象的作用范围是整个Spring容器,

而prototype则表示不管理作用范围,每次get就直接创建新的。

request、session和 application三种作用域仅在基于web的应用中使用

生命周期

init和destory

Spring提供了非入侵(不强制类继承或实现)方式的生命周期方法,可以在Bean的初始化以及销毁时做一些额外的操作

<!--init-method  用于初始化操作的方法
        destroy-method  用于销毁操作的方法
        这两个都是指定方法名-->
    <bean id="userService" class="cx.service.UserServiceImpl" scope="singleton" init-method="init" destroy-method="destroy"/>
    

注意:
destroy仅仅在scope为singleton时有效

Bean的完整生命周期

面试阿里,字节,美团必看的Spring的Bean管理详解

1 构造对象

2 设置属性

3 了解Bean在容器中的name

4 了解关联的beanFactory

5 初始化前处理

6 属性设置完成

7 自定义初始化方法

8 初始化后处理

9 业务方法

10 Bean销毁方法

11 自定义销毁方法

依赖注入

依赖指的是档期啊对象在运行过程中需要使用到的其他参数或者对象;

Spring可以帮助我们完成这个依赖关系的建立;

说的简单点即把你需要的参数给你,而你不管参数是怎么来的,只管用即可,以达到尽可能地解耦。

for example

controller中需要service对象,Spring可以把service自动丢到controller中,你不需要关注service对象是怎么来的,用就可以了

要使用依赖注入,必须先在需要依赖的一方(controller)中为被依赖的一方(service)定义属性,用于接收注入;

构造方法注入

bean:
书写user

public class User {
    private Integer id;
    private String name;
    private String sex;
    private PC pc;
}

还有有参/无参构造,getter/setter方法,以及toString
user中需要的pc类

public class PC {
    private String model;
}

在applicationContext.xml中注入,构造方法注入

 <!--依赖注入构造方法注入-->
    <bean id="user" class="cx.pojo.User">
        <!--按参数名称注入-->
        <constructor-arg name="id" value="1"/>
        <!--按参数位置注入-->
        <constructor-arg index="1" value="张三"/>
        <constructor-arg index="2" value="男" />
        <!--参数类型为其他bean对象时,value换为ref-->
        <constructor-arg index="3" ref="pc"/>
    </bean>
    <bean id="pc" class="cx.pojo.PC">
        <constructor-arg index="0" value="小米"/>
    </bean>

测试代码:

 public void test1() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = (User) context.getBean("user");
        System.out.println(user);
    }

    public static void main(String[] args) {
        new TestController().test1();
    }

setter方法注入

在上述中,还可以使用下述注入

 <!--使用setter方法注入(属性注入)-->
    <bean id="user2" class="cx.pojo.User">
        <property name="id" value="2"/>
        <property name="name" value="李四"/>
        <property name="sex" value="男"/>
        <property name="pc" ref="pc" />
    </bean>

注意:setter方法注入配置要求User中必须有无参构造方法

C和P命名标签

上面的两种依赖注入方法,如果在需要注入的依赖较多时导致xml显得很臃肿,C名称空间来简化xml中标签的书写。

需要先在xml中进行声明

xmlns:c="http://www.springframework.org/schema/c"

面试阿里,字节,美团必看的Spring的Bean管理详解

使用:

    <!--c命名空间的使用-->
    <!--c:id              指定为id参数赋值
        c:_1                指定为构造函数的第2个参数赋值
        c:p-ref   指定为构造函数的pc参数赋值为id为"pc"的Bean    -->
    <bean id="user3" class="cx.pojo.User" c:id="3" c:_1="王五" c:_2="男" c:pc-ref="pc" />

同理p命名标签也需要在xml中进行声明

xmlns:p="http://www.springframework.org/schema/p"

使用:

<!--p:id             指定为id属性赋值
        p:name           name属性赋值
        p:sex           sex属性赋值
        p:pc-ref           为pc属性赋值为id为"pc"的Bean-->
<bean id="user4" class="cx.pojo.User" p:id="4" p:name="赵六" p:sex="女" p:pc-ref="pc"/>
  

SpEL注入

SpEL即Spring Expression Language的缩写,与JSTL一样是表达式语言,可以支持使用更加复杂的语法注入依赖,包括标准数学运算符,关系运算符,逻辑运算符,条件运算符,集合和正则表达式等;

语法: #{表达式}

用例:

<!--SpEL    -->
<bean id="user" class="com.cx.pojo.User">
<!--<property name="name" value="#{'cx'}"/>-->           <!--字符常量-->
<!--<property name="age" value="#{100.0}"/>-->              <!--数字常量-->
<!--<property name="phone" value="#{phone}"/>-->            <!--对象引用-->
<!--<property name="name" value="#{phone.model.concat(' cx')}"/>--> <!--方法调用-->
<!--<property name="age" value="#{1+100}"/>-->              <!--算数符-->
<!--<property name="name" value="#{1>100}"/>-->       <!--比较符-->
<!--<property name="name" value="#{true or false}"/>-->     <!--逻辑符-->
<!--<property name="name" value="#{1 > 0?1:0}"/>-->         <!--三目-->
</bean>

容器类型的注入

xml:

<bean id="userdemo" class="com.lbb.pojo.UserDemo">
       <!--注入set-->
       <property name="set">
           <set>
               <value>1</value>
               <value>2</value>
               <value>3</value>
               <value>4</value>
           </set>
       </property>
       <!--注入list-->
       <property name="list">
           <list>
               <value>1</value>
               <value>2</value>
               <value>3</value>
               <value>4</value>
           </list>
       </property>
       <!--注入map-->
       <property name="map">
           <map>
               <entry key="1" value="1"/>
               <entry key="2" value="2"/>
               <entry key="3" value="3"/>
               <entry key="4" value="4"/>
           </map>
       </property>
       <!--注入properties-->
       <property name="properties">
           <props>
               <prop key="1">1</prop>
               <prop key="2">2</prop>
               <prop key="3">3</prop>
               <prop key="4">4</prop>
           </props>
       </property>
   </bean>

上述写法同样适用于构造函数注入:

<bean class="service.Person">
        <constructor-arg name="name" value="jerry"/>
        <constructor-arg name="properties">
            <props>
                <prop key="pwd">123</prop>
            </props>
        </constructor-arg>
  </bean>

强调:java Spring的依赖注入要么通过构造函数,要么通过setter

接口注入不是一种注入方式,只不过由于OOP的多态,Spring在按照类型注入时,会在容器中查找类型匹配的Bean,如果没有则查找该类的子类,如果容器中有多个匹配的子类Bean时会抛出异常。

注解配置

注解配置Bean
通用注解

​ @Component 用于在Spring中加入Bean

MVC场景下

@Controller 等价于 @Component 标注控制层

@Service 等价于 @Component 标注业务层

@Repository 等价于 @Component 标注数据访问层(DAO)

在实现上暂时没有任何不同,在底层源码里就是给Component取了别名,仅仅是为了对Bean进行分层是结构更清晰

使用步骤:

1.需要依赖context和aop两个jar包
2.配置applicationContext.xml
添加命名空间(idea下 输入<context:可自动补全,指定扫描的注解所在的包:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd           
       http://www.springframework.org/schema/context 
       https://www.springframework.org/schema/context/spring-context.xsd">
        
        <!--指定要扫描注解的包 会自动扫描子包   -->
        <context:component-scan base-package="com.cx.pojo"/>

</beans>

要注册的Bean

@Component
public class User {
    private Integer id;
    private String name;
    private String sex;
    private PC pc;
}

测试代码

public void test1() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = (User) context.getBean("user");
        System.out.println(user);
    }

如果注解中没有指定id,则默认使用简单类名且小写开头,例如:上述的user。

注解注入
@value用于对基本类型的注入
@Autowired将容器中的其他Bean注入到属性中,自动装配
@Qualifier(“BeanId”)指定要注入的Bean的Id,需要和Autowired一起使用
@Resource 相当于: @Autowired 和@Qualifier 一起使用.
@value

    @Value("12")
    private int age;
    @Value("tom")
    private String name;
    @Value("#{1+2+3}")
    private String pwd;
    @Value("${jdbc.user}") //注入属性配置
   private String user;

上述注入属性配置使用${}取配置文件里面的值:

首先需要加载配置文件,在applicationContext.xml加入:

<context:property-placeholder location="jdbc.properties"/>

@Autowired
自动注入一个Bean,默认按照类型来注入
required属性用于设置属性是否是必须的默认为true,如果为true找不到会报错,如果false则会为空,不会报错

使用:
User 类:

@Component
public class User {
    @Value("1")
    private Integer id;
    @Value("cc")
    private String name;
    @Value("男")
    private String sex;
    @Value("小米")
    private PC pc;
}

dao层:

@Repository
public class PersonDao {
    public void smile(){
        System.out.println("ta笑了");
    }
}

service实现层:

@Service
public class PerSonServiceImpl implements PersonService {
    @Autowired
    private PersonDao personDao;
    public void smile(){
        personDao.smile();
    }
}

接口层

public interface PersonService {
    public void smile();
}

controller层

@Controller
public class PersonController {
    @Autowired
    private PersonService personService;

    public void smile() {
        personService.smile();
    }
}

测试:

@Test
    public void test1() {
       ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        PersonController bean = context.getBean(PersonController.class);
        bean.smile();
    }        

@Qualifier
当我们需要指定注入的bean的id时,Autowired不能配置,这时候要和Qualifier一起使用,用于明确指定要注入的Bean的ID(注意一定要一起使用)

使用:

上面PersonService可以这样写:

@Service
public class PerSonServiceImpl implements PersonService {
    @Autowired
    @Qualifier("personDaoImpl")
    private PersonDao personDao;
    public void smile(){
        personDao.smile();
     }
}

注意:

若通过类型注入,则当Spring中存在多个类型都匹配的Bean时直接报错,演示如下:

接口:

public interface PersonDao {
}

两个实现类:

@Repository()
public class PersonDaoImpl1 implements PersonDao{
}
@Repository()
public class PersonDaoImpl2 implements PersonDao{
}

3
注入:

@Component
public class UserService {
    @Autowired
    private PersonDao personDao;
}

这时候就会报错,如果要这样用必须指定Bean的name,通过name来区分。

@Resource

Qualifier和Autowired书写繁琐,@Resource可将两个标签的功能整合,即注入指定ID的Bean:

@Resource标准注解是java注解,但是在jdk中没有导入,要配置导入,在pom.xml配置:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

注解:
Resource默认按照使用属性名称作为name查找,查找失败则使用类型查找

可以利用name属性指定要查找的id

也可通过type指定类型,当出现相同类型的多个Bean时抛出异常

@Service
public class PerSonServiceImpl implements PersonService {
    @Resource(name = "personDaoImpl")
    private PersonDao personDao;
    public void smile(){
        personDao.smile();
     }
}

Resource可以设置name,也可以设置type.如果不设置的话,会先按照类型找,找不到会再按照属性名来寻找。

Scope

用于标注Bean的作用域

@Repository()
@Scope("prototype") //每次get都创建新的 
public class PersonDao {
    public void smile(){
        System.out.println("笑了");
    }
}

XML与注解配合使用

因为注解的表达能力有限,很多时候无法满足使用需求;我们可以将注解和XML配合使用,让XML负责管理Bean,注解仅负责依赖注入;

与注解扫描的不同之处,注解扫描用于将Bean放入容器中同时进行依赖注入,而后者仅仅是在已存在的Bean中进行依赖注入;

配置applicationContext.xml文件,开启注解:

<!--使用xml默认是不扫描注解的,所以要配置下面开启注解-->
        <context:annotation-config/>

多配置文件的使用:

当配置文件中的内容过多是不便于维护,Spring支持多配置文件

方法1:

在初始化容器时指定要加载的所有配置文件

 @Test
    public void test3(){
        //读取配置文件
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml","applicationContext2.xml");
        //获取bean
        PersonController personController= (PersonController) context.getBean("personController");
        personController.smile();
    }

方法2:

在配置文件中使用import来引入其他的配置文件

<import resource="applicationContext2.xml"/>

import 里的其他写法:

前缀 含义
classpath: 从classpath加载第一个匹配的文件
classpath*: 从classpath加载所有匹配的文件(用于成功分为多个jar包时)
file: 按绝对路径加载文件
http: 按URL路径加载文件
最后来一波~~

Spring的Bean管理的方式比较

基于XML配置

Bean的实例化:<bean id="" class="" />
Bean的命名:通过id或name指定
Bean的注入:构造方法或setter方法注入(p或c命名标签)
生命周期,Bean的作用范围:init-method,destroy-method,围scope属性,destroy仅仅在scope为singleton时有效
使用场景:Bean来自于第三方
基于注解配置
Bean的实例化:@component、@Controller、@Service、@Repository
Bean的命名:@component(“name")
Bean的注入:@AutoWired按类型注入、@Qualifier按名称注入
生命周期,Bean的作用范围:@PostConstruct初始化、@PreDestroy销毁、@Scope设置作用范围
使用场景:Bean的实现由自己开发

点赞
收藏
评论区
推荐文章
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
java面试题:Spring
Spring面试时,最好能结合底层代码说出IOC,AOP或SpringMVC的流程,能说出拦截器的底层。如果看过Spring的源码,并能结合设计模式表达,是很大的加分项。IOCQ:讲一下IOCIOC是"控制反转"。IOC将对象的控制权进行分离,交由第三方进行控制。IOC容器负责创建对象,管理对象.详情参见:https:/
Wesley13 Wesley13
4年前
java框架篇
IOC(DI):其实这个Spring架构核心的概念没有这么复杂,更不像有些书上描述的那样晦涩。java程序员都知道:java程序中的每个业务逻辑至少需要两个或以上的对象来协作完成,通常,每个对象在使用他的合作对象时,自己均要使用像newobject()这样的语法来完成合作对象的申请工作。你会发现:对象间的耦合度高了。而IOC的思想是:Spring容器来实
Easter79 Easter79
4年前
SpringIOC官方文档解读
IoC容器本章介绍了Spring的控制反转(IoC)容器。1.1。SpringIoC容器和Bean简介本章介绍了反转控制(IoC)原则的Spring框架实现。IoC也称为依赖注入(DI)。在此过程中,对象可以通过①构造函数参数(),②工厂方法的参数③或在构造或从工厂方法返回后在对象实例上设置的属性来定义其依
Stella981 Stella981
4年前
Spring Ioc之BeanFactory
   IoC(InversionofControl),即所谓的控制反转,就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器(BeanFactory)负责的。   Spring通过配置文件(或者注解、JavaConfig)描述Bean和Bean之间的依赖关系,利用反射机制实例化Bean并建立Bean之间的依赖关系。Spr
Easter79 Easter79
4年前
Spring系列(五) 容器初始化过程源码
IoC/DI的概念容器是Spring的核心之一(另一个核心是AOP).有了容器,IOC才可能实现.什么使IoC?IoC就是将类自身管理的与其由依赖关系的对象的创建/关联和管理交予容器实现,容器按照配置(比如xml文件)来组织应用对象的创建和关联.什么使DI?DI是IoC的实现方式,由容器
Wesley13 Wesley13
4年前
IOC的使用
1.概要:在spring中,都是使用反射机制创建对象,将创建对象的权利交给spring容器,就是控制反转(ioc)对象创建方式有参构造无参构造工厂模式(静态,非静态)2.创建IDEA控制台测试项目!(https://oscimg.oschina.net/oscnet/a77ea6a2907139751d63
Wesley13 Wesley13
4年前
JAVA日常记录
11.IOC的优点是什么IOC(InversionofControl)控制反转,将控制权(创建对象和对象之间的依赖关系的权利)交给spring容器。接口驱动设计(InterfaceDrivenDesign)的好处,可以灵活提供不同的子类实现(其实就是解耦),提高程序的灵活性、可扩展性和可维护性。IOC模式将耦合代
Stella981 Stella981
4年前
Spring 学习笔记(二):SpringIoC
1IoC与DIIoC是InversionofControl的简称,也就是控制反转。通常来说,创建对象需要调用者手动创建,也就是newXXX()的方式。当Spring框架出现后,对象的实例不再由调用者创建,而是由Spring容器创建,这样控制权就由调用者转移到Spring容器,控制权发生了反
Easter79 Easter79
4年前
Spring源码阅读——3
SpringIoC容器1、IoC容器概述控制反转、IoC容器、依赖注入控制反转:不是什么技术,而是一种设计思想。Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。IoC容器:IoC容器控制了对象的
Easter79 Easter79
4年前
Spring的IOC逐层深入——源码解析之IoC的根本BeanFactory(五)
什么是IoC    IoC容器,最主要的就是完成对象的创建以及维护对象的依赖关系等。    所谓控制反转,包括两部分:一是控制,二是反转,就是把传统方式需要由代码来实现对象的创建、维护对象的依赖关系,反转给容器来帮忙管理和实现。所以我们必须要创建一个容器,同时需要一种描述来让容器创建对象与对象的关系。    Io
常雕
常雕
Lv1
一度失去的东西怎么可能再拥有.
文章
3
粉丝
0
获赞
0