你只管使用,一切交给IOC。
通过组件注册,IOC容器就自动帮助我们管理bean。
创建项目,引入依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.16</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.3.1</version > </dependency > <dependency > <groupId > javax.annotation</groupId > <artifactId > javax.annotation-api</artifactId > <version > 1.3.2</version > </dependency >
@Bean注册组件 创建一个实例类User
:
1 2 3 4 5 6 7 8 @AllArgsConstructor @NoArgsConstructor @Data @ToString public class User { private Long id; private String name; }
接着创建一个配置类,在配置类中通过@Bean
注解注册User
类:
1 2 3 4 5 6 7 8 @Configuration public class Config { @Bean public User user () { return new User (); } }
在SpringBoot入口类中编写如下代码:
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication public class KHighnessApplication { public static void main (String[] args) { ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder (KHighnessApplication.class) .web(WebApplicationType.SERVLET) .run(args); User user = applicationContext.getBean(User.class); System.out.println(user); } }
运行启动类,控制台输出如下:
1 User(id=1 , name=KHighness)
@ComponentSacn扫描包 在这个SpringBoot启动类所在包下创建其他类:UserDao、UserService、UserController,并且标注上响应注解,给User
类标注@Component
注解。
并修改配置类:
1 2 3 4 5 @Configuration @ComponentScan("top.parak") public class WebConfig { }
在启动类中获取所有的bean:
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication public class KHighnessApplication { public static void main (String[] args) { ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder (KHighnessApplication.class) .web(WebApplicationType.SERVLET) .run(args); String[] names = applicationContext.getBeanDefinitionNames(); Arrays.stream(names).forEach(System.out::println); } }
运行启动类,观察控制台,有如下输出:
1 2 3 4 5 6 7 KHighnessApplication org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory webConfig userController userDao user userService
@Scope组件作用域 默认情况下,在Spring的IOC容器中每个组件都是单例的,即无论在任何地方注入多少次,这些对象都是同一个。
将配置类修改回去:
1 2 3 4 5 6 7 8 @Configuration public class WebConfig { @Bean public User user () { return new User (1L , "KHighness" ); } }
修改SpringBoot启动类如下:
1 2 3 4 5 6 7 8 9 10 11 @SpringBootApplication public class KHighnessApplication { public static void main (String[] args) { ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder (KHighnessApplication.class) .web(WebApplicationType.SERVLET) .run(args); Object user1 = applicationContext.getBean("user" ); Object user2 = applicationContext.getBean("user" ); System.out.println(user1 == user2); } }
运行启动类,控制台输出如下:
可以通过@Scope
修改作用域,具体如下:
类型
描述
singleton
单实例(默认),在Spring IOC容器启动的时候会调用方法创建对象然后纳入到IOC容器中,以后每次获取都是直接从IOC容器中获取
prototype
多实例,IOC容器启动的时候并不会去创建对象,而是在每次获取的时候才会去调用方法创建对象
request
一个请求对应一个实例
session
同一个session对应一个实例
@Lazy懒加载 懒加载是针对单例模式而言的,容器启动的时候会调用方法创建对象然后纳入到IOC容器中。
在User注册的地方打印一句话以观察:
1 2 3 4 5 @Bean public User user () { System.out.println("向IOC容器中注册user" ); return new User (1L , "KHighness" ); }
在IOC容器启动后加入一句话以观察:
1 2 3 4 ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder (KHighnessApplication.class) .web(WebApplicationType.SERVLET) .run(args); System.out.println("IOC容器创建完毕" );
运行启动类,观察控制台,有如下输出:
1 2 3 4 5 向IOC容器中注册user 2021 -06-28 16 :43 :13.064 INFO 49772 --- [ main ] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2021 -06-28 16 :43 :13.165 INFO 49772 --- [ main ] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2021 -06-28 16 :43 :13.168 INFO 49772 --- [ main ] top.parak.KHighnessApplication : Started KHighnessApplication in 1.081 seconds (JVM running for 1.667 )IOC容器创建完毕
可以看到,在IOC容器创建完毕之前,组件已经注册到容器中。
将User修改为懒加载注册:
1 2 3 4 5 6 @Bean @Lazy public User user () { System.out.println("向IOC容器中注册user" ); return new User (1L , "KHighness" ); }
将启动类修改一下,获取user:
1 2 3 4 5 6 7 8 9 10 11 @SpringBootApplication public class KHighnessApplication { public static void main (String[] args) { ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder (KHighnessApplication.class) .web(WebApplicationType.SERVLET) .run(args); System.out.println("IOC容器创建完毕" ); Object user = applicationContext.getBean("user" ); System.out.println(user); } }
运行启动类,观察控制台,有如下输出:
1 2 3 IOC容器创建完毕 向IOC容器中注册user User(id=1 , name=KHighness)
从输出看到,创建时并没有注册user,获取时才去注册,可以证明懒加载成功。
条件注册 @Conditional 使用@Conditional
注解我们指定组件注册的条件,即满足特定条件才将组件纳入到IOC容器中。
在使用该注解之前,先创建一个类,实现Condition
接口:
1 2 3 4 5 6 public class MyCondition implements Condition { @Override public boolean matches (ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { return false ; } }
该接口包含一个matches
方法,包含两个入参:
ConditionContext
:上下文信息;
AnnotatedTypeMetadata
:注解信息。
简单完善一下实现类:
1 2 3 4 5 6 7 public class MyCondition implements Condition { @Override public boolean matches (ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { String osName = conditionContext.getEnvironment().getProperty("os.name" ); return osName != null && osName.contains("windows" ); } }
将这个条件添加到注册User的地方:
1 2 3 4 5 6 @Bean @Conditional(MyCondition.class) public User user () { System.out.println("向IOC容器中注册user" ); return new User (1L , "KHighness" ); }
在Windows系统下,User会被成功注册,其他操作系统下,这个组件则不会被注册到IOC容器中。
@Profile @Profile
可以根据不同的环境变量来注册不同的组件。
新建一个接口CalculateService
,用于求和:
1 2 3 4 public interface CalculateService { Integer sum (Integer... value) ; }
接着添加两个实现类Java7CalculateServiceImpl
和Java8CalculateServiceImpl
:
1 2 3 4 5 6 7 8 9 10 11 12 @Service @Profile("java7") public class Java7CalculateServiceImpl implements CalculateService { @Override public Integer sum (Integer... value) { System.out.println("Java7环境下执行" ); int res = 0 ; for (int val : value) res += val; return res; } }
1 2 3 4 5 6 7 8 9 @Service @Profile("java8") public class Java8CalculateServiceImpl implements CalculateService { @Override public Integer sum (Integer... value) { System.out.println("Java8环境下执行" ); return Arrays.stream(value).reduce(0 , Integer::sum); } }
通过@Profile
注解我们实现了:当环境变量包含java7
的时候,Java7CalculateServiceImpl
将会被注册到IOC容器中;当环境变量包含java8
的时候,Java8CalculateServiceImpl
将会被注册到IOC容器中。
修改启动类如下:
1 2 3 4 5 6 7 8 9 10 11 @SpringBootApplication public class KHighnessApplication { public static void main (String[] args) { ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder (KHighnessApplication.class) .web(WebApplicationType.SERVLET) .profiles("java8" ) .run(args); CalculateService service = applicationContext.getBean(CalculateService.class); System.out.println("求和结果:" + service.sum(1 , 2 , 3 , 4 , 5 )); } }
运行启动类,观察控制台,有如下输出:
同理,如果设置了环境变量java7
的时候,将会输出Java7环境下执行
。
导入组件 @Import 上面学习了使用@Bean
和@@ComponentScan
扫描实现组件注册,除此之外,我们还可以使用@Import
来快速地往IOC容器中添加组件。
创建一个新的类Hello
:
然后在配置类中导入这个组件:
1 2 3 @Configuration @Import({Hello.class}) public class WebConfig {...
修改启动类,查看所有组件的名称:
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication public class KHighnessApplication { public static void main (String[] args) { ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder (KHighnessApplication.class) .web(WebApplicationType.SERVLET) .run(args); String[] names = applicationContext.getBeanDefinitionNames(); Arrays.stream(names).forEach(System.out::println); } }
运行启动类,观察控制台,有如下输出:
可以看到,通过@Import
我们可以快速向容器中添加组件,默认名称为全类名。
ImportSelector 通过@Import
可以实现组件导入,如果需要一次性导入较多组件,我们可以使用@ImportSelector
来实现。
查看ImportSelector
源码:
1 2 3 4 5 6 7 8 9 public interface ImportSelector { String[] selectImports(AnnotationMetadata importingClassMetadata); }
ImportSelector
是一个接口,包含一个selectImports
方法,方法返回的类的全类名数组(即需要导入到IOC容器中组件的全类名数组),包含一个@AnnotationMetadata
类型入参,通过这个参数我们可以获取到使用ImportSelector
的类的全部注解信息。
新建三个类,分别为Orange
、Banana
和Pineapple
。
新建一个ImportSelector
实现类MyImportSelector
:
1 2 3 4 5 6 7 8 9 10 public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String [] { "top.parak.entity.Orange" , "top.parak.entity.Banana" , "top.parak.entity.Pineapple" }; } }
接着通过@Import
注解将MyImportSelector
把丧组件快速导入到容器中:
1 2 @Import(MyImportSelector.class) public class WebConfig {...
运行启动类,观察控制台,发现有如下输出:
1 2 3 top.parak.entity.Orange top.parak.entity.Banana top.parak.entity.Pineapple
说明三个组件已经批量导入。
ImportBeanDefinitionRegistor 除了上述两种向IOC容器中导入组件的方法外,我们还可以使用ImportBeanDefinitionRegistrar
来手动往IOC容器中导入组件。
查看ImportBeanDefinitionRegistrar
源码:
1 2 3 4 5 6 public interface ImportBeanDefinitionRegistrar { default void registerBeanDefinitions (AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { } }
ImportBeanDefinitionRegistrar
是一个接口,包含一个registerBeanDefinitions
方法,该方法包含两个入参:
AnnotationMetadata
:可以通过它获取类的注解信息;
BeanDefinitionRegistry
:Bean定义注册器,包含了一些和Bean有关的方法:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public interface BeanDefinitionRegistry extends AliasRegistry { void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException; void removeBeanDefinition (String beanName) throws NoSuchBeanDefinitionException; BeanDefinition getBeanDefinition (String beanName) throws NoSuchBeanDefinitionException; boolean containsBeanDefinition (String beanName) ; String[] getBeanDefinitionNames(); int getBeanDefinitionCount () ; boolean isBeanNameInUse (String beanName) ; }
这里借助BeanDefinitionRegistry
的registerBeanDefinition
方法往IOC容器中注册Bean。该方法包含两个入参,第一个参数为需要注册的Bean名称,第二个参数为Bean的定义信息,它是一个接口,我们可以用其实现类RootBeanDefinition
来完成:
为了演示ImportBeanDefinitionRegistrar
的使用,新增一个类Blueberry
。
创建一个ImportBeanDefinitionRegistrar
实现类MyImportBeanDefinitionRegistrar
:
1 2 3 4 5 6 7 8 9 10 11 public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions (AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { final String beanName = "blueberry" ; boolean contain = registry.containsBeanDefinition(beanName); if (!contain) { RootBeanDefinition rootBeanDefinition = new RootBeanDefinition (Blueberry.class); registry.registerBeanDefinition(beanName, rootBeanDefinition); } } }
然后在配置类中导入:
1 2 3 @Configuration @Import({MyImportBeanDefinitionRegistrar.class}) public class WebConfig {...
运行启动类,观察控制台,有如下输出:
说明组件注册成功。
FactoryBean Spring提供了FactoryBean
接口,我们可以通过该接口来注册组件,该接口包含两个抽象方法和一个默认方法:
为了演示FactoryBean
的作用,新建一个类Cherry
。
然后创建FactoryBean
的实现类CherryFactoryBean
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class CherryFactoryBean implements FactoryBean <Cherry> { @Override public Cherry getObject () throws Exception { return new Cherry (); } @Override public Class<?> getObjectType() { return Cherry.class; } @Override public boolean isSingleton () { return false ; } }
最后在配置类中注册即可:
1 2 3 4 @Bean public CherryFactoryBean cherryFactoryBean () { return new CherryFactoryBean (); }
修改启动类,获取cherryFactoryBean
:
1 2 Object bean = applicationContext.getBean("cherryFactoryBean" );System.out.println(bean.getClass());
运行启动类,观察控制台,有如下输出:
1 class top .parak.entity.Cherry
可以看到,虽然获取的是ID为cherryFactoryBean
的组件,但是实际上获取到的是getObject
里返回的对象。
如果要获取cherryFactoryBean
本身,则可以这样做:
1 2 Object bean = applicationContext.getBean("&cherryFactoryBean" );System.out.println(bean.getClass());
重新运行,输出如下:
1 class top .parak.entity.CherryFactoryBean
为什么这里加上&
前缀就可以获取到相应的工厂类?
查看BeanFactory
源码就可以看到原因:
1 2 3 4 5 6 7 8 9 public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&" ;