你只管使用,一切交给IOC。
通过组件注册,IOC就自动帮助我们管理bean。
创建项目,引入依赖:
<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:
@AllArgsConstructor@NoArgsConstructor@Data@ToStringpublic class User {private Long id;private String name;}
接着创建一个配置类,在配置类中通过@Bean注解注册User类:
@Configurationpublic class Config {@Beanpublic User user() {return new User();}}
在SpringBoot入口类中编写如下代码:
@SpringBootApplicationpublic 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);}}
运行启动类,控制台输出如下:
User(id=1, name=KHighness)
@ComponentSacn扫描包
在这个SpringBoot启动类所在包下创建其他类:UserDao、UserService、UserController,并且标注上响应注解,给User类标注@Component注解。
并修改配置类:
@Configuration@ComponentScan("top.parak")public class WebConfig {}
早在启动类中获取所有的bean:
@SpringBootApplicationpublic 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);}}
运行启动类,观察控制台,有如下输出:
KHighnessApplicationorg.springframework.boot.autoconfigure.internalCachingMetadataReaderFactorywebConfiguserControlleruserDaouseruserService
@Scope组件作用域
默认情况下,在Spring的IOC容器中每个组件都是单例的,即无论在任何地方注入多少次,这些对象都是同一个。
将配置类修改回去:
@Configurationpublic class WebConfig {@Beanpublic User user() {return new User(1L, "KHighness");}}
修改SpringBoot启动类如下:
@SpringBootApplicationpublic 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);}}
运行启动类,控制台输出如下:
true
可以通过@Scope修改作用域,具体如下:
| 类型 | 描述 |
|---|---|
| singleton | 单实例(默认),在Spring IOC容器启动的时候会调用方法创建对象然后纳入到IOC容器中,以后每次获取都是直接从IOC容器中获取 |
| prototype | 多实例,IOC容器启动的时候并不会去创建对象,而是在每次获取的时候才会去调用方法创建对象 |
| request | 一个请求对应一个实例 |
| session | 同一个session对应一个实例 |
@Lazy懒加载
懒加载是针对单例模式而言的,容器启动的时候会调用方法创建对象然后纳入到IOC容器中。
在User注册的地方打印一句话以观察:
@Beanpublic User user() {System.out.println("向IOC容器中注册user");return new User(1L, "KHighness");}
在IOC容器启动后加入一句话以观察:
ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(KHighnessApplication.class).web(WebApplicationType.SERVLET).run(args);System.out.println("IOC容器创建完毕");
运行启动类,观察控制台,有如下输出:
向IOC容器中注册user2021-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修改为懒加载注册:
@Bean@Lazypublic User user() {System.out.println("向IOC容器中注册user");return new User(1L, "KHighness");}
将启动类修改一下,获取user:
@SpringBootApplicationpublic 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);}}
运行启动类,观察控制台,有如下输出:
IOC容器创建完毕向IOC容器中注册userUser(id=1, name=KHighness)
从输出看到,创建时并没有注册user,获取时才去注册,可以证明懒加载成功。
条件注册
@Conditional
使用@Conditional注解我们指定组件注册的条件,即满足特定条件才将组件纳入到IOC容器中。
在使用该注解之前,先创建一个类,实现Condition接口:
public class MyCondition implements Condition {@Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {return false;}}
该接口包含一个matches方法,包含两个入参:
ConditionContext:上下文信息;AnnotatedTypeMetadata:注解信息。
简单完善一下实现类:
public class MyCondition implements Condition {@Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {String osName = conditionContext.getEnvironment().getProperty("os.name");return osName != null && osName.contains("windows");}}
将这个条件添加到注册User的地方:
@Bean@Conditional(MyCondition.class)public User user() {System.out.println("向IOC容器中注册user");return new User(1L, "KHighness");}
在Windows系统下,User会被成功注册,其他操作系统下,这个组件则不会被注册到IOC容器中。
@Profile
@Profile可以根据不同的环境变量来注册不同的组件。
新建一个接口CalculateService,用于求和:
public interface CalculateService {Integer sum(Integer... value);}
接着添加两个实现类Java7CalculateServiceImpl和Java8CalculateServiceImpl:
@Service@Profile("java7")public class Java7CalculateServiceImpl implements CalculateService {@Overridepublic Integer sum(Integer... value) {System.out.println("Java7环境下执行");int res = 0;for (int val : value)res += val;return res;}}
@Service@Profile("java8")public class Java8CalculateServiceImpl implements CalculateService {@Overridepublic Integer sum(Integer... value) {System.out.println("Java8环境下执行");return Arrays.stream(value).reduce(0, Integer::sum);}}
通过@Profile注解我们实现了:当环境变量包含java7的时候,Java7CalculateServiceImpl将会被注册到IOC容器中;当环境变量包含java8的时候,Java8CalculateServiceImpl将会被注册到IOC容器中。
修改启动类如下:
@SpringBootApplicationpublic 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));}}
运行启动类,观察控制台,有如下输出:
Java8环境下执行求和结果:15
同理,如果设置了环境变量java7的时候,将会输出Java7环境下执行。
导入组件
@Import
上面学习了使用@Bean和@@ComponentScan扫描实现组件注册,除此之外,我们还可以使用@Import来快速地往IOC容器中添加组件。
创建一个新的类Hello:
public class Hello {}
然后在配置类中导入这个组件:
@Configuration@Import({Hello.class})public class WebConfig {...
修改启动类,查看所有组件的名称:
@SpringBootApplicationpublic 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);}}
运行启动类,观察控制台,有如下输出:
top.parak.entity.Hello
可以看到,通过@Import我们可以快速向容器中添加组件,默认名称为全类名。
ImportSelector
通过@Import可以实现组件导入,如果需要一次性导入较多组件,我们可以使用@ImportSelector来实现。
查看ImportSelector源码:
public interface ImportSelector {/*** Select and return the names of which class(es) should be imported based on* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.*/String[] selectImports(AnnotationMetadata importingClassMetadata);}
@ImportSelector是一个接口,包含一个selectImports方法,方法返回的类的全类名数组(即需要导入到IOC容器中组件的全类名数组),包含一个@AnnotationMetadata类型入参,通过这个参数我们可以获取到使用@ImportSelector的类的全部注解信息。
新建三个类,分别为Orange、Banana和Pineapple。
新建一个ImportSelector实现类MyImportSelector:
public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[] {"top.parak.entity.Orange","top.parak.entity.Banana","top.parak.entity.Pineapple"};}}
接着通过@Import注解将MyImportSelector把丧组件快速导入到容器中:
@Import(MyImportSelector.class)public class WebConfig {...
运行启动类,观察控制台,发现有如下输出:
top.parak.entity.Orangetop.parak.entity.Bananatop.parak.entity.Pineapple
说明三个组件已经批量导入。
ImportBeanDefinitionRegistor
除了上述两种向IOC容器中导入组件的方法外,我们还可以使用ImportBeanDefinitionRegistrar来手动往IOC容器中导入组件。
查看ImportBeanDefinitionRegistrar源码:
public interface ImportBeanDefinitionRegistrar {default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}}
ImportBeanDefinitionRegistrar是一个接口,包含一个registerBeanDefinitions方法,该方法包含两个入参:
AnnotationMetadata:可以通过它获取类的注解信息;BeanDefinitionRegistry:Bean定义注册器,包含了一些和Bean有关的方法: ```java 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`来完成:<br /><br />为了演示`ImportBeanDefinitionRegistrar`的使用,新增一个类`Blueberry`。<br />创建一个`ImportBeanDefinitionRegistrar`实现类`MyImportBeanDefinitionRegistrar`:```javapublic class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic 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);}}}
然后在配置类中导入:
@Configuration@Import({MyImportBeanDefinitionRegistrar.class})public class WebConfig {...
运行启动类,观察控制台,有如下输出:
blueberry
说明组件注册成功。
FactoryBean
Spring提供了FactoryBean接口,我们可以通过该接口来注册组件,该接口包含两个抽象方法和一个默认方法:
为了演示FactoryBean的作用,新建一个类Cherry。
然后创建FactoryBean的实现类CherryFactoryBean:
public class CherryFactoryBean implements FactoryBean<Cherry> {@Overridepublic Cherry getObject() throws Exception {return new Cherry();}@Overridepublic Class<?> getObjectType() {return Cherry.class;}@Overridepublic boolean isSingleton() {return false;}}
最后在配置类中注册即可:
@Beanpublic CherryFactoryBean cherryFactoryBean() {return new CherryFactoryBean();}
修改启动类,获取cherryFactoryBean:
Object bean = applicationContext.getBean("cherryFactoryBean");System.out.println(bean.getClass());
运行启动类,观察控制台,有如下输出:
class top.parak.entity.Cherry
可以看到,虽然获取的是ID为cherryFactoryBean的组件,但是实际上获取到的是getObject里返回的对象。
如果要获取cherryFactoryBean本身,则可以这样做:
Object bean = applicationContext.getBean("&cherryFactoryBean");System.out.println(bean.getClass());
重新运行,输出如下:
class top.parak.entity.CherryFactoryBean
为什么这里加上
&前缀就可以获取到相应的工厂类? 查看BeanFactory源码就可以看到原因:
public interface BeanFactory {/*** Used to dereference a {@link FactoryBean} instance and distinguish it from* beans <i>created</i> by the FactoryBean. For example, if the bean named* {@code myJndiObject} is a FactoryBean, getting {@code &myJndiObject}* will return the factory, not the instance returned by the factory.*/String FACTORY_BEAN_PREFIX = "&";
