ioc 容器

组件

几乎整个系统都是由组件构成的

容器

Spring IoC 容器,负责实例化、配置和组装 bean(组件)。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。配置元数据以 XML、Java 注解或 Java 代码形式表现。它允许表达组成应用程序的组件以及这些组件之间丰富的相互依赖关系

例子:

比如在 Servlet 创建 Service 对象,附加事务功能

原始做法

private BookServlet bookServlet = new BookServletImpl();
// 获取数据库连接
Connection conn = ...
try{
    // 关闭事务自动提交
    conn.setAutoCommit(false);
    
    // 执行核心操作
    chain.doFilter(req,resp);
    
    // 提交事务
    conn.commit();
    
}catch(Exception e){

    // 回滚事务
    conn.rollBack();

}finally{

    // 释放数据库连接
    conn.close();

}

使用反转控制方式获取资源

@Autowired
private BookService bookService;

我们只需要声明要什么类型,Spring会自动将我们所需类型的对象注入到这个属性中,而且是已经附加事务操作的、被增强了的BookService对象。对象的创建和事务的增强都与业务功能的开发过程无关,确实能够大大简化业务代码的编写。所以使用框架就是为了让程序员能够尽可能专注于业务的开发,而不必为重复性的操作分散精力

概览

  • ioc 容器

    Spring IoC 容器,负责实例化、配置和组装 bean(组件)核心容器。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令

  • IoC(Inversion of Control)控制反转

    IoC 主要是针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理,即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。

  • DI (Dependency Injection) 依赖注入

    DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。在 Spring 中,DI 是通过 XML 配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter 方法注入和接口注入。

实现

在 spring 实现 ioc 容器,被管理的组件被称为 bean,在创建 bean 之前,首先需要创建 IOC 容器

创建 ioc 两种方法:

  1. BeanFactory接口

    这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

  2. ApplicationContext接口

    BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。

对象 描述
ClassPathXmlApplicationContext 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContext ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力
WebApplicationContext 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中

例如

//创建ioc容器对象,指定配置文件,ioc也开始实例组件对象
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml");
//获取ioc容器的组件对象
PetStoreService service = context.getBean("petStore", PetStoreService.class);
//使用组件对象
List<String> userList = service.getUsernameList();

xml 管理 Bean

初始化项目

导入 spirng 和其他功能依赖

<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.3.1</version>
</dependency>

<!-- junit测试 -->
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>

<!-- 日志 -->
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
</dependency>

<!-- Lombok -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.12</version>
  <scope>provided</scope>
</dependency>

创建一个类

@Slf4j
@Data
public class JinCmp {
  private String name;

  public void doWork() {
    log.debug("component do work ...");
  }
  @Override
  public String toString() {
    return "JinCmp{" +
      "name='" + name + '\'' +
      ", jinCmp1=" + jinCmp1 +
      '}';
  }
}

创建获取bean

创建 spring 配置文件 resources下新建 applicationContext.xml ;文件名自定义,习惯命名:applicationContext

<?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">
<!-- 上面是命名规范约定 -->  
  
  <!--- id为bean的唯一标识 class为要配置bean的文件文字 -->
  <bean id="jinCmp" class="component.JinCmp" />
</beans>

测试是否生效

@Slf4j
public class XmlIocTest {
    // 创建 IOC 容器对象,为便于其他实验方法使用声明为成员变量
  private final ApplicationContext iocContainer = new ClassPathXmlApplicationContext("applicationContext.xml");

  // 获取 bean
  @Test
  public void test1() {
    // 根据id 获取 bean
    JinCmp jinCmp = (JinCmp) iocContainer.getBean("jinCmp");
    // 根据类型获取 bean ,注意 bean 要唯一不然报错
    JinCmp jinCmpClass = iocContainer.getBean(JinCmp.class);
    jinCmp.doWork(); // component do work ...
  }
}

bean 属性赋值

对 bean的属性进行赋值,可在配置文件进行

setter 注入

先在 bean 类里生成 set方法,再进行操作

新增 bean

public class JinCmp1 {
    public String cmpName;

    public String getCmpName() {
        return cmpName;
    }

    public void setCmpName(String cmpName) {
        this.cmpName = cmpName;
    }
}

// JinCmp 添加属性 
private JinCmp1 jinCmp1;

spring 文件配置:

<bean id="jinCmp" class="component.JinCmp">
  <!--   给属性赋值 setter 注入      -->
  <!--  使用 property 给属性赋值
               name 为属性的 名字
               value 为要赋的值
               -->
  <property name="name" value="fantasy"/>
  <!--   使用 ref 给引用的属性赋值另一个bean    -->
  <!--  在原组件的 bean 中引用新组件的 bean -->
  <property name="jinCmp1" ref="jinCmp1"/>
</bean>

<!-- JinCmp1 新增bean -->
<bean id="jinCmp1" class="component.JinCmp1"/>

property 标签属性

  • name :属性名字
  • value:给属性赋的值
  • ref:引用的属性赋值另一个bean,即在原组件的 bean 中引用新组件的 bean

构造器注入

在 bean 类生成构造器

public JinCmp(String name, JinCmp1 jinCmp1) {
  this.name = name;
  this.jinCmp1 = jinCmp1;
}

public JinCmp() {
}

spring 配置文件

<!-- 构造器赋值   -->
<bean id="jinCmpConstructor" class="component.JinCmp">
  <!--  按构造器形参顺序      -->
  <!--  <constructor-arg value="jinCmpConstructor"/>-->
  <!--  <constructor-arg ref="jinCmp1"/>-->
  <!--  构造器属性名      -->
  <!--   <constructor-arg name="name" value="jinCmpConstructor"/>-->
  <!--   <constructor-arg name="jinCmp1" ref="jinCmp1"/>-->
  <!--    按形参索引 从0开始   -->
  <constructor-arg index="0" value="jinCmpConstructor"/>
  <constructor-arg index="1" ref="jinCmp1"/>
</bean>

构造器 三种赋值方法:根据情况选择一种使用即可

  1. 按构造器形参顺序
  2. 构造器属性名
  3. 按形参索引角标 ,从0开始

级联属性赋值

给原组件的bean 引用新组件bean 的属性赋值,可采用级联属性赋值

spring配置文件

<!-- 级联赋值 -->
<bean id="jinCmpCascade" class="component.JinCmp">
  <property name="jinCmp1" ref="jinCmp1"/>
  <!-- 给 jinCmp1 组件的 cmpName 属性赋值为 jinCmp1Cascade  -->
  <property name="jinCmp1.cmpName" value="jinCmp1Cascade"/>
</bean>

内部 Bean

在bean里面配置的bean就是内部bean,内部bean只能在当前bean内部使用,在其他地方不能使用

作用和上面级联属性赋值大致相同

<!--  内部 bean 可对属性赋值  -->
<bean id="innerBean" class="component.JinCmp">
  <property name="jinCmp1">
    <bean class="component.JinCmp1">
      <property name="cmpName" value="InnerCmpName"/>
    </bean>
  </property>
</bean>

引入外部文件赋值

比如使用数据库连接池,用外部文件配置连接信息

导入依赖

<!-- MySQL驱动 -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.28</version>
</dependency>
<!-- 数据源 -->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.0.31</version>
</dependency>

新建数据库配置文件 jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_ex?serverTimezone=UTC
jdbc.user=root
jdbc.password=123456

spring 配置文件使用外部文件的变量

<!--   引用外部文件  -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 给bean的属性赋值:引入外部属性文件 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
  <property name="url" value="${jdbc.url}"/>
  <property name="driverClassName" value="${jdbc.driver}"/>
  <property name="username" value="${jdbc.user}"/>
  <property name="password" value="${jdbc.password}"/>
</bean>

p 命名空间

<!--  p 命名空间  -->
<!--  用 p 名称空间的方式可以省略子标签 property,
将组件属性的设置作为 bean 标签的属性来完成。-->
<bean id="jinCmpP" class="component.JinCmp" p:name="jinCmpP"/>

注:报错 alt+enter 导入p命名空间的约束即可

集合赋值

创建一个包含集合的 bean

public class ListCmp {
  private List<String> nameList;

  public List<String> getNameList() {
    return nameList;
  }

  public void setNameList(List<String> nameList) {
    this.nameList = nameList;
  }

  public ListCmp() {
  }

  public ListCmp(List<String> nameList) {
    this.nameList = nameList;
  }
  @Override
  public String toString() {
    return "ListCmp{" +
      "nameList=" + nameList +
      '}';
  }
}

spring 配置文件

<!--  测试 list 集合  -->
<bean id="listCmp" class="component.ListCmp">
  <property name="nameList">
    <list>
      <value>jay</value>
      <value>范特西</value>
      <value>叶惠美</value>
    </list>
  </property>
</bean>

测试

@Test
public void test10(){
  ListCmp listCmp = (ListCmp) iocContainer.getBean("listCmp");
  List<String> nameList = listCmp.getNameList();
  nameList.forEach(System.out::println); // jay 范特西 叶惠美
}

集合类型 bean

spring 配置文件

<!--    集合类型的bean -->
<util:list id="listBean">
  <bean class="component.ListCmp">
    <property name="nameList" value="jay"/>
  </bean>
  <bean class="component.ListCmp">
    <property name="nameList" value="fantasy"/>
  </bean>
  <bean class="component.ListCmp">
    <property name="nameList" value="hello"/>
  </bean>
</util:list>

测试代码

@Test
public void test11(){
  List<ListCmp> listBean = (List<ListCmp>) iocContainer.getBean("listBean");
  for (ListCmp listCmp : listBean) {
    log.debug(listCmp.toString());
    // ListCmp{nameList=[jay]}
    // ListCmp{nameList=[fantasy]}
    // ListCmp{nameList=[hello]}
  }
}

bean 自动装配

例如 JinController 需要用到 JinService ,自动装配就是一个组件需要其他组件时,由 IOC 容器负责找到那个需要的组件,并装配进去

创建组件

public class JinController {
  private JinService jinService;

  public JinService getJinService() {
    return jinService;
  }

  public void setJinService(JinService jinService) {
    this.jinService = jinService;
  }
}

public class JinService {
}

spring 配置文件

<!-- <bean id="jinCmpAuto" class="component.JinCmp" autowire="byType"/> -->
<bean id="jinCmpAuto" class="component.JinCmp" autowire="byName"/>

自动装配

  • 使用bean标签的autowire属性设置自动装配效果
  • byType表示根据类型进行装配,此时如果类型匹配的 bean 不止一个,那么会抛NoUniqueBeanDefinitionException
  • byName表示根据bean的id进行匹配。而 bean 的 id 是根据需要装配组件的属性的属性名来确定的

测试

@Test
public void test12(){
  JinCmp jinCmp = (JinCmp) iocContainer.getBean("jinCmpAuto");
  JinCmp1 jinCmp1 = jinCmp.getJinCmp1();
  log.debug("jinCmp1 = "+ jinCmp1); //  jinCmp1 = component.JinCmp1@3e8c3cb
}

FactoryBean

应用场景

  • 代理类的创建
  • 第三方框架整合
  • 复杂对象实例化等

创建一个 Factory bean

public class JinFactoryBean implements FactoryBean{
  private  String factoryBeanName;
  
  public String getFactoryBeanName() {
    return factoryBeanName;
  }
  
  public void setFactoryBeanName(String factoryBeanName) {
    this.factoryBeanName = factoryBeanName;
  }
  
  @Override
  public JinCmp getObject() throws Exception {
    // 方法内部模拟创建、设置一个对象的复杂过程
    JinCmp jinCmp = new JinCmp();
    jinCmp.setName(this.factoryBeanName);
    return jinCmp;
  }

  @Override
  public Class<?> getObjectType() {
    // 返回要生产的对象的类型
    return JinFactoryBean.class;
  }
}

对于的 spring 配置文件

<!-- FactoryBean机制 -->
<!-- 这个bean标签中class属性指定的是 JinFactoryBean,
    但是将来从这里获取的bean 是 JinCmp 对象 -->
<bean id="jinFactoryBean" class="component.JinFactoryBean">
  <!-- property标签仍然可以用来通过setXxx()方法给属性赋值 -->
  <property name="factoryBeanName" value="jin"/>
</bean>

测试代码

@Test
public void test13() {
  // 注意: 直接根据声明FactoryBean的id,获取的是getObject方法返回的对象
  JinCmp jinFactoryBean = (JinCmp) iocContainer.getBean("jinFactoryBean");
  log.debug(jinFactoryBean.getName()); //  jin
  JinCmp jinCmp = iocContainer.getBean("jinFactoryBean", JinCmp.class);
  log.debug(jinCmp.getName()); //  jin

  // 如果想要获取FactoryBean对象, 直接在id前添加&符号即可!
  // &jinFactoryBean这是一种固定的约束
  Object bean = iocContainer.getBean("&jinFactoryBean");
  log.debug("bean="+bean); // component.JinFactoryBean@1033576a

}

总结:

如果一个 bean 实现了 FactoryBean 接口,得到的是 getObject() 返回的 bean,而非本身

FactoryBean 可以自定义任何所需的初始化逻辑,生产出一些定制化的 bean

一般情况下,整合第三方框架,都是通过定义FactoryBean实现

BeanFactory:

Spring 框架的基础,其作为一个顶级接口定义了容器的基本行为,BeanFactory 接口提供了访问 bean 的方式。

总的来说,FactoryBean 和 BeanFactory 的区别主要在于前者是用于创建 bean 的接口,它提供了更加灵活的初始化定制功能,而后者是用于管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理

bean的作用域

Spring 中可以通过配置 bean 标签的 scope 属性来指定 bean 的作用域范围

含义 创建对象时机 默认值
singleton 在 IOC 容器中,这个 bean 的对象始终为单实例 IOC 容器初始化时
prototype 这个 bean 在 IOC 容器中有多个实例 获取 bean 对象时
request 在一个请求范围内有效 每次请求
session 在一个会话范围内有效 每次会话

测试作用域

spring 配置文件

<!--  作用域   -->
<bean id="jinCmpScope" scope="prototype" class="component.JinCmp">
  <property name="name" value="jin1"/>
</bean>

测试代码

@Test
public void test14(){
  // 创建多实例
  JinCmp jinCmpScope1 = (JinCmp) iocContainer.getBean("jinCmpScope");
  JinCmp jinCmpScope2 = (JinCmp) iocContainer.getBean("jinCmpScope");

  boolean equals = jinCmpScope1.equals(jinCmpScope2);
  log.debug("equals: "+equals); // false

  log.debug(String.valueOf(jinCmpScope1.hashCode())); // 271800170
  log.debug(String.valueOf(jinCmpScope2.hashCode())); // 809300666
}

bean 生命周期

  • bean 对象创建(调用无参构造器)
  • 给 bean 对象设置属性(调用属性对应的 setter 方法)
  • bean 对象初始化之前操作(由 bean 的后置处理器负责)
  • bean 对象初始化(需在配置 bean 时指定初始化方法)
  • bean 对象初始化之后操作(由 bean 的后置处理器负责)
  • bean 对象就绪可以使用
  • bean 对象销毁(需在配置 bean 时指定销毁方法)
  • IOC 容器关闭

初始化方法和销毁方法

我们可以指定 bean 的初始化方法和销毁方法

组件 JinCmp 加入方法

周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表

public void InitMethod() {
  log.debug("JinCmp初始化");
}

public void DestroyMethod() {
  log.debug("JinCmp销毁");
}

spring 配置文件

<!-- 生命周期   -->
<bean id="jinCmpLife" 
      class="component.JinCmp" 
      init-method="InitMethod"
      destroy-method="DestroyMethod"
      >
  <property name="name" value="jin1"/>
</bean>
  • init-method :设置初始化方法
  • destroy-method:设置销毁方法

bean 的后置处理器

声明一个自定义的bean后置处理器

注意:bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行

@Slf4j
public class JinBeanProcessor implements BeanPostProcessor {
  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    log.debug("Before: " + beanName + "=" + bean);
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    log.debug("After: " + beanName + "=" + bean);
    return bean;
  }
}

spring 配置文件 ,放进 ioc 容器管理

<!--  后置处理器   -->
<bean id="jinBeanProcessor" class="component.JinBeanProcessor"/>

执行顺序

  1. JinCmp创建对象
  2. JinCmp要设置属性了
  3. Before: jinCmpLife=JinCmp{name=‘jin1’, jinCmp1=null}
  4. JinCmp初始化
  5. After: jinCmpLife=JinCmp{name=‘jin1’, jinCmp1=null}
  6. JinCmp销毁

注解管理 Bean

和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作

组件类添加注解

普通组件类

@Component

@Slf4j
@Component
public class JinCmp {
  private String name;
}

控制器组件

@Controller

@Controller
public class JinController {
  private JinService jinService;

  public JinService getJinService() {
    return jinService;
  }

  public void setJinService(JinService jinService) {
    this.jinService = jinService;
  }
}

业务逻辑组件

@Service

@Service
public class JinService {
}

持久化组件

@Repository

这个组件就是我们以前用的Dao类,但是以后我们整合了Mybatis,这里就变成了Mapper接口,而Mapper接口是由Mybatis和Spring的整合包负责扫描的

@Repository
public class JinDao {
}

总结

对于Spring使用IOC容器管理这些组件来说没有区别,也就是语法层面没有区别。所以@Controller、@Service、@Repository 这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。

注意:虽然它们本质上一样,但是为了代码的可读性、程序结构严谨!我们肯定不能随便胡乱标记

包扫描

基本的包扫描

spring 配置文件

<!-- 扫描 component 包下 -->
<context:component-scan base-package="component"/>

测试代码

// 基本包扫描
@Test
public void test1(){
  JinCmp jinCmp = iocContainer.getBean(JinCmp.class);
  JinController jinController = iocContainer.getBean(JinController.class);
  JinService jinService = iocContainer.getBean(JinService.class);
  JinDao jinDao = iocContainer.getBean(JinDao.class);
  log.debug(jinCmp.toString()); // component.JinCmp@6fd83fc1
  log.debug(jinController.toString()); // component.JinController@6fd83fc1
  log.debug(jinService.toString()); // component.JinService@4f2b503c
  log.debug(jinDao.toString()); // component.JinDao@bae7dc0
}

排除指定组件

<!--  排除指定组件  -->
<context:component-scan base-package="component">
  <!-- context:exclude-filter标签:指定排除规则 -->
  <!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 -->
  <!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 -->
  <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

使用 context:exclude-filter 排除组件:

  • type:根据写的类型排除,例如注解 annotation
  • expression:根据排除类型指定排除表达式,例如注解的全类名 org.springframework.stereotype.Controller

扫描指定组件

<!-- 扫描指定组件 -->
<context:component-scan base-package="component" use-default-filters="false">
  <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

use-default-filters="false" 属性:取值false表示关闭默认扫描规则

仅扫描 = 关闭默认规则 + 追加规则

上面关闭默认规制附加扫描 Controller 组件的规制,表示仅仅扫描 Controller 组件

如果 use-default-filters="ture" 则开启默认规制扫描 ,component下都会扫描+扫描 Controller 组件

BeanName

BeanName 相当与我们用 xml 配置 bean 的 id 属性

默认情况:

简单类名首字母小写就是 bean 的 id。

例如:JinController 类对应的 bean 的 id 就是 jinController。

配置 bean 的 id :在注解上添加 value 属性值

例如

@Controller(value = "jin")
public class JinController {
}
// 注解只有 value 值可以省略 value
@Controller("jin")
public class JinController {
}

自动装配

使用场景:

比如 controller 组件需要用到 service 组件,service 组件需要用到 dao 组件

同时在各个组件中声明要调用的方法

代码

controller 组件

@Controller
public class JinController {
  private JinService jinService;

  public void getMsg(){
    jinService.getMsg();
  }
}

service 组件

@Service
public class JinService {
    private JinDao jinDao;
    public void getMsg() {
        jinDao.getMsg();
    }
}

dao 组件

@Repository
@Slf4j
public class JinDao {
  public void getMsg() {
    log.debug("hello,spring!!");
  }
}

使用自动装配

前提:所需组件都在 ioc 容器管理

使用:@Autowired注解

在成员变量上直接标记 @Autowired 注解即可,不需要提供 setXxx() 方法

@Controller
public class JinController {
  // 装配 service
  @Autowired
  private JinService jinService;

  public void getMsg(){
    jinService.getMsg();
  }
}
@Service
public class JinService {
  	// 装配 dao 
    @Autowired
    private JinDao jinDao;
    public void getMsg() {
        jinDao.getMsg();
    }
}

@Autowired 注解可标注到其他地方(需要装配其他组件的地方):构造器,set 方法

自动装配工作流程

首先根据所需要的组件类型到 IOC 容器中查找

  • 能够找到唯一的 bean:直接执行装配
  • 如果完全找不到匹配这个类型的 bean:装配失败
  • 和所需类型匹配的 bean 不止一个
    • 没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配
      • 能够找到:执行装配
      • 找不到:装配失败
    • 使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配
      • 能够找到:执行装配
      • 找不到:装配失败
@Controller
public class JinController {
  @Autowired
  // 根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配
  @Qualifier(value = "JinService2")
  private JinService jinService;
}
佛系装配

给 @Autowired 注解设置 required = false 属性表示:能装就装,装不上就不装。但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性。

理解JSR系列注解

JSR-250 @Resource注解

  • @Resource: 标识一个需要注入的资源,是实现Java EE组件之间依赖关系的一种方式
  • @Resource 注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案
  • @Autowired 注解是 Spring框架自己的
  • @Resource 注解默认根据Bean名称装配,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型装配
  • @Autowired 注解默认根据类型装配,如果想根据名称装配,需要配合@Qualifier注解一起用
  • @Resource 注解用在属性上、setter方法上
  • @Autowired 注解用在属性上、setter方法上、构造方法上、构造方法参数上。
  • @Resource 注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:高于JDK11或低于JDK8需要引入以下依赖 jakarta.annotation-api

外部文件赋值

使用 @Value 用于注入外部化属性

创建文件 application.properties

jin.msg = hello,spring
//@PropertySource导入配置文件 ,也可以在spring配置导入
@PropertySource("classpath:application.properties")
public class JinController {
  private JinService jinService;

  /**
   * 情况1: ${key} 取外部配置key对应的值!
   * 情况2: ${key:defaultValue} 没有key,可以给与默认值
   */
  @Value("${jin.msg:haha}")
  private String name;
  
  public String getName() {
    return name;
  }
}

测试

@Test
public void test2(){
  JinController jinController = iocContainer.getBean(JinController.class);
  System.out.println(jinController.getName()); // hello,spring
}

配置类管理 Bean

Spring 完全注解配置(Fully Annotation-based Configuration)是指通过 Java配置类 代码来配置 Spring 应用程序,使用注解来替代原本在 XML 配置文件中的配置。相对于 XML 配置,完全注解配置具有更强的类型安全性和更好的可读性。

创建一个 spring 配置类

@Configuration 注解将一个普通的类标记为 Spring 的配置类。

@Configuration
@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})
@ComponentScan(basePackages = {"component"})
public class MyConfiguration {
}

@Configuration:标注当前类是配置类,替代application.xml

@PropertySource:导入外部文件替代 context:property-placeholder 标签

@ComponentScan:扫描基础组件 替代 context:component-scan标签

实例化容器

@Test
public void test3() {
  // AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
  ApplicationContext iocContainer =
    new AnnotationConfigApplicationContext(MyConfiguration.class);

  // 无参创建
  AnnotationConfigApplicationContext iocContainer1 = new AnnotationConfigApplicationContext();
  //外部设置配置类
  iocContainer1.register(MyConfiguration.class);
  //刷新后方可生效!!
  iocContainer1.refresh();
}

定义 Bean 组件

使用 @Bean 来定义组件

@Configuration
@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})
@ComponentScan(basePackages = {"component"})
public class MyConfiguration {
  // @Bean 注解相当于 XML 配置文件中的 bean 标签
  // @Bean 注解标记的方法的返回值会被放入 IOC 容器
  // 默认以方法名作为 bean 的 id
  @Bean
  public JinCmp getJinCmp() {
    JinCmp cmp = new JinCmp();
    cmp.setName("created by annotation config");
    return cmp;
  }
}

测试

@Test
public void test4(){
  //   AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
  ApplicationContext iocContainer =
    new AnnotationConfigApplicationContext(MyConfiguration.class);

  JinCmp jinCmp = iocContainer.getBean("getJinCmp",JinCmp.class);
  String name = jinCmp.getName();
  log.debug(name); // created by annotation config
}

@Bean生成BeanName问题

@Bean 源码

public @interface Bean {
  //前两个注解可以指定Bean的标识
  @AliasFor("name")
  String[] value() default {};
  @AliasFor("value")
  String[] name() default {};

  //autowireCandidate 属性来指示该 Bean 是否候选用于自动装配。
  //autowireCandidate 属性默认值为 true,表示该 Bean 是一个默认的装配目标,
  //可被候选用于自动装配。如果将 autowireCandidate 属性设置为 false,则说明该 Bean 不是默认的装配目标,不会被候选用于自动装配。
  boolean autowireCandidate() default true;

  //指定初始化方法
  String initMethod() default "";
  //指定销毁方法
  String destroyMethod() default "(inferred)";
}

可指定 bean 的名称

@Configuration
public class AppConfig {
  @Bean("myThing") //指定名称
  public Thing thing() {
    return new Thing();
  }
}

@bean 注解没有指定名称时默认和方法名一样

@Configuration
public class AppConfig {
  @Bean
  public Thing thing() {
    return new Thing();
  }
}

// 省略部分代码
// 测试获取
iocContainer.getBean("thing",Thing.class);

指定初始化和销毁的方法

@Bean 注解支持指定任意初始化和销毁回调方法,非常类似于 Spring XML 在 bean 元素上的 init-methoddestroy-method 属性

public class BeanOne {

  public void init() {
    // initialization logic
  }
}

public class BeanTwo {

  public void cleanup() {
    // destruction logic
  }
}



@Configuration
public class AppConfig {

  @Bean(initMethod = "init")
  public BeanOne beanOne() {
    return new BeanOne();
  }

  @Bean(destroyMethod = "cleanup")
  public BeanTwo beanTwo() {
    return new BeanTwo();
  }
}

initMethod :初始化方法赋值

destroyMethod:销毁方法赋值

作用域

可以指定使用 @Bean 注释定义的 bean 应具有特定范围。您可以使用在 Bean 作用域部分中指定的任何标准作用域。

默认作用域为 singleton ,但您可以使用 @Scope 注释覆盖此范围

@Configuration
public class MyConfiguration {

  @Bean
  @Scope("prototype")
  public Encryptor encryptor() {
    // ...
  }
}

组件间依赖

场景:controller 组件依赖 service 组件

@Data
public class JinController {
  private JinService jinService;

  public void getMsg(){
    jinService.getMsg();
  }
}
@Data
public class JinService {
  public void getMsg() {
    System.out.println("hello spring");
  }
}

java 配置类:实现方法一:

@Configuration
public class JavaConfig {

    @Bean
    public JinService jinService(){
        return new JinService();
    }

    @Bean
    public JinController jinController(){
        JinController jinController = new JinController();
        //直接调用方法即可! 
        jinController.setJinService(jinService());
        return jinController;
    }

}

实现方法二:

@Configuration
public class JavaConfig {

    @Bean
    public JinService jinService(){
        return new JinService();
    }

    @Bean
    public JinController jinController(JinService jinService){
        JinController jinController = new JinController();
        //直接调用方法即可! 
        jinController.setJinService(jinService);
        return jinController;
    }

}

可以直接在形参列表接收 IoC 容器中的Bean!

  • 情况1: 直接指定类型即可
  • 情况2: 如果有多个bean,(HappyMachine 名称 ) 形参名称等于要指定的bean名称!

@Import 注解

@Import 注释允许从另一个配置类加载 @Bean 定义

@Configuration
public class ConfigA {

  @Bean
  public A a() {
    return new A();
  }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

  @Bean
  public B b() {
    return new B();
  }
}

测试

@Test
public void test4 {
  ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

  // now both beans A and B will be available...
  A a = ctx.getBean(A.class);
  B b = ctx.getBean(B.class);
}

参考资料