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