概述
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进行了装配和注入,故有两个初始化的信息。
以上只是生命周期简单的描述,具体的可以对照官方文档描述,然后对比本文慢慢去理解,相信收获会更多。