1.Spring IOC 容器

IoC 也称为依赖注入 (DI)。这是一个过程,其中对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖项(即,它们使用的其他对象) . 然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身的逆过程(因此得名,控制反转),通过使用类的直接构造或诸如服务定位器模式之类的机制来控制其依赖项的实例化或位置。

在org.springframework.beans和org.springframework.context包是Spring框架的IoC容器的基础。该 BeanFactory 接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContext 是 的子接口BeanFactory,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多企业特定的功能。

org.springframework.context.ApplicationContext接口代表 Spring IoC 容器,负责实例化、配置和组装 bean。容器通过读取配置元数据来获取有关要实例化、配置和组装哪些对象的指令。配置元数据以 XML、Java 注解或 Java 代码表示。它可以让您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。

ApplicationContext接口有ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext。它们的作用如下

  • ClassPathXmlApplicationContext:根据XML文件的路径获取IOC容器,ClassPathXmlApplicationContext用于获取项目下XML配置文件构造IOC容器。
  • FileSystemXmlApplicationContext:根据文件路径构造IOC容器,FileSystemXmlApplicationContext不仅可以使用项目下XML配置文件路径构造IOC容器,也可以使用磁盘上的文件路径构造IOC容器。
  • AnnotationConfigApplicationContext:通过Java注解形式构造IOC容器。

    1.1 ClassPathXmlApplicationContext

    (1).创建一个类作为将要注入的Bean: ```java // com.example.User package com.example;

public class User { public String userName; public Integer sex; public String address;

  1. public User(){
  2. }
  3. public User(String userName, Integer sex, String address) {
  4. this.userName = userName;
  5. this.sex = sex;
  6. this.address = address;
  7. }
  8. public String getUserName() {
  9. return userName;
  10. }
  11. public void setUserName(String userName) {
  12. this.userName = userName;
  13. }
  14. public Integer getSex() {
  15. return sex;
  16. }
  17. public void setSex(Integer sex) {
  18. this.sex = sex;
  19. }
  20. public String getAddress() {
  21. return address;
  22. }
  23. public void setAddress(String address) {
  24. this.address = address;
  25. }

}

(2).创建xml文件。在resources目录下创建spring.xml,ClassPathXmlApplicationContext默认以resources目录作为基础路径。
```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">
    <bean id="user" class="com.example.User">
        <property name="userName" value="z乘风"/>
        <property name="sex" value="0"/>
        <property name="address" value="中国"/>
     </bean>
</beans>

(3).测试。

package com.example;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * ApplicationContext表示IOC容器,
 */

public class Test01 {
    public static void main(String[] args) {
        //获取项目classpath xml配置文件构造IOC容器
        ApplicationContext classPathContext=new ClassPathXmlApplicationContext("spring.xml");
        //根据Bean id 获取Bean
        User user =(User)classPathContext.getBean("user");
        System.out.println(user.getUserName()); // 结果打印:z乘风
    }
}

1.2 FileSystemXmlApplicationContext

将spring.xml移动至/App目录下,测试代码如下:

package com.example;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;


public class Test02 {
    public static void main(String[] args) {
        /*
            获取配置文件路径构造IOC容器,若不加file:前缀,FileSystemXmlApplicationContext默认
            会以当前项目作为基础路径,例如:/study/java/spring/spring-example01/App/spring.xml
         */
        ApplicationContext classPathContext=new
                FileSystemXmlApplicationContext("file:/App/spring.xml");
        //根据Bean id 获取Bean
        User user =(User)classPathContext.getBean("user");
        System.out.println(user.getUserName()); // z乘风
    }
}

1.3 AnnotationConfigApplicationContext

�(1).创建配置类。

package com.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfig {

    //自定义注入Bean名称
    @Bean("initUser01")
    public User initUser01(){
        User user = new User();
        user.setUserName("大黄");
        user.setSex(1);
        user.setAddress("土狗村");
        return user;
    }

    @Bean
    public User initUser02(){
        User user = new User();
        user.setUserName("小黄");
        user.setSex(0);
        user.setAddress("土狗村");
        return user;
    }
}

(2).测试类

package com.example;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test03 {
    public static void main(String[] args) {
        /**
         * AnnotationConfigApplicationContext接收一个或多个包基础路径作为参数,
         * AnnotationConfigApplicationContext会扫描该包下的所有配置类
         */
        ApplicationContext context=
                new AnnotationConfigApplicationContext("com.example");
        // 根据类全限定名获取Bean,无需强转型
        UserConfig dogUser=context.getBean(UserConfig.class);
        System.out.println(dogUser.initUser01().getUserName());// 大黄
        System.out.println(dogUser.initUser02().getUserName());// 小黄
    }
}

�2.Bean的注入与装配

Spring中Bean的其实就是一个普通的Java对象,其核心在于Bean的注入与Bean的装配使用,Spring提供了属性注入、setter注入、构造注入(推荐使用构造注入)三种注入方式,下面将会详细描述Spring Bean的注入和装配使用。

2.1 Bean的注入

Spring 官方推荐使用注解方式进行Bean的注入与装配,为了实验的便捷性所有案例将在SpringBoot环境下测试,注入Bean的常用注解如下:

注解名 作用范围 作用 使用场景
@Bean 只能用于方法上 声明一个Bean并将Bean注入到Spring IOC容器中。默认以@Bean注释的首字母小写方法名作为该Bean的Bean名称,也可以通过@Bean的name属性指定一个或多个Bean的别名,在装配Bean时可根据Bean的别名装配对应的Bean。 一般通过方法实例化对象时使用
@Component 仅用于类上 把普通Java对象注入到Spring IOC容器中,相当于Spring配置文件中的 @Component泛指各种组件,当要注入的Bean不属于Controller、Servic、Dao(@Controller、@Services、
@Repository)种类时都可以使用@Component。
@Repository 仅用于类上 用于标注数据访问层,也可以说用于标注数据访问组件。即将DAO类型的Bean注入到Spring 容器。 用于DAO(数据访问层)组件
@Service 仅用于类上 用于标注服务层,主要用来进行业务的逻辑处理。即将Service类型的Bean注入到Spring容器。 用于Service(服务逻辑层)组件
@Controller 仅用于类上 用于标注控制层,相当于struts中的action层。 用于Controller(控制器层)组件
@Configuration 仅用于类上 用于定义配置类,可替换xml配置文件。被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
使用@Configuration有如下要求:
(1).@Configuration不可以是final类型。
(2).@Configuration不可以是匿名类;嵌套的configuration必须是静态类。
用于定义配置类

虽然以上注解都能将Java普通对象注入到Spring IOC容器,但在开发中为了代码可维护性和阅读性,应在不同的场景选择对应的注解注入Bean。

2.1.2 @Bean注解注入Bean

(1).新建User.class

// User.class
package com.fly.entity;
public class User {
    private String name;
    public User(){}
    public User(String name) {
        this.name = name;
    }
    public String hello(){
        return "hello "+this.name;
    }
}

(2).新建UserService.class。因为@Bean注解只能用于方法上,为了方便测试这里使用了@Component注解将UserService.class注入Spring容器,UserService.class中被@Bean注解标识的对象也会被注入Spring容器。下面示例中列举了默认使用@Bean注解和使用@Bean注解时指定Bean Name。

package com.fly.service;

import com.fly.entity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;


@Component
public class UserService {

    // 将Bean注入到Spring IOC容器,默认以方法名作为Bean Name,在此示例中Bean Name为getUser
    @Bean
    public User getUser(){
        return new User("user");
    }

    // 将Bean注入到Spring IOC容器,显示指定注入Bean的名称,可以指定多个别名
    @Bean("getUser01")
    public User getUser01(){
       return new User("user01");
    }

    // 将Bean注入到Spring IOC容器,显示指定注入Bean的多个Bean别名
    @Bean({"getUser02","user02"})
    public User getUser02(){
        return new User("user02");
    }
}

(3).SpringBoot测试类。为了使用注入后的Bean,下面示例使用了@Autowired和@Qualifier注解的组合装配Spring 容器中指定Bean Name的Bean对象,其中@Autowired注解用于自动装配,@Qualifier注解用于指定需要装配的Bean Name。@Qualifier注解的用处:当一个接口有多个实现的时候,为了指名具体调用哪个类的实现。

SpringBoot启动类标识了@SpringBootApplication注解,@SpringBootApplication注解由@ComponentScan等多个注解组合而成,@ComponentScan用于类或接口上主要是指定扫描路径(默认以当前类所在类的包为扫描路径,在此示例中以com.fly作为扫描路径),Spring会把指定路径下带有指定注解的类自动装配到bean容器里。会被自动装配的注解包括@Controller、@Service、@Component、@Repository等等。其作用等同于<context:component-scan base-package="com.fly" />配置。运行SpringBoot启动类时,SpringBoot会默认自动扫描App所在包下(com.fly)所有指定注解的类自动装配到Spring Bean容器里,会被自动装配的注解包括@Controller、@Service、@Component、@Repository等等,所以被@Component标识了的UserService.class会被自动注入Spring容器,此时就能使用@Autowired注解装配Bean使用了。

package com.fly;

import com.fly.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class App {


   @Autowired
   @Qualifier("getUser")
   User user;

   /*
     自动装配指定Bean Name的Bean。其中@Autowired用于Bean的自动装配,@Qualifier用于指定
     装配指定Bean的Bean Name。即自动装配Bean Name为getUser01的Bean。
    */
   @Autowired
   @Qualifier("getUser01")
   User user01;

    /*
      自动装配指定Bean Name的Bean。其中@Autowired用于Bean的自动装配,@Qualifier用于指定
      装配指定Bean的Bean Name。即自动装配Bean Name为getUser02的Bean。
    */
   @Autowired
   @Qualifier("getUser02")
   User user02;

    public static void main(String[] args) {
        SpringApplication.run(App.class);
    }
    @RequestMapping("/hello")
    public String hello(){
        return user.hello(); //打印:hello user
    }
    @RequestMapping("/hello01")
    public String hello01(){
        return user01.hello();//打印:hello user01
    }
    @RequestMapping("/hello02")
    public String hello02(){
        return user02.hello();//打印:hello user02
    }
}

由于@Autowired注解只会根据Bean的Type(byType)进行自动装配,若要根据Bean Name装配Bean则需要结合@Qualifier注解指定装配Bean的BeanName(byName)使用,其目的是用于区分Bean在同一实现了调用哪个具体的Bean。但Spring也提供了功能强大的@Primary注解,使用了@Primary注解的Bean将会优化被装配使用。


public interface Singer {
    String sing(String lyrics);
}

package com.fly.service;
import org.springframework.stereotype.Component;

@Component
public class Cat implements  Animal{
    @Override
    public String run(String name) {
        return "The cat is running";
    }
}



package com.fly.service;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Primary //使用@Primary注解标识在装配时优先装配
@Component
public class Dog implements Animal{
    @Override
    public String run(String name) {
        return "The dog is running";
    }
}


package com.fly;
import com.fly.service.Animal;
import com.sun.org.apache.bcel.internal.generic.RETURN;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class StartApp01 {

    @Autowired
    Animal animal;

    @RequestMapping("/run")
    public String run(){
        return animal.run("大黄");
    }
    public static void main(String[] args) {
        SpringApplication.run(StartApp01.class);
    }
}

访问/run端点返回”The dog is running”,说明@Primary注解生效了,在多个类实现同一接口时,使用@Primary注解在装配时会优先装配。

2.1.2 @Component注解注入组件类型Bean

@Component用于组件类型的Bean注入到Spring容器。
(1).使用@Component注解将UserComponent.class注入到Spring容器。

package com.fly.component;
import org.springframework.stereotype.Component;

@Component
public class UserComponent {
    public String sayHi(){
        return "hello";
    }
}

(2).通过@Autowired注解自动装配userComponent

package com.fly;

import com.fly.component.UserComponent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class StartApp {
    // 自动装配userComponent
    @Autowired
    UserComponent userComponent;

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/sayHi")
    public String sayHi(){
        return userComponent.sayHi(); // hello
    }
}

2.1.3 @Repository注解注入Dao类型Bean

@Repository注解用于DAO类型的Bean注入到Spring容器。

package com.fly.dao;
import org.springframework.stereotype.Repository;

// 将Dao类型的Bean注入到Spring容器
@Repository
public class UserDao {
    public String getUserName(){
        // 假设由数据库查询返回
        return "z乘风";
    }
}
package com.fly;

import com.fly.component.UserComponent;
import com.fly.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class StartApp {
    // 自动装配userDao
    @Autowired
    UserDao userDao;

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/sayHi")
    public String getUserName(){
        return userDao.getUserName(); // z乘风
    }
}

2.1.4 @Service注解注入Service类型Bean

@Service注解用于Service类型的Bean注入到Spring容器。

package com.fly.service;

import org.springframework.stereotype.Service;

// 将Service类型的Bean注入到Spring容器
@Service
public class RoleService {
    public String getRoleName(){
        return "管理员";
    }
}
package com.fly;

import com.fly.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class StartApp {

    // 自动装配roleService
    @Autowired
    RoleService roleService;

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/sayHi")
    public String getUserName(){
        return roleService.getRoleName(); // 管理员
    }
}

2.1.5 @Controller注解注入Controller类型Bean

@Controller用于将Controller类型的Bean注入到Spring容器中。在前面几个示例中通过@RestController注解来表示Controller类,@RestController注解是一个组合注解,该注释将该类标记为控制器,其中每个方法都返回一个域对象(json对象)而不是视图。,由@Controller注解@ResponseBody注解组合而成,其中@Controller注解用于标识类为控制器Bean,@ResponseBody将java对象转为json格式的数据。

package com.fly;

import com.fly.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ResponseBody;

@SpringBootApplication
@Controller
public class StartApp {
    // 自动装配roleService
    @Autowired
    RoleService roleService;

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/sayHi")
    @ResponseBody //此路由方法返回字符串所以需要添加@ResponseBody
    public String getUserName(){
        return roleService.getRoleName();
    }
}

2.1.6 @Configuration注解注入配置类型Bean

@Configuration用于将配置类型的Bean注入到Spring容器(在配置小节将详情讲解)。

package com.fly.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class ProjectConfiguration {
    public String getConfig(){
        return "{version:1.0}";
    }
}
package com.fly;

import com.fly.config.ProjectConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;


@SpringBootApplication
@RestController
public class StartApp {
    // 自动装配roleService
    @Autowired
    ProjectConfiguration projectConfiguration;

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/sayHi")
    public String getConfig(){
        return projectConfiguration.getConfig(); // {version:1.0}
    }
}

2.2 Bean的三种注入方式

在Spring 注解方式下(官方推荐注解)通过@Autowired注解即可完成属性注入及装配工作。@Autowired 注解,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。

2.2.1 属性注入

package com.fly;

import com.fly.config.ProjectConfiguration;
import com.fly.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;


@SpringBootApplication
@RestController
public class StartApp {
    // 属性注入。通过@Autowired注解对roleService属性注入并自动装配 
    @Autowired
    RoleService roleService;

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/getRoleName")
    public String getRoleName(){
        return roleService.getRoleName();
    }
}

属性注入是开发中最为常用的一种注入方式,只需在属性加上@Autowired注解即可完成Bean自动装配。这种方式使得整体代码简洁明了,但IDE会提示”Field injection is not recommended”即不推荐属性注入,这是因为Spring团队建议:“在bean中始终使用基于构造函数的依赖项注入。始终对强制依赖项使用断言”。

2.2.2 setter注入

package com.fly;

import com.fly.config.ProjectConfiguration;
import com.fly.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class StartApp {

    RoleService roleService;

    /**
     * setter注入。通过在属性的setter方法添加@Autowired完成Bean的自动装配
     */
    @Autowired
    public void setRoleService(RoleService roleService) {
        this.roleService = roleService;
    }

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/getRoleName")
    public String getRoleName(){
        return roleService.getRoleName();
    }
}

setter注入这种方式比较笨重,一般不用,但setter注入的方式能用让类在之后重新配置或者重新注入。

2.2.3 构造注入

package com.fly;

import com.fly.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class StartApp {

    // 构造注入最好在属性上使用final修饰,这样会导致属性需要初始化值,使注入Bean不可变
    final RoleService roleService;

    /**
     * 构造注入。在构造方法上添加@Autowired完成Bean的自动装配,需要装配的属性通过构造方法的入参传入
     * 并赋值。当构造注入方式注入多个属性时,这种方式并不优雅,但Spring官方团队推荐使用构造注入,
     * 因为可以对注入属性进行类型断言。
     */
    @Autowired
    public StartApp(RoleService roleService) {
        this.roleService = roleService;
    }


    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/getRoleName")
    public String getRoleName(){
        return roleService.getRoleName();
    }
}

构造注入。在构造方法上添加@Autowired完成Bean的自动装配,需要装配的属性通过构造方法的入参传入
并赋值。当构造注入方式注入多个属性时,这种方式并不优雅,但Spring官方团队推荐使用构造注入,因为可以对注入属性进行类型断言。

2.2.4 扩展:为什么推荐构造注入

构造注入能够保证注入的Bean不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。

  • 依赖不可变:通过final修饰注入属性,可以保证依赖不可变且依赖必须有初始化值,即引出了依赖不为空。
  • 依赖不为空:省去了对依赖的空检查,实例化对象时由于使用了构造注入方式,所以不会默认调用无参构造,此时就需要想有参构造传入指定类型的参数,这样不仅完成了类型断言,也可以有效避免依赖为空。
  • 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法)。所以返回来的都是初始化之后的状态。

属性注入的缺点在于对于IOC容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。而且属性注入会产生循环依赖问题,即A中注入了B,B中又注入了A。

public class A {
    @Autowired
    private B b;
}

public class B {
    @Autowired
    private A a;
}

2.2.5 扩展:@Autowired与@Resource注解的区别?

除了@Autowired可以装配Bean外,@Resource注解也可以装配Bean,@Resource注解由J2EE提供,位于javax.annotation包下。

package com.fly.service;
import org.springframework.stereotype.Service;

// 使用@Service标识当前类为Service类注入到Spring容器,并指定注入Bean的Bean Name
@Service("empService")
public class EmpService {

    public String getEmpName(){return "二狗子";}
}

使用@Autowired装配Bean:

package com.fly;

import com.fly.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;



@SpringBootApplication
@RestController
public class StartApp {

    // 根据EmpService.class类型自动装配依赖
    @Autowired
    EmpService empService;

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/getEmpName")
    public String getEmpName(){
        return empService.getEmpName();// 二狗子
    }
}

使用@Resource装配Bean:

package com.fly;

import com.fly.service.EmpService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@SpringBootApplication
@RestController
public class StartApp {

    @Resource
    EmpService empService;

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/getEmpName")
    public String getEmpName(){
        return empService.getEmpName(); // 二狗子
    }
}

虽然@Autowired注解和@Resource注解都可以装配Bean,但它们仍有如下不同:
(1).@Autowired注解由Spring提供,而@Resource注解由J2EE提供,位于javax.annotation包下,Spring支持该注解的注入。
(2).@Autowired注解是根据装配Bean的类型(byType)来进行装配依赖,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果想按照Bean名称(byName)来装配依赖,可以结合@Qualifier注解一起使用,例如:

@Service("user") // 指定注入Bean Name
public class UserService(){
}

public class Test(){

    /* @Autowired与@Qualifier注解组合,自动装配指定Bean Name的依赖*/
    @Autowired
    @Qualifier("user")
    UserService userService;
}

(3).@Resource注解默认以装配Bean的Bean Name(byName)来进行装配依赖,@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。
如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。

@Service
public class UserService(){}

@Service("role") // 指定注入Bean Name
public class RoleService(){}

@Service
public class EmpService(){}

public class Test(){
    /*
      不指定Bean name和 Bean type,默认采用Bean Name进行自动注入,即会自动注入Bean Name为
      user的依赖,如果匹配失败则回退为一个原始类型进行匹配,如果匹配则自动装配。
    */
    @Resource
    public UserService userService;

    /*
       指定name属性,根据Bean Name进行自动装配,若匹配不到则抛出异常。
    */
    @Resource("role")
    public RoleService roleService; // 正确的例子

    @Resource("roleService")
    public RoleService roleService; // 错误的例子,找不为Bean Name为roleService的依赖



    /*
       指定type属性,根据Bean的类型进行自动装配,若匹配不到则抛出异常。    
    */
    @Resource(type=EmpService.class)
    public EmpService empService; // 正确的例子

    @Resource(type=RoleService.class)
    public EmpService empService; // 错误的例子,Bean type为EmpService.class,而查找Bean的type为RoleService,类型不一致故报错

}

@Resource注解的装配顺序如下:

  • 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
  • 如果指定了name,则从上下文中查找名称(Bean Name)匹配的bean进行装配,找不到则抛出异常。
  • 如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个都会抛出异常。
  • 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。

    2.2.6 扩展:动态注入Bean

3.Bean的作用域

Spring中的Bean是对普通Java对象的抽象(在Spring中通过),它描述了普通Java对象在Spring容器中的行为,例如Bean是否懒加载、Bean作用范围、Bean的依赖等等,Spring 为了Bean提供五种作用域,不同作用域下Bean的行为会有所不同。

作用域类别 描述
singleton 在Spring IOC容器仅会存在一个Bean实例,Bean以单例方式存在,singleton为默认作用域
prototype 每次从容器中调用Bean时,都会返回一个新的Bean实例,即每次调用getBean()时,相当于执行new XXXBean()
request 每次接收Http请求时都会创建新的Bean,该作用域仅适用于基于web的Spring ApplicationContext环境下
session 同一个Session共享同一个Bean,不同Session使用不同Bean,该作用域仅适用于基于web的Spring ApplicationContext环境下
globalSession 一般用于Portlet环境下,该作用域仅适用于基于web的Spring ApplicationContext环境下

在五种作用域中,request、session和global session三种作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

singleton:当Bean作用域为singleton时,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管是否使用,它都存在,每次获取到的对象都是同一个对象

prototype:当Bean的作用域为Prototype时,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。

request:当Bean的作用域为Request时,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。

session:当Bean的作用域为Session是,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

globalSession:当Bean的作用域为Global Session,表示在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。

3.1 使用@Scope注解指定Bean作用域

@Scope是Spring提供用于指定Bean作用域的注解,只能用于类或方法上,@Scope注解相当于spring xml配置文件中bean标签的scope属性,例如:

<bean id="user" class="com.example.User" scope="singleton">
   <property name="userName" value="z乘风"/>
   <property name="sex" value="0"/>
   <property name="address" value="中国"/>
</bean>

3.1.1 使用@Scope注解指定singleton作用域

package com.fly.service;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope("singleton") // 指定Bean的作用域为单例模式
public class EmpService {
    public String getEmpName(){return "二狗子";}
}
package com.fly;

import com.fly.service.EmpService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestStartApp {
    public static void main(String[] args) {
        // 扫描指定包路径下所有Spring注解
        ApplicationContext context=
                new AnnotationConfigApplicationContext("com.fly.service");
        EmpService empService01 = context.getBean(EmpService.class);
        EmpService empService02 = context.getBean(EmpService.class);
        System.out.println(empService01); // com.fly.service.EmpService@7880cdf3
        System.out.println(empService02); // com.fly.service.EmpService@7880cdf3
        System.out.println(empService01==empService02); // true
    }
}

上面例子中使用context.getBean()获取了两个EmpService.class,在EmpService Bean为单例作用域情况下,empService01==empService02结果为true,即单例作用域下无论创建多少个实例都会共享同一个Bean

3.1.2 使用@Scope注解指定prototype作用域

package com.fly.service;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope("prototype") // 指定Bean的作用域为单例模式
public class EmpService {
    public String getEmpName(){return "二狗子";}
}
package com.fly;

import com.fly.service.EmpService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestStartApp {
    public static void main(String[] args) {
        // 扫描指定包路径下所有Spring注解
        ApplicationContext context=
                new AnnotationConfigApplicationContext("com.fly.service");
        EmpService empService01 = context.getBean(EmpService.class);
        EmpService empService02 = context.getBean(EmpService.class);
        System.out.println(empService01); // com.fly.service.EmpService@7880cdf3
        System.out.println(empService02); // com.fly.service.EmpService@5be6e01c
        System.out.println(empService01==empService02); // false
    }
}

上面例子中使用context.getBean()获取了两个EmpService.class,在EmpService Bean为prototype作用域下,empService01==empService02结果为false,表示prototype作用域下每调用一次getBean()都会创建一个新的Bean实例。

3.1.3 扩展:Spring 中的Bean是线程安全吗?

Spring Bean默认是单例模式的,Spring容器本身并没有提供Bean的线程安全策略,所以Spring 容器的Bean不具备线程安全。

Spring Bean是线程不安全的,为什么controller、service、dao层Bean却能保证线程安全?
实际上大部分Bean都是无状态的(例如Dao),所以从某种程度上Bean是线程安全的。对于有状态的Bean需要开发人员自己保证线程安全,最简单的办法就是改变bean的作用域设置为protopyte,这样每次请求Bean就相当于是 new Bean() 这样就可以保证线程的安全了。

有状态Bean指含有数据存储功能的bean,无状态Bean表示无数据存储功能的Bean。

3.1.4 扩展:Spring 中的Bean为什么默认为单例作用域?

Spring 中Bean默认是单例的,即Spring IOC容器仅会存在一个Bean实例,其他匹配到的Bean会共享此Bean实例,Bean单例的优点如下:

  • 减少了新生成实例的消耗

新生成实例消耗包括两方面,第一,spring会通过反射或者cglib来生成bean实例这都是耗性能的操作.其次给 对象分配内存也会涉及复杂算法。

  • 减少jvm垃圾回收

由于不会给每个请求都新生成bean实例,所以自然回收的对象少了。

  • 可以快速获取到Bean

因为单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快。

3.2 使用@Lazy注解指定Bean懒加载

Spring IoC (ApplicationContext)容器一般都会在启动的时候实例化所有单实例 Bean。如果我们想要 Spring 在启动的时候延迟加载 bean,即在调用某个 bean 的时候再去初始化,那么就可以使用 @Lazy 注解,从而提升应用首次执行效率。
@Lazy只有一个value属性,默认为true,表示默认开启延迟加载Bean,value为false时表示按正常情况加载Bean,@Lazy只在Bean的作用域为singleton时生效。

package com.fly.service;
import org.springframework.stereotype.Service;

@Service
public class RoleService {
    public RoleService(){
        System.out.println("init RoleService...");
    }
    public String getRoleName(){
        return "管理员";
    }
}
package com.fly.service;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

@Lazy
@Service
public class EmpService {
    public EmpService(){
        System.out.println("init EmpService...");
    }
    public String getEmpName(){return "二狗子";}
}
package com.fly;

import com.fly.service.EmpService;
import com.fly.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
@Lazy
public class StartApp {

    @Autowired
    EmpService empService;

    @Autowired
    RoleService roleService;

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/getName")
    public String getName(){
        empService.getEmpName();
        return "z乘风";
    }
}

解析:启动项目后控制台会立即打印”init RoleService…”,因为IOC容器在启动时会实例化所有但实例Bean,由于EmpService.class类上加了@Lazy注解表示会延迟加载当前Bean,当访问了/getName端点时由于使用了empService.getEmpName(),此时IOC会加载EmpService Bean,控制台打印”init EmpService…”。此时你会好奇为什么StartApp类上为什么要加上@Lazy注解,这是因为@Lazy在以下两种情况下会失效:

  • 如果只在某个类上添加@Lazy注解,如果工程中有其它地方依赖了该类,那么即使添加了@Lazy注解,也依然会在IoC容器初始化的时候就去实例化该类。解决办法是在每个依赖类上都添加@Lazy注解。
  • @DependsOn注解也会让@Lazy失效。@DependsOn注解可以设置Bean直接的依赖关系,被依赖的会先创建加载到Spring容器中,如果@DependsOn设置的Bean使用了@Lazy进行延迟加载,则会导致@Lazy失效。

    3.2.1 @Lazy失效情况1:其他类与依赖与@Lazy Bean

    ```java package com.fly.service; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service;

@Lazy @Service public class EmpService { public EmpService(){ System.out.println(“init EmpService…”); } public String getEmpName(){return “二狗子”;} }

package com.fly.service; import org.springframework.stereotype.Service;

@Service public class RoleService { public RoleService(){ System.out.println(“init RoleService…”); } public String getRoleName(){ return “管理员”; } }

```java
package com.fly;

import com.fly.service.EmpService;
import com.fly.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
/**
 * StartApp类上若不使用@Lazy会导致 EmpService Bean懒加载无效,
 * 因为EmpService类上添加了@Lazy进行Bean懒加载,StartApp类依赖于EmpService,
 * 即使EmpServic添加了@Lazy注解,也依然会在IoC容器初始化的时候就去实例化该类。
 * 解决办法是给依赖类上添加@Lazy注解。
 */
// @Lazy
public class StartApp {

    @Autowired
    EmpService empService;

    @Autowired
    RoleService roleService;

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/getName")
    public String getName(){
        System.out.println(empService.getEmpName());
        System.out.println(roleService.getRoleName());
        return "z乘风";
    }
}
执行结果:
init EmpService...
init RoleService...

根据上面的执行结果来看EmpService上的@Lazy失效了。

3.2.2 @Lazy失效情况2:@DependsOn依赖于@Lazy Bean会导致@Lazy失效

package com.fly.service;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

@Lazy
@Service
public class EmpService {
    public EmpService(){
        System.out.println("init EmpService...");
    }
    public String getEmpName(){return "二狗子";}
}




package com.fly.service;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;

@Service
@DependsOn("empService") // 使用@DependsOn设置当前Bean依赖于empService Bean,被依赖的Bean会优先的加载到IOC容器中,若被依赖的Bean使用了@Lazy注解进行懒加载,则导致@Lazy注解失效
public class RoleService {
    public RoleService(){
        System.out.println("init RoleService...");
    }
    public String getRoleName(){
        return "管理员";
    }
}
package com.fly;

import com.fly.service.EmpService;
import com.fly.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
@Lazy
public class StartApp {

    @Autowired
    EmpService empService;

    @Autowired
    RoleService roleService;

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }

    @RequestMapping("/getName")
    public String getName(){
        System.out.println(empService.getEmpName());
        System.out.println(roleService.getRoleName());
        return "z乘风";
    }
}
执行结果:
init EmpService...
init RoleService...

结论:从运行结果来看,EmpService类的注解失效了,这是因为RoleService类上使用了@DependsOn()依赖于EmpService Bean,被依赖的Bean会优先的加载到IOC容器中,若被依赖的Bean使用了@Lazy注解进行懒加载,则导致@Lazy注解失效。

3.3 使用@Order注解指定Bean加载顺序

@Order或Ordered接口的作用是定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响。@Order的默认值为2147483647,@Order允许值的范围为-2147483647到2147483647之间,值越小执行顺序越靠前。

下面将借助CommandLineRunner接口来测试@Order注解的效果,实现CommandLineRunner接口的类会在Spring IOC容器加载完毕后执行,适合预加载类及其它资源;也可以使用ApplicationRunner,使用方法及效果是一样的。

package com.fly.service;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

@Order(-1)
@Service
public class RoleService implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("init RoleService...");
    }
}
package com.fly.service;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

@Order(0)
@Service
public class EmpService implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("init EmpService...");
    }
}
package com.fly;

import com.fly.service.EmpService;
import com.fly.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StartApp {

    @Autowired
    EmpService empService;

    @Autowired
    RoleService roleService;

    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }
}
执行结果:
init RoleService...
init EmpService...

结论:从结果来看@Order中的值越小Bean的执行顺序越靠前。用构造函数来测试@Order并不准确,它只能测试Bean的加载顺序,并不能测试Bean的执行顺序,上面例子中实现了CommandLineRunner接口,实现CommandLineRunner接口的类会在Spring IOC容器加载完毕后执行。

4.Bean的扩展

4.1 使用@Bean自定义Bean的初始化方法和销毁方法

@Bean注解的initMethod、destroyMethod属性可以分别指定Bean自定义初始化和销毁方法名。当Bean进行初始化时就会执行initMethod中的方法,当SpringIOC容器关闭时(上下文对象调用close()关闭容器)就会调用destroyMethod中的方法。注意:initMethod和destroyMethod指定的方法名不允许有参数,但允许抛出异常。

package com.fly.entity;
public class Color {
    public String name;
    public Color(String name) {
        this.name = name;
    }
    public void init(){
        System.out.println("init Color...");
    }
    public void destroy(){
        System.out.println("destroy Color...");
    }
}
package com.fly.component;
import com.fly.entity.Color;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class ColorComponent {

    /**
     * @Bean注解的initMethod、destroyMethod属性可以分别指定Bean自定义初始化和销毁方法名。
     * 当Bean进行初始化时就会执行initMethod中的方法,Bean的初始化方法执行在Bean构造函数之后;
     * 当SpringIOC容器关闭时(上下文对象调用close()关闭容器)就会调用destroyMethod中的方法。
     *
     * 注意:initMethod和destroyMethod指定的方法名不允许有参数
     */
    @Bean(initMethod = "init",destroyMethod = "destroy")
    public Color getColor(){
        return new Color("red");
    }
}
package com.fly;
import com.fly.entity.Color;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestStartApp {
    public static void main(String[] args) throws InterruptedException {
        // 扫描指定包路径下所有Spring注解
        AnnotationConfigApplicationContext context=
                new AnnotationConfigApplicationContext("com.fly");
        Color color = (Color) context.getBean("getColor");
        Thread.sleep(3000);
        context.close(); // 关闭容器,会触发Bean的销毁方法
    }
}
执行结果:
init Color...
destroy Color...    # 3秒后打印destroy Color...

4.2 InitializingBean与DisposableBean接口Bean的初始化和销毁方法

除了使用@Bean主机的initMethod和destroyMethod自定义Bean的初始化和销毁方法,也可以实现InitializingBean接口自定义Bean初始化方法,实现DisposableBean接口自定义Bean销毁方法。实现InitializingBean接口需要重写afterPropertiesSet,此方法用于Bean的初始化;实现DisposableBean
�接口需要重写destroy方法,此方法用于Bean的销毁方法。

package com.fly.entity;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class Color implements InitializingBean, DisposableBean {
    public String name;
    public Color(String name) {
        this.name = name;
    }

    /**
     * Bean初始化时执行此方法
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("init Color...");
    }

    /**
     * Bean销毁时执行此方法
     * @throws Exception
     */
    @Override
    public void destroy() throws Exception {
        System.out.println("destroy Color...");
    }
}
package com.fly.component;
import com.fly.entity.Color;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class ColorComponent {

    @Bean
    public Color getColor(){
        return new Color("red");
    }
}
package com.fly;

import com.fly.entity.Color;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestStartApp {
    public static void main(String[] args) throws InterruptedException {
        // 扫描指定包路径下所有Spring注解
        AnnotationConfigApplicationContext context=
                new AnnotationConfigApplicationContext("com.fly");
        Color color = (Color) context.getBean("getColor");
        Thread.sleep(3000);
        context.close();
    }
}
执行结果:
init Color...
destroy Color...    # 3秒后打印destroy Color...

4.3 @PostContructor和@PreDestroy自定义Bean的初始化和销毁方法

@PostConstruct和@PreDestroy注解由J2EE(不是由Spring提供,但Spring对此进行了实现),位于javax.annotation包下。
@PostConstruct注解用于方法上,该方法在Bean的依赖项注入完成后执行,以执行Bean的初始化工作。
@PreDestroy注解用于方法上,当实例被容器删除时会通知被标识了@PreDestroy注解的方法进行回调,PreDestroy注释的方法通常用于释放它所持有的资源。
注意:@PostConstruct和@PreDestroy只能用于无参数且非静态的方法上,否则将抛出异常。

package com.fly.entity;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class Color{
    public String name;
    public Color(String name) {
        this.name = name;
    }
       /**
     * @PostConstruct标识方法在Bean依赖注入完成后执行.
     */
    @PostConstruct
    public void init(){
        System.out.println("init Color...");
    }
    /**
     * @PreDestroy注解标识方法在实例被IOC容器删除时通知的回调方法。
     */
    @PreDestroy()
    public void destroy(){
        System.out.println("destroy Color...");
    }
}
package com.fly.component;

import com.fly.entity.Color;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class ColorComponent {
    @Bean
    public Color getColor(){
        return new Color("red");
    }
}
package com.fly;

import com.fly.entity.Color;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestStartApp {
    public static void main(String[] args) throws InterruptedException {
        // 扫描指定包路径下所有Spring注解
        AnnotationConfigApplicationContext context=
                new AnnotationConfigApplicationContext("com.fly");
        Color color = (Color) context.getBean("getColor");
        Thread.sleep(3000);
        context.close();
    }
}
执行结果:
init Color...
destroy Color...    # 3秒后打印destroy Color...

4.4 BeanPostProcessor定义Bean初始化前后行为

BeanPostProcessor是Spring IOC容器给提供的一个扩展接口,用于Bean在初始化前后扩展一些行为。BeanPostProcessor接口中提供了postProcessBeforeInitialization和postProcessAfterInitialization方法,postProcessBeforeInitialization()在Bean实例化之后初始化之前执行,postProcessAfterInitialization()在Bean初始化后执行。postProcessBeforeInitialization和postProcessAfterInitialization方法都有bean和beanName两个入参,其中bean表示实例化的Bean,beanName表示实例化的名称。postProcessBeforeInitialization和postProcessAfterInitialization方法还可以对bean对象进行包装,通过返回包装后的Bean控制Bean的行为。

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
package com.fly.entity;
import org.springframework.beans.factory.InitializingBean;
public class Color implements InitializingBean {
    public Color(){
        System.out.println("constructor Color...");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("color afterPropertiesSet...");
    }
}
package com.fly.component;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor, InitializingBean {
    /**
     * postProcessBeforeInitialization()将在Bean实例化后Bean初始化前执行
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization...");
        return bean;
    }

    /**
     * postProcessAfterInitialization()放将在Bean初始化后执行
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization...");
        return bean;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet...");
    }
}
package com.fly.component;

import com.fly.entity.Color;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class ColorComponent {

    @Bean
    public Color getColor(){
        return new Color();
    }
}
package com.fly;

import com.fly.entity.Color;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestStartApp {
    public static void main(String[] args) throws InterruptedException {
        // 扫描指定包路径下所有Spring注解
        AnnotationConfigApplicationContext context=
                new AnnotationConfigApplicationContext("com.fly");
        Color color = (Color) context.getBean("getColor");
    }
}
执行结果(结果忽略了Spring内部Bean实例化拦截):
constructor Color...
postProcessBeforeInitialization...
color afterPropertiesSet...
postProcessAfterInitialization...

Bean实例化到销毁流程如下:
13150128-bb5c9389cd0acc6c.webp

5.Bean的生命周期

6.本节注解大全