IoC(Inversion of Control,IoC)是Spring的核心,可以说Spring是一种基于IoC容器编程的框架。由于Spring Boot 是基于注解开发Spring IoC,所以本文使用全注解的方式对IoC进行讲述。
一个系统的开发离不开许许多多的类,通过整合业务逻辑,我们组织这么多类进行业务的展开,不同类之间存在或多或少的依赖关系。以前我们实例化一个类是通过 new 关键字,现在我们把这个工作交给IoC,由它进行统一的管理。
IoC(Inversion of Control),控制反转,用白话来讲,就是由容器控制程序之间的关系,而非传统实现中,由程序代码直接控制,这也就是所谓“控制反转”的概念所在:控制权由应用代码中转到了外部容器,控制权的转移,就是所谓反转。
传统方式:
决定使用哪一个具体实现是由应用程序负责的,在编译阶段就确定了。
Spring方式:
调用类只依赖接口,而不依赖具体的实现类,减少了耦合。控制权交给了容器,在运行期才由容器决定将具体的实现动态的“注入”到调用类的对象中。这也是使用IoC的根本原因。
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。
它是一个基于注解的IoC容器,介绍它的原因是,我们可以直观演示Spring Boot装配和获取Bean。我们下面演示手动装配Bean到该容器,然后从容器获取Bean。
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 + '/'' +
'}';
}
}
复制代码
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;
}
}
复制代码
@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);
}
}复制代码
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。
前面使用AnnotationConfigApplicationContext体验了一把手动装配,但是开发中那么多的类,如果按照这种方式去装配,那将是多么麻烦。所以Spring 提供了注解方式,通过扫描装配Bean。
我们将使用@Component和@ComponentScan这两个注解完成扫描装配Bean。
@Component:表示这个类被标记可以被扫描进入IoC容器。
@ComponentScan:表示用扫描样的策略去扫描我们装配的Bean。
这里在类名上面加上@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 +
'}';
}
}
复制代码
加上扫描策略:@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; } } 复制代码
加入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);
}
}复制代码
. ____ _ __ _ _ /// / ___'_ __ _ _(_)_ __ __ _ / / / / ( ( )/___ | '_ | '_| | '_ // _` | / / / / /// ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_/__, | / / / / =========|_|==============|___/=/_/_/_/ :: 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容器来完成,然后注入调用者,因此也称为依赖注入。
举例:人有时候会依赖于动物替我们做一些事情,比如猫抓老鼠,狗看家等。猫和狗都属于动物。下面以代码的角度去描述人依赖动物做事的逻辑。
它是动物的接口,封装一个方法,我们形象的给它一个方法 work
public interface Animal {
public void work();
}复制代码
它是人的顶级接口
public interface Person {
public void service();
public void setAnimal(Animal animal);
}复制代码
package com.example.acutator.entity;
import org.springframework.stereotype.Component;
@Component
public class Dog implements Animal{
@Override
public void work() {
System.out.println("***狗正在看门***");
}
}复制代码
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;
}
}
复制代码
@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();
}
}复制代码
. ____ _ __ _ _ /// / ___'_ __ _ _(_)_ __ __ _ / / / / ( ( )/___ | '_ | '_| | '_ // _` | / / / / /// ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_/__, | / / / / =========|_|==============|___/=/_/_/_/ :: 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怎么知道我们要使用哪种类型注入到调用者实例中呢?请下面内容。
package com.example.acutator.entity;
import org.springframework.stereotype.Component;
@Component
public class Cat implements Animal {
@Override
public void work() {
System.out.println("***猫正在抓老鼠***");
}
}复制代码
. ____ _ __ _ _ /// / ___'_ __ _ _(_)_ __ __ _ / / / / ( ( )/___ | '_ | '_| | '_ // _` | / / / / /// ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_/__, | / / / / =========|_|==============|___/=/_/_/_/ :: 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到容器中,如何注入到引用实例中使用,但是我们不清楚IoC容器是如何装配和销毁Bean的过程,有时候我们需要在实例的初始化过程中取做一些特殊的工作,比如初始化数据库连接,关闭连接资源等等。所以我们需要去了解下Bean的生命周期,了解了生命周期可以帮助我们进一步加深对Spring IoC和DI的理解。
在完成上面三步之后,默认情况下,spring会继续去完成Bean的实例化和依赖注入,这样从IoC容器中就可以得到一个依赖注入完成的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进行了装配和注入,故有两个初始化的信息。
以上只是生命周期简单的描述,具体的可以对照官方文档描述,然后对比本文慢慢去理解,相信收获会更多。
有误请指正,不胜感激!