转载

Spring Boot ——注解方式学习IoC

概述

IoC(Inversion of Control,IoC)是Spring的核心,可以说Spring是一种基于IoC容器编程的框架。由于Spring Boot 是基于注解开发Spring IoC,所以本文使用全注解的方式对IoC进行讲述。

一个系统的开发离不开许许多多的类,通过整合业务逻辑,我们组织这么多类进行业务的展开,不同类之间存在或多或少的依赖关系。以前我们实例化一个类是通过 new 关键字,现在我们把这个工作交给IoC,由它进行统一的管理。

为什么使用IoC

概念

IoC(Inversion of Control),控制反转,用白话来讲,就是由容器控制程序之间的关系,而非传统实现中,由程序代码直接控制,这也就是所谓“控制反转”的概念所在:控制权由应用代码中转到了外部容器,控制权的转移,就是所谓反转。

传统方式和Spring方式对比

传统方式:

决定使用哪一个具体实现是由应用程序负责的,在编译阶段就确定了。

Spring方式:

调用类只依赖接口,而不依赖具体的实现类,减少了耦合。控制权交给了容器,在运行期才由容器决定将具体的实现动态的“注入”到调用类的对象中。这也是使用IoC的根本原因。

BeanFactory

IoC容器是一个管理Bean的容器,在Spring的定义中,它要求所有的IoC容器都要实现BeanFactory接口,它是顶级容器接口,下面是BeanFactory的源码。

public interface BeanFactory {
    
    String FACTORY_BEAN_PREFIX = "&";


   
    Object getBean(String name) throws BeansException;

  
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

   
    Object getBean(String name, Object... args) throws BeansException;

   
    <T> T getBean(Class<T> requiredType) throws BeansException;

   
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

  
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

  
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

   
    boolean containsBean(String name);

 
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

   
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

   
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;


    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;


    String[] getAliases(String name);

}复制代码

源码中有很多getBean方法,它们是Ioc容器重要的方法之一,我们可以按类型或者名称获取Bean,理解这个内容对后面依赖注入十分重要。

isSingleton判断Bean是否为单例,在IoC中,默认的Bean都是单例存在,也就是getBean返回的都是同一个对象。

isPrototype方法如果返回true,那么IoC容器总是返回一个新的Bean。

AnnotationConfigApplicationContext

它是一个基于注解的IoC容器,介绍它的原因是,我们可以直观演示Spring Boot装配和获取Bean。我们下面演示手动装配Bean到该容器,然后从容器获取Bean。

1,创建User.java

package com.example.acutator.entity;

public class User {
    private int id;
    private String username;
    private String phone;
    private String email;



    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '/'' +
                ", phone='" + phone + '/'' +
                ", email='" + email + '/'' +
                '}';
    }
}
复制代码

2,创建AppConfig.java

package com.example.acutator.config;

import com.example.acutator.entity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean(name = "user")
    public User getUser() {
        User user = new User();
        user.setId(1);
        user.setUsername("张三疯");
        user.setPhone("1129882512");
        user.setEmail("1129882512@qq.com");
        return user;
    }
}
复制代码

3,在启动类里面获取Bean

@SpringBootApplication
public class AcutatorApplication {

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

        ApplicationContext applicationContext=new AnnotationConfigApplicationContext(AppConfig.class);
        User user=applicationContext.getBean(User.class);
        System.out.println("user info>>"+user);

    }

}复制代码

4,查看结果

2019-09-29 17:07:25.250  INFO 9148 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.24]
2019-09-29 17:07:25.359  INFO 9148 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-09-29 17:07:25.359  INFO 9148 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1094 ms
2019-09-29 17:07:25.747  INFO 9148 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-09-29 17:07:25.939  INFO 9148 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 14 endpoint(s) beneath base path '/actuator'
2019-09-29 17:07:26.004  INFO 9148 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-09-29 17:07:26.006  INFO 9148 --- [           main] c.example.acutator.AcutatorApplication   : Started AcutatorApplication in 2.076 seconds (JVM running for 2.85)
user info>>User{id=1, username='张三疯', phone='1129882512', email='1129882512@qq.com'}
2019-09-29 17:07:27.803  INFO 9148 --- [on(2)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-09-29 17:07:27.803  INFO 9148 --- [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-09-29 17:07:27.809  INFO 9148 --- [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 6 ms
复制代码

可以看到我们装配的User信息已经打印出来了。

@Configuration注解的类表示这是一个java配置文件,Spring 容器会根据它来生成IoC容器去装配Bean。

@Bean 代表方法返回的Bean会装配到IoC容器中,如果不给定name值,则默认装配到容器的Bean名称是方法名(本例中是getUser)

AnnotationConfigApplicationContext构造方法传入@Configuration注解的类,相当于载入它里面的配置信息,根据配置信息将Bean装配到IoC容器中。当然也可以根据名称或者类型取出Bean。

自动装配Bean

前面使用AnnotationConfigApplicationContext体验了一把手动装配,但是开发中那么多的类,如果按照这种方式去装配,那将是多么麻烦。所以Spring 提供了注解方式,通过扫描装配Bean。

我们将使用@Component和@ComponentScan这两个注解完成扫描装配Bean。

@Component:表示这个类被标记可以被扫描进入IoC容器。

@ComponentScan:表示用扫描样的策略去扫描我们装配的Bean。

1,创建Student.java

这里在类名上面加上@Component注解,表示可以被扫描到

package com.example.acutator.entity;

import org.springframework.stereotype.Component;

@Component
public class Student {
    private String num;
    private String name;
    private int score;

    public String getNum() {
        return num;
    }

    public void setNum(String num) {
        this.num = num;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "num='" + num + '/'' +
                ", name='" + name + '/'' +
                ", score=" + score +
                '}';
    }
}
复制代码

2,修改AppConfig.java

加上扫描策略:@ComponentScan("com.example.acutator.entity"),代表扫描该包下面所有的类,凡是类名有@Component注解的都将扫描装配到IoC容器。

@Configuration
@ComponentScan("com.example.acutator.entity")
public class AppConfig {

    @Bean(name = "user")
    public User getUser() {
        User user = new User();
        user.setId(1);
        user.setUsername("张三疯");
        user.setPhone("1129882512");
        user.setEmail("1129882512@qq.com");
        return user;
    }
}
复制代码

3,修改启动类

加入Student对象打印信息

@SpringBootApplication
public class AcutatorApplication {

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

        ApplicationContext applicationContext=new AnnotationConfigApplicationContext(AppConfig.class);
//        User user=applicationContext.getBean(User.class);
//        System.out.println("user info>>"+user);

        Student student=applicationContext.getBean(Student.class);
        System.out.println("student info>>"+student);
    }

}复制代码

4,查看结果

.   ____          _            __ _ _
 /// / ___'_ __ _ _(_)_ __  __ _ / / / /
( ( )/___ | '_ | '_| | '_ // _` | / / / /
 ///  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_/__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.8.RELEASE)

2019-09-29 17:32:10.600  INFO 14844 --- [           main] c.example.acutator.AcutatorApplication   : Starting AcutatorApplication on XXW4VSPSAQE4OR2 with PID 14844 (D:/ideaProject2/acutator/target/classes started by Administrator in D:/ideaProject2/acutator)
2019-09-29 17:32:10.602  INFO 14844 --- [           main] c.example.acutator.AcutatorApplication   : No active profile set, falling back to default profiles: default
2019-09-29 17:32:11.539  INFO 14844 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-09-29 17:32:11.559  INFO 14844 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-09-29 17:32:11.560  INFO 14844 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.24]
2019-09-29 17:32:11.680  INFO 14844 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-09-29 17:32:11.680  INFO 14844 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1047 ms
2019-09-29 17:32:12.082  INFO 14844 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-09-29 17:32:12.267  INFO 14844 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 14 endpoint(s) beneath base path '/actuator'
2019-09-29 17:32:12.328  INFO 14844 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-09-29 17:32:12.330  INFO 14844 --- [           main] c.example.acutator.AcutatorApplication   : Started AcutatorApplication in 2.018 seconds (JVM running for 2.761)
student info>>Student{num='null', name='null', score=0}
2019-09-29 17:32:14.207  INFO 14844 --- [on(2)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-09-29 17:32:14.207  INFO 14844 --- [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-09-29 17:32:14.213  INFO 14844 --- [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 6 ms
复制代码

可以看到Student对象信息,我们在装配的时候没有赋值,所以打印的都是默认值。

依赖注入

(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。具体含义是:当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者 实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。

举例:人有时候会依赖于动物替我们做一些事情,比如猫抓老鼠,狗看家等。猫和狗都属于动物。下面以代码的角度去描述人依赖动物做事的逻辑。

1,创建Animal.java

它是动物的接口,封装一个方法,我们形象的给它一个方法 work

public interface Animal {
    public void work();
}复制代码

2,创建Person.java

它是人的顶级接口

public interface Person {
    public void service();
    public void setAnimal(Animal animal);
}复制代码

3,创建Dog.java实现Animal接口

package com.example.acutator.entity;

import org.springframework.stereotype.Component;

@Component
public class Dog implements Animal{
    @Override
    public void work() {
        System.out.println("***狗正在看门***");
    }
}复制代码

4,创建BusinessPerson实现Person接口

package com.example.acutator.entity;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class BusinessPerson implements Person{

    @Autowired
    private Animal animal=null;

    @Override
    public void service() {
        this.animal.work();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.animal=animal;
    }
}
复制代码

5,启动类测试

@SpringBootApplication
public class AcutatorApplication {

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

        ApplicationContext applicationContext=new AnnotationConfigApplicationContext(AppConfig.class);
//        User user=applicationContext.getBean(User.class);
//        System.out.println("user info>>"+user);

//        Student student=applicationContext.getBean(Student.class);
//        System.out.println("student info>>"+student);
        Person person=applicationContext.getBean(BusinessPerson.class);
        person.service();
    }

}复制代码

6,测试结果

.   ____          _            __ _ _
 /// / ___'_ __ _ _(_)_ __  __ _ / / / /
( ( )/___ | '_ | '_| | '_ // _` | / / / /
 ///  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_/__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.8.RELEASE)

2019-09-30 08:49:24.093  INFO 11792 --- [           main] c.example.acutator.AcutatorApplication   : Starting AcutatorApplication on XXW4VSPSAQE4OR2 with PID 11792 (D:/ideaProject2/acutator/target/classes started by Administrator in D:/ideaProject2/acutator)
2019-09-30 08:49:24.101  INFO 11792 --- [           main] c.example.acutator.AcutatorApplication   : No active profile set, falling back to default profiles: default
2019-09-30 08:49:26.992  INFO 11792 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-09-30 08:49:27.046  INFO 11792 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-09-30 08:49:27.046  INFO 11792 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.24]
2019-09-30 08:49:27.191  INFO 11792 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-09-30 08:49:27.191  INFO 11792 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2972 ms
2019-09-30 08:49:27.948  INFO 11792 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-09-30 08:49:28.236  INFO 11792 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 14 endpoint(s) beneath base path '/actuator'
2019-09-30 08:49:28.314  INFO 11792 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-09-30 08:49:28.331  INFO 11792 --- [           main] c.example.acutator.AcutatorApplication   : Started AcutatorApplication in 5.185 seconds (JVM running for 6.756)
***狗正在看门***
2019-09-30 08:49:30.709  INFO 11792 --- [on(9)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-09-30 08:49:30.710  INFO 11792 --- [on(9)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-09-30 08:49:30.851  INFO 11792 --- [on(9)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 141 ms
复制代码

@Autowired 它会根据属性的类型找到对应的Bean进行注入。这里Dog类是动物的一种,所以IoC容器会把Dog的实例注入到BusinessPerson中,这样,Dog就可以为我们工作了。测试结果中可以看到打印的信息,代表我们注入依赖成功。

这里有个问题,之前我们说过,IoC容器的顶级接口是BeanFactory,它获取Bean的方式很多,其中一个是根据类型获取Bean,然后本例中Animal的实现类只有Dog,但是动物并不止一种,还有猫狼象鼠等,那么根据类型获取Bean是不是有问题呢,IoC怎么知道我们要使用哪种类型注入到调用者实例中呢?请下面内容。

7,创建Cat.java

package com.example.acutator.entity;

import org.springframework.stereotype.Component;

@Component
public class Cat implements Animal {
    @Override
    public void work() {
        System.out.println("***猫正在抓老鼠***");
    }
}复制代码

8,异常

.   ____          _            __ _ _
 /// / ___'_ __ _ _(_)_ __  __ _ / / / /
( ( )/___ | '_ | '_| | '_ // _` | / / / /
 ///  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_/__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.8.RELEASE)

2019-09-30 09:10:51.358  INFO 10716 --- [           main] c.example.acutator.AcutatorApplication   : Starting AcutatorApplication on XXW4VSPSAQE4OR2 with PID 10716 (D:/ideaProject2/acutator/target/classes started by Administrator in D:/ideaProject2/acutator)
2019-09-30 09:10:51.361  INFO 10716 --- [           main] c.example.acutator.AcutatorApplication   : No active profile set, falling back to default profiles: default
2019-09-30 09:10:52.313  INFO 10716 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-09-30 09:10:52.329  INFO 10716 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-09-30 09:10:52.329  INFO 10716 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.24]
2019-09-30 09:10:52.432  INFO 10716 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-09-30 09:10:52.432  INFO 10716 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1042 ms
2019-09-30 09:10:52.684  WARN 10716 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'businessPerson': Unsatisfied dependency expressed through field 'animal'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.acutator.entity.Animal' available: expected single matching bean but found 2: cat,dog
2019-09-30 09:10:52.687  INFO 10716 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2019-09-30 09:10:52.707  INFO 10716 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-09-30 09:10:52.783 ERROR 10716 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Field animal in com.example.acutator.entity.BusinessPerson required a single bean, but 2 were found:
	- cat: defined in file [D:/ideaProject2/acutator/target/classes/com/example/acutator/entity/Cat.class]
	- dog: defined in file [D:/ideaProject2/acutator/target/classes/com/example/acutator/entity/Dog.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed


Process finished with exit code 
复制代码

当我们创建了Cat实例之后,程序抛出异常,这是因为我们在注入的时候,IoC容器不知道你要注入什么动物,它自己混乱了,所以在这里报错。继续看下面内容,我们解决这种问题。

消除依赖歧义性

概念

Spring IoC容器在注入依赖中,由于某个实例的多种类型而产生的注入引用的混乱或者困扰,我们把这个问题成为歧义性。

方式一

把注入的Animal属性名称修改为dog或者cat

package com.example.acutator.entity;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class BusinessPerson implements Person{

    @Autowired
    private Animal dog=null;

    @Override
    public void service() {
        this.dog.work();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.dog=animal;
    }
}复制代码

结果

.   ____          _            __ _ _
 /// / ___'_ __ _ _(_)_ __  __ _ / / / /
( ( )/___ | '_ | '_| | '_ // _` | / / / /
 ///  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_/__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.8.RELEASE)

2019-09-30 09:15:05.128  INFO 11684 --- [           main] c.example.acutator.AcutatorApplication   : Starting AcutatorApplication on XXW4VSPSAQE4OR2 with PID 11684 (D:/ideaProject2/acutator/target/classes started by Administrator in D:/ideaProject2/acutator)
2019-09-30 09:15:05.130  INFO 11684 --- [           main] c.example.acutator.AcutatorApplication   : No active profile set, falling back to default profiles: default
2019-09-30 09:15:06.081  INFO 11684 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-09-30 09:15:06.100  INFO 11684 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-09-30 09:15:06.100  INFO 11684 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.24]
2019-09-30 09:15:06.211  INFO 11684 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-09-30 09:15:06.211  INFO 11684 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1047 ms
2019-09-30 09:15:06.566  INFO 11684 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-09-30 09:15:06.756  INFO 11684 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 14 endpoint(s) beneath base path '/actuator'
2019-09-30 09:15:06.819  INFO 11684 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-09-30 09:15:06.821  INFO 11684 --- [           main] c.example.acutator.AcutatorApplication   : Started AcutatorApplication in 2.011 seconds (JVM running for 2.758)
***狗正在看门***
2019-09-30 09:15:08.689  INFO 11684 --- [on(8)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-09-30 09:15:08.689  INFO 11684 --- [on(8)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-09-30 09:15:08.694  INFO 11684 --- [on(8)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms
复制代码

这个时候系统就正常了,为什么?

原因:

@Autowired提供了这样的规则,首先它会根据类型找到对应的Bean,如果对应的类型不是唯一的,那么它会根据属性名称和Bean的名称进行匹配,如果匹配上,就会使用该Bean,如果还是无法匹配就抛出异常。

@Autowired注解还有个需要注意的点,就是它默认必须要找到对应Bean,如果不能确定其标注属性一定会存在并且允许这个标注的属性为null,那么可以配置@Autowired属性required为false

方式二

虽然方式一的做法可以做到消除歧义,但是直接把animal修改为dog,好好的一个动物,硬是被我们改成了狗,感觉这种做法太不符合正常的逻辑。

这里我们使用@Primary注解,修改Cat.java,在类名上面加上该注解。它告诉IoC容器,如果在注入的过程中,发现同实例多种类型,请优先注入带有@Primary的这个。(把dog属性名称改回原来的animal)

package com.example.acutator.entity;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class Cat implements Animal {
    @Override
    public void work() {
        System.out.println("***猫正在抓老鼠***");
    }
}复制代码

运行结果

/// / ___'_ __ _ _(_)_ __  __ _ / / / /
( ( )/___ | '_ | '_| | '_ // _` | / / / /
 ///  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_/__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.8.RELEASE)

2019-09-30 09:31:52.588  INFO 14992 --- [           main] c.example.acutator.AcutatorApplication   : Starting AcutatorApplication on XXW4VSPSAQE4OR2 with PID 14992 (D:/ideaProject2/acutator/target/classes started by Administrator in D:/ideaProject2/acutator)
2019-09-30 09:31:52.591  INFO 14992 --- [           main] c.example.acutator.AcutatorApplication   : No active profile set, falling back to default profiles: default
2019-09-30 09:31:53.488  INFO 14992 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-09-30 09:31:53.506  INFO 14992 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-09-30 09:31:53.506  INFO 14992 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.24]
2019-09-30 09:31:53.619  INFO 14992 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-09-30 09:31:53.619  INFO 14992 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1000 ms
2019-09-30 09:31:54.019  INFO 14992 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-09-30 09:31:54.232  INFO 14992 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 14 endpoint(s) beneath base path '/actuator'
2019-09-30 09:31:54.292  INFO 14992 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-09-30 09:31:54.294  INFO 14992 --- [           main] c.example.acutator.AcutatorApplication   : Started AcutatorApplication in 2.011 seconds (JVM running for 2.745)
***猫正在抓老鼠***
2019-09-30 09:31:56.073  INFO 14992 --- [on(8)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-09-30 09:31:56.074  INFO 14992 --- [on(8)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-09-30 09:31:56.079  INFO 14992 --- [on(8)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms复制代码

如果我们在Dog.java上面也加上这个注解,则在编译器就通过不了,所以这个方法有种治标不治标本的感觉。所以这种方法能解决,但是还是不好。

方式三

@Qualifier(),它需要配置一个value去定义,它将与@Autowired组合在一起,通过类型和名称一起找到Bean,因为Bean名称在Spring IoC容器中是唯一的,通过这种方式就可以消除歧义性。

修改BusinessPerson.java,(将Cat.java中@Primary注解去掉)

package com.example.acutator.entity;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class BusinessPerson implements Person{

    @Autowired
    @Qualifier("cat")
    private Animal animal=null;

    @Override
    public void service() {
        this.animal.work();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.animal=animal;
    }
}复制代码

运行结果

.   ____          _            __ _ _
 /// / ___'_ __ _ _(_)_ __  __ _ / / / /
( ( )/___ | '_ | '_| | '_ // _` | / / / /
 ///  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_/__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.8.RELEASE)

2019-09-30 09:41:57.385  INFO 8276 --- [           main] c.example.acutator.AcutatorApplication   : Starting AcutatorApplication on XXW4VSPSAQE4OR2 with PID 8276 (D:/ideaProject2/acutator/target/classes started by Administrator in D:/ideaProject2/acutator)
2019-09-30 09:41:57.388  INFO 8276 --- [           main] c.example.acutator.AcutatorApplication   : No active profile set, falling back to default profiles: default
2019-09-30 09:41:58.442  INFO 8276 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-09-30 09:41:58.463  INFO 8276 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-09-30 09:41:58.463  INFO 8276 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.24]
2019-09-30 09:41:58.581  INFO 8276 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-09-30 09:41:58.582  INFO 8276 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1163 ms
2019-09-30 09:41:58.940  INFO 8276 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-09-30 09:41:59.119  INFO 8276 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 14 endpoint(s) beneath base path '/actuator'
2019-09-30 09:41:59.192  INFO 8276 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-09-30 09:41:59.195  INFO 8276 --- [           main] c.example.acutator.AcutatorApplication   : Started AcutatorApplication in 2.072 seconds (JVM running for 2.762)
***猫正在抓老鼠***
2019-09-30 09:42:00.875  INFO 8276 --- [n(10)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-09-30 09:42:00.875  INFO 8276 --- [n(10)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-09-30 09:42:00.880  INFO 8276 --- [n(10)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms复制代码

完全没问题,说明我们的歧义问题解决。

Bean的生命周期

前面我们已经做到了如何装配Bean到容器中,如何注入到引用实例中使用,但是我们不清楚IoC容器是如何装配和销毁Bean的过程,有时候我们需要在实例的初始化过程中取做一些特殊的工作,比如初始化数据库连接,关闭连接资源等等。所以我们需要去了解下Bean的生命周期,了解了生命周期可以帮助我们进一步加深对Spring IoC和DI的理解。

Bean的定义

  • 我们前面介绍了@ComponentScan注解,它制定扫描路径规则,告诉IoC容器去哪里找需要装配的Bean,我们称它为资源定位。
  • 找到资源之后,它开始进行信息解析,此时还没有初始化Bean,也就没有Bean的实例,它目前仅仅是Bean的定义,如属性,方法等。
  • 最后把Bean的定义发布到IoC容器中,此时IoC容器中也只有Bean的定义,还是没有进行实例化

在完成上面三步之后,默认情况下,spring会继续去完成Bean的实例化和依赖注入,这样从IoC容器中就可以得到一个依赖注入完成的Bean。

Bean的初始化

当我们调用BeanFactory的getBean方法的时候,这时候IoC容器才开始实例化Bean。Ioc根据Bean的定义信息,通过实现不同的功能接口,对Bean进行实例化,流程如下。

  • Spring对bean进行实例化,默认bean是单例;

  • Spring对bean进行依赖注入;

  • 如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法;

  • 如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;

  • 如果bean实现了ApplicationContextAware接口,它的setApplicationContext()方法将被调用,将应用上下文的引用传入到bean中;

  • 如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization方法将被调用;

  • 如果bean实现了InitializingBean接口,spring将调用它的afterPropertiesSet接口方法,类似的如果bean使用了init-method属性声明了初始化方法,该方法也会被调用;

  • 如果bean实现了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法将被调用;

  • 此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文被销毁;

  • 若bean实现了DisposableBean接口,spring将调用它的distroy()接口方法。同样的,如果bean使用了destroy-method属性声明了销毁方法,则该方法被调用;

修改BusinessPerson.java

package com.example.acutator.entity;

import com.sun.org.apache.xml.internal.security.Init;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class BusinessPerson implements Person,BeanNameAware,BeanFactoryAware,ApplicationContextAware,InitializingBean,DisposableBean {

    @Autowired
    @Qualifier("cat")
    private Animal animal=null;

    @Override
    public void service() {
        this.animal.work();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.animal=animal;
    }

    @PostConstruct
    public void init(){
        System.out.println("Bean的初始化》》》init");
    }

    @PreDestroy
    public void destroy1(){
        System.out.println("Bean的初始化》》》destroy1");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("Bean的初始化》》》setBeanFactory");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("Bean的初始化》》》setBeanName");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("Bean的初始化》》》destroy");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Bean的初始化》》》afterPropertiesSet");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("Bean的初始化》》》setApplicationContext");
    }
}复制代码

修改启动类

@SpringBootApplication
public class AcutatorApplication {


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

        ApplicationContext applicationContext=new AnnotationConfigApplicationContext(AppConfig.class);
//        User user=applicationContext.getBean(User.class);
//        System.out.println("user info>>"+user);

//        Student student=applicationContext.getBean(Student.class);
//        System.out.println("student info>>"+student);
        Person person=applicationContext.getBean(BusinessPerson.class);
        person.service();
        ((AnnotationConfigApplicationContext) applicationContext).close();
    }

}复制代码

运行结果

.   ____          _            __ _ _
 /// / ___'_ __ _ _(_)_ __  __ _ / / / /
( ( )/___ | '_ | '_| | '_ // _` | / / / /
 ///  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_/__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.8.RELEASE)

2019-09-30 10:50:09.272  INFO 5256 --- [           main] c.example.acutator.AcutatorApplication   : Starting AcutatorApplication on XXW4VSPSAQE4OR2 with PID 5256 (D:/ideaProject2/acutator/target/classes started by Administrator in D:/ideaProject2/acutator)
2019-09-30 10:50:09.275  INFO 5256 --- [           main] c.example.acutator.AcutatorApplication   : No active profile set, falling back to default profiles: default
2019-09-30 10:50:10.167  INFO 5256 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-09-30 10:50:10.184  INFO 5256 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-09-30 10:50:10.185  INFO 5256 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.24]
2019-09-30 10:50:10.291  INFO 5256 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-09-30 10:50:10.291  INFO 5256 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 986 ms
Bean的初始化》》》setBeanName
Bean的初始化》》》setBeanFactory
Bean的初始化》》》setApplicationContext
Bean的初始化》》》init
Bean的初始化》》》afterPropertiesSet
2019-09-30 10:50:10.731  INFO 5256 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-09-30 10:50:10.919  INFO 5256 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 14 endpoint(s) beneath base path '/actuator'
2019-09-30 10:50:10.974  INFO 5256 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-09-30 10:50:10.976  INFO 5256 --- [           main] c.example.acutator.AcutatorApplication   : Started AcutatorApplication in 1.994 seconds (JVM running for 2.725)
Bean的初始化》》》setBeanName
Bean的初始化》》》setBeanFactory
Bean的初始化》》》setApplicationContext
Bean的初始化》》》init
Bean的初始化》》》afterPropertiesSet
***猫正在抓老鼠***
Bean的初始化》》》destroy1
Bean的初始化》》》destroy
2019-09-30 10:50:12.794  INFO 5256 --- [on(8)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-09-30 10:50:12.794  INFO 5256 --- [on(8)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-09-30 10:50:12.800  INFO 5256 --- [on(8)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 6 ms复制代码

为什么会出现多次初始化呢,上面的初始化为什么没有执行销毁方法呢?

分析:

我们之前使用AppConfig.java这个类进行了一个配置,而且使用注解扫描了entity包下面的所有类。如果类名带注解@Component,那么此时会有两个IoC容器对该类进行管理。一个是默认的IoC容器,另外一个是AnnotationConfigApplicationContext通过构造创建的容器,也就是ApplicationContext,我们知道所有的容器顶级接口是BeanFactory,ApplicationContext是在BeanFactory上面扩展的,它具有更强大的功能,如果我们把BeanFactory比喻为人体心脏,那么ApplicationContext就是躯体。

所有可以认为同时有两个容器对Bean进行了装配和注入,故有两个初始化的信息。

以上只是生命周期简单的描述,具体的可以对照官方文档描述,然后对比本文慢慢去理解,相信收获会更多。

结束语

有误请指正,不胜感激!

原文  https://juejin.im/post/5d906b75f265da5bb628d5ad
正文到此结束
Loading...