Spring Boot ——註解方式學習IoC

概述

IoC(Inversion of Control,IoC)是Spring的核心,能夠說Spring是一種基於IoC容器編程的框架。因爲Spring Boot 是基於註解開發Spring IoC,因此本文使用全註解的方式對IoC進行講述。java

一個系統的開發離不開許許多多的類,經過整合業務邏輯,咱們組織這麼多類進行業務的展開,不一樣類之間存在或多或少的依賴關係。之前咱們實例化一個類是經過 new 關鍵字,如今咱們把這個工做交給IoC,由它進行統一的管理。web


爲何使用IoC


概念

 IoC(Inversion of Control),控制反轉,用白話來說,就是由容器控制程序之間的關係,而非傳統實現中,由程序代碼直接控制,這也就是所謂「控制反轉」的概念所在:控制權由應用代碼中轉到了外部容器,控制權的轉移,就是所謂反轉。
spring


傳統方式和Spring方式對比

傳統方式:數據庫

決定使用哪個具體實現是由應用程序負責的,在編譯階段就肯定了。express

Spring方式:apache

調用類只依賴接口,而不依賴具體的實現類,減小了耦合。控制權交給了容器,在運行期才由容器決定將具體的實現動態的「注入」到調用類的對象中。這也是使用IoC的根本緣由。編程


BeanFactory

IoC容器是一個管理Bean的容器,在Spring的定義中,它要求全部的IoC容器都要實現BeanFactory接口,它是頂級容器接口,下面是BeanFactory的源碼。tomcat

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,理解這個內容對後面依賴注入十分重要。bash

isSingleton判斷Bean是否爲單例,在IoC中,默認的Bean都是單例存在,也就是getBean返回的都是同一個對象。app

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進行了裝配和注入,故有兩個初始化的信息。


以上只是生命週期簡單的描述,具體的能夠對照官方文檔描述,而後對比本文慢慢去理解,相信收穫會更多。


結束語

有誤請指正,不勝感激!

相關文章
相關標籤/搜索