Spring注解驱动 组件注册(一)
Spring 中所有的组件都会被放到 IOC 容器当中,然后组件之间通过容器来进行自动装配,也就是我们所说的依赖注入。
下面介绍用纯注解的方式来进行组件注册的几种方法。
一、@ComponentScan包扫描注解
1、@ComponentScan基本使用示例
新建一个Dog类,然后用@Component标注为组件。
package com.example.demo.annotation;
import org.springframework.stereotype.Component;
@Component
public class Dog {
}
新建一个配置类,扫描Dog组件所在的包来加入Dog组件。
package com.example.demo.annotation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(value = "com.example.demo.annotation")
public class MainConfig {
}
写个main测试,看看容器中是否有了它。
package com.example.demo;
import com.example.demo.annotation.MainConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class annoationTest {
public static void main(String[] args) {
//导入配置类MainConfig,创建容器
AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MainConfig.class);
System.out.println("容器创建完成。。。");
String[] names=applicationContext.getBeanDefinitionNames();
for (String name:names) {
System.out.println(name);
}
}
}
运行测试方法,看控制台打印,发现刚才添加的配置类 MainConfig 和 dog 组件已经被放到 IOC 容器中了。
2、value属性
有时候我们想要扫描更多的包,比如再加上Controller包。
这个时候只需直接将要扫描的包名写到 ComponentScan 的 value 属性的数组中即可。
@ComponentScan(value = {"com.example.demo.annotation","com.example.demo.controller"})
再次运行测试,发现Controller包中的标注了@Controller的类也会被加入到容器中。
事实上,加了组件标注注解的类都会被加入到容器中。如@Controller,@Component,@Service等。
3、excludeFilters属性
这个 ComponentScan 里面还可以来指定扫描过滤规则。比如我在后面再加一句。
用excludeFilters 来排除标有Controller注解的类(type = FilterType.ANNOTATION表示按照注解过滤),如下:
@ComponentScan(value = {"com.example.demo.annotation","com.example.demo.controller"},excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION ,classes = Controller.class)
})
运行后看控制台。结果显示容器中没有Controller了,只剩下了dog。
4、includeFilters属性
与它相对应的还有 includeFilters,用来指定只包含的组件。
使用这个属性同时需要将useDefaultFilters 置为false,因为默认true是扫描所有的。
这样容器中就只包含标注有@Controller的组件了。代码如下:
@ComponentScan(value = {"com.example.demo.annotation","com.example.demo.controller"},includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION ,classes = Controller.class)
},useDefaultFilters = false)
控制台结果显示dog没有了,只剩下了Contoller。
5、直接指定一个类型加入到容器中
我们还可以用 type = FilterType.ASSIGNABLE_TYPE 直接指定一个类型的加入到容器中,比如我把这个dog加回来。
@ComponentScan(value = {"com.example.demo.annotation","com.example.demo.controller"},includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION ,classes = Controller.class),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE ,classes = Dog.class)
},useDefaultFilters = false)
运行后看控制台,dog又被加回来了。
6、自定义扫描过滤规则
另外还可以自定义扫描过滤规则。将type属性改为FilterType.CUSTOM,再写一个过滤规则的类即可。
@ComponentScan(value = {"com.example.demo.annotation","com.example.demo.controller"},includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM ,classes = MyTypeFilter.class)
},useDefaultFilters = false)
用于过滤的自定义类,规则为将扫描到的全类名中包含有Controller的都加入到IOC容器中。
package com.example.demo.annotation;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
public class MyTypeFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
AnnotationMetadata annotationMetadata=metadataReader.getAnnotationMetadata();
ClassMetadata classMetadata=metadataReader.getClassMetadata();
String name=classMetadata.getClassName();
if(name.contains("Controller")){
return true;
}
return false;
}
}
结果显示规则已经生效,类名中包含有 Controller 字符串的类都被加入到了 IOC 容器中。
二、@Bean导入
1、@Bean的基本使用示例
新建一个Person对象,加上有参和无参构造器。
package com.example.demo.annotation;
import lombok.Data;
@Data
public class Person {
public String name;
public Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
}
配置类:
package com.example.demo.annotation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MainConfig {
@Bean
public Person person(){
System.out.println("给容器中添加Person。。。");
return new Person("lisi",20);
}
}
再次运行测试类,容器中出现了名为person的组件。
2、组件id名字修改
@Bean注解加入的bean默认id为方法名,我们要改id名字怎么搞呢?
(1)改方法名
修改方法名为 person111 。
@Bean
public Person person111(){
System.out.println("给容器中添加Person。。。");
return new Person("lisi",20);
}
控制台结果:
(2)赋值Bean注解的name属性
将名字写在Bean注解的name属性中。
@Bean(name = "person")
public Person person111(){
System.out.println("给容器中添加Person。。。");
return new Person("lisi",20);
}
控制台结果:
3、@Scope多实例
这样获得的组件是单实例的,测试一下看是也不是:
package com.example.demo;
import com.example.demo.annotation.MainConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class annotationTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MainConfig.class);
System.out.println("容器创建完成。。。");
String[] names=applicationContext.getBeanDefinitionNames();
for (String name:names) {
System.out.println(name);
}
Object bean=applicationContext.getBean("person");
Object bean1=applicationContext.getBean("person");
//判断是否为同一个实例
System.err.println(bean==bean1);
}
}
打印结果,确实是单实例的。
单实例的bean组件会在创建IOC容器的时候自动生成。
那么要怎么改成多实例的呢?有一个@Scope注解(prototype表示多实例),代码如下。
@Scope("prototype")
@Bean(name = "person")
public Person person111(){
System.out.println("给容器中添加Person。。。");
return new Person("lisi",20);
}
再次运行测试,可以看到 Person 确实变成了多实例。
以这种方式注册的组件在创建对象的时候才会加入到容器中。
4、@Lazy懒加载
有时候我们不想在创建容器的时候就添加单实例组件,这个@Lazy懒加载可以帮我们做到:
@Lazy
@Bean(name = "person")
public Person person111(){
System.out.println("给容器中添加Person。。。");
return new Person("lisi",20);
}
运行结果如下。
可以看见在创建对象的时候才创建单实例bean。
5、@Conditional条件注册
还有一个springboot底层使用非常多的注解@Conditional,该注解表示满足一定的条件才会在容器中注册bean。下面来实现一下。
先实现 Condition 接口,条件设置为容器中没有Dog组件则添加被@Conditional标注的bean组件。
package com.example.demo.annotation;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class LisiCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
BeanDefinitionRegistry definitionRegistry=conditionContext.getRegistry();
boolean contain=definitionRegistry.containsBeanDefinition("dog");
if(!contain){
return true;
}
return false;
}
}
然后将@Conditional注解标注到bean上。
@Conditional({LisiCondition.class})
@Bean(name = "person")
public Person person111(){
System.out.println("给容器中添加Person。。。");
return new Person("lisi",20);
}
最后将Dog组件扫描到容器中。
@ComponentScan(value = "com.example.demo.annotation")
来看看运行结果。
由于IOC容器中现在存在Dog组件,所以没有再注册Person组件。
三、@Import快速导入
1、@Import基本使用示例
在新建一个Cat类:
package com.example.demo.annotation;
public class Cat {
}
配置类,直接用@Import注解引入Cat组件:
package com.example.demo.annotation;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(Cat.class)
public class MainConfig {
}
运行结果如下,我们发现这个Cat以全类名为id被导入到了IOC容器中:
2、实现ImportSelector接口
我们还可以用ImportSelector接口返回需要导入组件的全类名数组,下面换这种方式导入Cat组件。
先实现这个接口:
package com.example.demo.annotation;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MySelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.example.demo.annotation.Cat"};
}
}
配置类,用@Import引入上面写的实现类:
package com.example.demo.annotation;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(MySelector.class)
public class MainConfig {
}
然后运行,结果显示Cat这样也被导入进来了。
3、实现ImportBeanDefinitionRegistrar接口
我们还可以实现 ImportBeanDefinitionRegistrar 接口手动注册组件并导入,同时顺便自定义组件id为cat。
package com.example.demo.annotation;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class MyRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition beanDefinition=new RootBeanDefinition(Cat.class);
registry.registerBeanDefinition("cat",beanDefinition);
}
}
运行结果如下,我们自定义id名字的Cat也被导入到容器中了:
四、FactoryBean组件注册
示例如下:
实现FactoryBean接口,用 getObject() 方法返回要导入的对象Cat,用 isSingleton() 方法设置为是单实例。
package com.example.demo.annotation;
import org.springframework.beans.factory.FactoryBean;
public class MyFactroy implements FactoryBean<Cat> {
//获取对象
@Override
public Cat getObject() throws Exception {
System.out.println("factory...获取对象。。。");
return new Cat();
}
@Override
public Class<?> getObjectType() {
return Cat.class;
}
//是否单实例
@Override
public boolean isSingleton() {
return true;
}
}
在配置类中用 @bean 注册 MyFactroy 组件。
package com.example.demo.annotation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MainConfig {
@Bean
public MyFactroy myFactroy(){
return new MyFactroy();
}
}
修改测试方法,看看它在容器中是什么样子的:
package com.example.demo;
import com.example.demo.annotation.MainConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class annotationTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MainConfig.class);
System.out.println("容器创建完成。。。");
String[] names=applicationContext.getBeanDefinitionNames();
for (String name:names) {
System.out.println(name);
}
Object bean=applicationContext.getBean("myFactroy");
System.out.println(bean.getClass());
}
}
控制台打印结果如下:
可以看到,虽然它的id名字是myFactroy,但是它里面实际的东西是工厂中的Cat对象。