自SpringBoot和SpringCloud火起来后, 使用Spring注解驱动开发就必须提上日程了...
首先回顾一下Spring配置文件方式的使用:
① 创建一个maven项目, 导入spring的依赖
org.springframework
spring-context
4.3.12.RELEASE
② 创建bean类(Person)
public class Person {
private Integer age;
private String name;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
public Person() {
}
}
③ 编写配置文件beans.xml, 存放于classpath目录下
④ 创建一个主方法进行测试
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Person bean = (Person) applicationContext.getBean("person");
System.out.println(bean);
}
⑤ 结果如下
注解形式实现上面配置文件形式的功能: 配置类就等同于配置文件(只不过是格式不同)
@Configuration
① 创建配置类MyConfig
@Configuration//告诉Spring这是一个配置类
public class MyConfig {
@Bean//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
public Person person01(){
return new Person(23, "王五");
}
}
② 主方法测试
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
Person bean = applicationContext.getBean(Person.class);
System.out.println(bean);
String[] names = applicationContext.getBeanNamesForType(Person.class);
for (String name: names){
System.out.println(name);//输出id名称(默认是方法名)
}
}
③ 结果如下
④ 在配置类MyConfig中指定id名称
@Configuration//告诉Spring这是一个配置类
public class MyConfig {
@Bean(name = "person")//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
public Person person01(){
return new Person(23, "王五");
}
}
⑤ 结果如下
@ComponentScan
配置文件方式的包扫描: 可以扫描@Controller @Service @Repository @Component
注解方式的包扫描:
① 创建一个测试类IOCTest, 并且新建Controller / Service / Dao类用于测试
public class IOCTest {
@Test
public void test01(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
String[] names = applicationContext.getBeanDefinitionNames();
for (String name: names){
System.out.println(name);
}
}
}
② 在配置类MyConfig上使用@ComponentScan注解扫描包路径
@Configuration//告诉Spring这是一个配置类
@ComponentScan(value = "com.spring.annotation")
public class MyConfig {
@Bean(name = "person")//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
public Person person01(){
return new Person(23, "王五");
}
}
③ 结果如下
④ 排除某些组件
@Configuration//告诉Spring这是一个配置类
@ComponentScan(value = "com.spring.annotation", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {
Controller.class
})
})
public class MyConfig {
@Bean(name = "person")//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
public Person person01(){
return new Person(23, "王五");
}
}
⑤ 只注入某些组件
@Configuration//告诉Spring这是一个配置类
@ComponentScan(value = "com.spring.annotation", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Service.class})
}, useDefaultFilters = false)
public class MyConfig {
@Bean(name = "person")//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
public Person person01(){
return new Person(23, "王五");
}
}
⑥ jdk8以下的多组件扫描用法, jdk8可以直接使用多个@ComponentScan
@Configuration//告诉Spring这是一个配置类
@ComponentScans(
value = {
@ComponentScan(value = "com.spring.annotation", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Service.class})
}, useDefaultFilters = false),
@ComponentScan(value = "com.spring.annotation", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
}
)
public class MyConfig {
@Bean(name = "person")//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
public Person person01(){
return new Person(23, "王五");
}
}
⑦ FilterType下的常用规则, 上面是根据注解类型来做排除或注入的(FilterType.ANNOTATION)
FilterType.ASSIGNABLE_TYPE: 根据指定组件的类型过滤
@Configuration//告诉Spring这是一个配置类
@ComponentScans(
value = {
@ComponentScan(value = "com.spring.annotation", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Service.class}),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookController.class})
}, useDefaultFilters = false)
}
)
结果如下
FilterType.CUSTOM: 根据自定义规则来过滤
@Configuration//告诉Spring这是一个配置类
@ComponentScans(
value = {
@ComponentScan(value = "com.spring.annotation", includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
}, useDefaultFilters = false)
}
)
public class MyConfig {
@Bean(name = "person")//给容器中注册一个Bean, 类型是返回值的类型, id默认是用方法名作为id
public Person person01(){
return new Person(23, "王五");
}
}
根据源码来看, 需要先创建一个TypeFilter的实现类MyTypeFilter
public class MyTypeFilter implements TypeFilter {
/**
* @param metadataReader 读取到当前正在扫描的类的信息
* @param metadataReaderFactory 可以获取到其他任何类的信息
* @return
* @throws IOException
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();//获取当前正在扫描的类的注解信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();//获取当前正在扫描的类的信息
Resource resource = metadataReader.getResource();//获取当前正在扫描的类的资源
String className = classMetadata.getClassName();
System.out.println("--->"+className);
if(className.contains("er")){//如果当前类的类名中包含"er", 则注入
return true;
}
return false;
}
}
结果如下
@Scope
可以调整作用域, 默认的作用域是单实例的.
@Configuration
public class MyConfig2 {
@Bean("person")
public Person person(){
return new Person(23, "赵六");
}
}
默认情况下是单实例的, 测试类输出
@Test
public void test02(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig2.class);
Person person1 = (Person) applicationContext.getBean("person");
Person person2 = (Person) applicationContext.getBean("person");
System.out.println(person1 == person2);
}
结果如下
改为多实例并进行测试
@Configuration
public class MyConfig2 {
@Scope("prototype")
@Bean("person")
public Person person(){
return new Person(23, "赵六");
}
}
结果如下
@Lazy(懒加载)
单实例: 在容器启动时创建对象, 创建完成后就存放于容器中, 随时取都是同一个实例
多实例: 在容器启动时不创建对象, 直到第一次获取bean的时候才创建对象(此时的情况就是懒加载)
也可以结合单实例进行懒加载
@Configuration
public class MyConfig2 {
@Lazy
@Bean("person")
public Person person(){
return new Person(23, "赵六");
}
}
@Conditional
作用: 按照一定的条件进行判断, 满足条件则给容器中注册bean, 此时当不满足条件时, 即使加上了@Bean注解也不会向容器中注入bean
创建两个实现org.springframework.context.annotation包下的Condition接口的判断类
WindowsCondition
//判断是否为windows系统
public class WindowsCondition implements Condition {
/**
* @param conditionContext 判断条件能使用的上下文环境
* @param annotatedTypeMetadata 注释信息
* @return
*/
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
/**
* todo 判断是否Windows系统
*/
//获取到ioc当前使用的beanFactory(用于创建对象和装配的)
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
//获取类加载器
ClassLoader classLoader = conditionContext.getClassLoader();
//获取环境信息
Environment environment = conditionContext.getEnvironment();
//获取到bean定义的注册类
BeanDefinitionRegistry registry = conditionContext.getRegistry();
String osName = environment.getProperty("os.name");
if(osName.contains("Windows")){
return true;
}
return false;
}
}
LinuxCondition
//判断是否为linux系统
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
String osName = environment.getProperty("os.name");
if(osName.contains("Linux")){
return true;
}
return false;
}
}
测试@Conditional注解
@Conditional({WindowsCondition.class})
@Bean("this is a windows system")
public Person person02(){
return new Person(23, "田七");
}
@Conditional({LinuxCondition.class})
@Bean("this is a linux system")
public Person person03(){
return new Person(23, "周八");
}
结果如下
@Import
作用: 快速给容器中导入一个组件, 容器中就会自动注册这个组件, id默认是全类名
用法①(直接就是@Import)
新建一个bean, Color类
public class Color {
}
@Configuration
@Import({Color.class})
@Conditional({WindowsCondition.class})
public class MyConfig2 {...}
测试
@Test
public void testImport(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig2.class);
printBean(applicationContext);
}
private void printBean(ApplicationContext applicationContext){
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for(String beanName: beanDefinitionNames){
System.out.println(beanName);
}
}
结果如下 注入的bean的id默认是全类名
用法②(最多使用, 特别是源码里面)
创建一个实现ImportSelector接口的实现类MyImportSelector, 返回的是需要导入组件的全类名数组
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
/**
* @param annotationMetadata 当前标注@Import注解的类的其他注解的所有信息
* @return
*/
//返回值就是要导入到容器中的组件的全类名
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.spring.annotation.bean.Green", "com.spring.annotation.bean.Yellow"};
}
}
@Import注解中引入MyImportSelector的类对象
@Configuration
@Import({Color.class, MyImportSelector.class})
@Conditional({WindowsCondition.class})
public class MyConfig2 {...}
测试结果如下
用法③
创建一个实现ImportBeanDefinitionRegistrar接口的实现类MyImportBeanDefinitionRegistrar, 用于手动注册bean到容器中
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 当前类的注解信息
* @param registry bean定义的注册信息
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean green = registry.containsBeanDefinition("com.spring.annotation.bean.Green");
if(green){
//指定bean的定义信息(bean的类型, bean的scope...)
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainBow.class);
//注册一个bean, 指定bean名为rainBow
registry.registerBeanDefinition("rainBow", rootBeanDefinition);
}
}
}
@Import注解中引入MyImportBeanDefinitionRegistrar 的类对象
@Configuration
@Import({Color.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@Conditional({WindowsCondition.class})
public class MyConfig2 {...}
测试结果如下
工厂bean(FactoryBean)
创建一个实现FactoryBean
public class ColorFactoryBean implements FactoryBean {
/**
* 工厂bean通过调用该方法而获取Color的bean对象
* @return
* @throws Exception
*/
@Override
public Color getObject() throws Exception {
System.out.println("获取对象bean的方法被调用...");
return new Color();
}
/**
* 获取Color的类对象
* @return
*/
@Override
public Class> getObjectType() {
return Color.class;
}
/**
* 判断是否是单实例
* true: 单实例
* false: 多实例(默认)
* @return
*/
@Override
public boolean isSingleton() {
return true;
}
}
把ColorFactoryBean在配置类中进行注册
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
测试
@Test
public void testFactoryBean(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig2.class);
Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
Object colorFactoryBean1 = applicationContext.getBean("colorFactoryBean");
System.out.println(colorFactoryBean.getClass()); //此时的类型是Color
System.out.println(colorFactoryBean == colorFactoryBean1); //此时是单例模式, 所以输出true
}
结果如下
如果将返回值改为false
@Override
public boolean isSingleton() {
return false;
}
测试结果
以上默认是获取工厂bean调用getObject()方法创建的对象, 如果要获取工厂bean本身的对象, 则需要容器根据id获取bean时加上"&"
@Test
public void testFactoryBean(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig2.class);
Object colorFactoryBean = applicationContext.getBean("&colorFactoryBean");
System.out.println(colorFactoryBean.getClass()); //此时输出是工厂bean本身的对象
}
结果如下
原因在源码中已有说明
整理了一晚上, 组件注册部分暂告一段落, 接下来会写生命周期部分...