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进行了装配和注入,故有两个初始化的信息。


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


结束语

有误请指正,不胜感激!

相关文章
相关标签/搜索