bean的注册有很多中方式,我们一样一样来讲解。
首先搭建一个java项目,这里就不多说了,需要使用的jar包,这里选用的是比较新的5.1.7.RELEASE:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
通过@Bean等注解创建
通过包扫描和对应的注解(@Bean,@Controller,@Service,@Component,@Repository)来将bean放到容器中。
一般用来注入自己创建的类或者数量比较少的jar包里面的配置类,为了测试方便我们创建一个自定义类。
package com.tr.demos.spring.annotion.bean;
/**
* @Author: glg
* @Date: 2019/5/30
*/
public class Food {
String name;
int num;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public Food() {
}
public Food(String name, int num) {
this.name = name;
this.num = num;
}
@Override
public String toString() {
return "Food{" +
"name='" + name + '\'' +
", num=" + num +
'}';
}
}
然后我们需要通过配置类去将自定义类注入到容器中:
package com.tr.demos.spring.annotion.config;
import com.tr.demos.spring.annotion.bean.Food;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: glg
* @Date: 2019/5/30
*/
@Configuration
public class MainConfig {
@Bean
public Food niceFood(){
return new Food("鱼豆腐",18);
}
}
@Configuration告诉springMainConfig是一个配置文件;
@Bean将该类配置到spring容器中,@Bean为默认值时,容器中bean的名字是配置函数的名字。
编写一个测试运行类进行测试,在这里说明一下,因为我创建的是spring boot项目,spring-boot本身有依赖junit单元测试,所以我就没有重新依赖,有需要的可以自行添加:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
package com.tr.demos.spring.annotion.test;
import com.tr.demos.spring.annotion.bean.Food;
import com.tr.demos.spring.annotion.config.MainConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @Author: glg
* @Date: 2019/5/30
*/
public class Test1 {
@Test
public void test01(){
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
Food name = context.getBean(Food.class);
System.out.println(name);
String[] names = context.getBeanNamesForType(Food.class);
for (String s : names) {
System.out.println(s);
}
}
}
AnnotationConfigApplicationContext 后面输入配置类,获取配置类的Application-上下文;
通过指定Food.class,获取指定类的bean内容;
通过getBeanNamesForType这个方法获取bean名称。
测试结果如下:
当给Bean赋值的时候,容器中bean的名字就是你赋值的名字:
@Bean(value = "food")
通过扫描包注解@ComponentScan
@ComponentScan 可以扫描配置包下面的@Controller,@Service,@Repository,@Component注解。
创建几个带有注解的类
package com.tr.demos.spring.annotion.dao;
import org.springframework.stereotype.Repository;
/**
* @Author: glg
* @Date: 2019/5/30
*/
@Repository
public class FoodDao {
}
package com.tr.demos.spring.annotion.service;
import org.springframework.stereotype.Service;
/**
* @Author: glg
* @Date: 2019/5/30
*/
@Service
public class FoodService {
}
package com.tr.demos.spring.annotion.controller;
import org.springframework.stereotype.Controller;
/**
* @Author: glg
* @Date: 2019/5/30
*/
@Controller
public class FoodController {
}
在配置类上面加上注解@ComponentScan,写一个测试类 测试一下
package com.tr.demos.spring.annotion.test;
import com.tr.demos.spring.annotion.config.MainConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @Author: glg
* @Date: 2019/5/30
*/
public class Test2 {
@Test
public void test01(){
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
测试结果如下:
因为我创建的是spring boot ,所以bean有点多,可以看到上面的都是IOC容器自己的组件,中间框起来的是我们创建的,有一个乱入的请忽略,下面的都是springboot需要的bean。
接下来应该是介绍@ComponentScan注解的参数使用了
@ComponentScan(value = "com.tr.demos.spring.annotion",excludeFilters = {
@Filter(type = FilterType.ANNOTATION,classes = {Component.class})
})
excludeFilters 不包括哪些标签,我们去掉了所有带@Component标签的bean,里面参数形式为@Filter(),Filter的参数有type和calsess,这里我们讲一下Filter
@Filter有很多种type
ANNOTATION, //注解
ASSIGNABLE_TYPE, //按照给定的类型,过滤给定类型及其子类
ASPECTJ, //一种类型,没接触过
REGEX, //正则
CUSTOM; //自定义
classes就是注解的类
我们在测试下,看看结果如何:
世界清静了好多,就剩IOC容器组件需要的bean和我们的配置bean mainConfig和@Bean注入的food,
那么其他的Bean去哪里了?为什么不过滤service,controller之类的其他文件呢?
我打开注解看了一下,service是依赖@Conponent的,其他的注解也是,所以我们过滤controller和service或者Repository的时候,是过滤不掉的。
为什么会导致这样的情况呢,这个还要赖我使用了springboot了,我是用的springboot是2.1.5.RELESE版本的,本身有依赖spring-context,而且是最新的5.1.7.RELESE版本,我添加的依赖也是这个版本,刚开始的时候没有注意到,后来改掉了spring-context版本的时候,发现jar包冲突了,才发现的问题,所以各位使用springboot的时候一定要好好的斟酌一下。
这里安利一款插件--maven helper。
这是一款对你pom里面jar包做分析的插件,使用起来时这样的,十分方便,不仅可以帮你分析jar包,还可以帮你快速解决jar包冲突的问题,具体的用法可以自行百度。
好了,继续,我们回归到主题,虽然jar包版本确实导致了一些问题使我们的解释结果没有那么清晰,但是不影响我们使用这个功能,并且我们也依然得出了结论。
@ComponentScan(value = "com.tr.demos.spring.annotion",includeFilters = {
@Filter(type = FilterType.ANNOTATION,classes = {Component.class})
},useDefaultFilters = false)
同样的includeFilters参数,包含什么注解,因为service等注解都加上了@Component,所以我们使用它添加任何一个注解都会使所有的注解回来,达不到过滤注解的目的。
另外注意一点,使用includeFilters,一定要设置useDefaultFilters =false,useDefaultFilters默认是true,默认扫描全部,如果不设置为false,你的includeFilters将不会生效。
通过@Import注入
用来批量注入bean:在导入外部包的时候,有大量的bean需要创建,@Bean一个一个添加是很麻烦的,这个时候就可以使用@Import注解,批量导入。
我们首先先创建一个新的实体类Bike
package com.tr.demos.spring.annotion.bean;
/**
* @Author: glg
* @Date: 2019/6/3
*/
public class Bike {
}
接下来是配置类:
package com.tr.demos.spring.annotion.config;
import com.tr.demos.spring.annotion.bean.Bike;
import com.tr.demos.spring.annotion.bean.Food;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @Author: glg
* @Date: 2019/6/3
*/
@Configuration
@Import(Bike.class)
public class MyConfig3 {
@Bean(value = "food")
public Food niceFood() {
return new Food("鱼豆腐", 18);
}
}
我们重新开一个测试类,对MyConfig3进行测试:
package com.tr.demos.spring.annotion.test;
import com.tr.demos.spring.annotion.config.MyConfig3;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @Author: glg
* @Date: 2019/6/3
*/
public class Test4 {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig3.class);
@Test
public void test01() {
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
结果如下:
这里可以看一下BIke的Bean的名称,是它的全类名,@Import在导入Bean的时候,Bean的名称是全类名,如果导入多个类的话,输入格式是这样:
@Import({Bike.class,Car.class})
@Import的参数参数也是丰富多样的,@Import的参数可以是ImportSeclector的实现类,也可以是ImportBeanDefinitionRegistrar的实现类,我们这里先讲一下ImportSeclector的实现类:
package com.tr.demos.spring.annotion.condition;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* @Author: glg
* @Date: 2019/6/3
*/
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.tr.demos.spring.annotion.bean.Food"};
}
}
配置是这样的:
@Import({Bike.class,Car.class, MyImportSelector.class})
然后我们测试一下:
可以看到1是我们@Import注解直接导入的两个bean,2是我们通过实现ImportSeclector接口导入的bean,可能会有人会问,@Import注解中也有MyImportSeclector.class,为什么没有对应的bean,这里简单解释下,因为它实现了ImportSeclector接口,不是普通的类,返回的结果是实现方法中返回的数组,所以没有它对应的bean,当然具体的实现就不清楚了。
需要注意的一个地方是,在一些版本里面实现ImportSeclector接口的时候,默认的返回值是null,会报NullPointerException,是因为在底层会取返回的类的数组的长度,在新的版本里面这个地方已经改掉了,默认的返回是返回new String[0]。
接下来是ImportBeanDefinitionRegistrar的实现类:
首先,按照惯例,我们先创建一个实体类备用:
package com.tr.demos.spring.annotion.bean;
/**
* @Author: glg
* @Date: 2019/6/3
*/
public class Wheel {
}
我们要写一个自定义类去实现ImportBeanDefinitionRegistrar:
package com.tr.demos.spring.annotion.condition;
import com.tr.demos.spring.annotion.bean.Wheel;
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;
import java.util.Set;
/**
* @Author: glg
* @Date: 2019/6/3
*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* @param annotationMetadata 当前类的注解信息
* @param beanDefinitionRegistry BeanDefinotion注册类
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
Set<String> annotationTypes = annotationMetadata.getAnnotationTypes();
System.out.println("注解类:" + annotationTypes);
boolean definition = beanDefinitionRegistry.containsBeanDefinition("com.tr.demos.spring.annotion.bean.Bike");
boolean definition1 = beanDefinitionRegistry.containsBeanDefinition("com.tr.demos.spring.annotion.bean.Car");
if (definition definition1){
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Wheel.class);
beanDefinitionRegistry.registerBeanDefinition("wheel", rootBeanDefinition);
}
}
}
继承的方法有两个参数:AnnotationMetadata,BeanDefinitionRegistry;
beanDefinitionRegistry.containsBeanDefinition这个方法,可以判断注册类中是否有参数名称的实例;
registerBeanDefinition("wheel",rootBeanDefinition) 方法是注册名称为wheel的实例,rootBeanDefinition是参数类,在上面指定了bean的实体类。
测试结果1那里,我们在自定义实现类中有打印当前注解类的注解信息,很明显成功了,前面是@Configuration,后面是@Import
我们在下面注册类的地方,加了一个判断,Bike和Car如果存在一个,就会注册3,实例wheel,根据我们的打印结果,我们有Bike和Car,所以成功注册wheel。
另外还有一种实现FactoryBean注入,个人感觉太麻烦,实用性不高,不多作介绍,如果有感兴趣的,可以自行百度。
简单的见解,简单的介绍,新手入门,如果有不正确的地方,希望大家多多指教。