通过组件扫描和自动注入已经大大简化了我们的开发,然而,Spring仍然不满足于此,经过版本的迭代,现在我们已经可以完全抛弃配置文件使用Spring进行开发了,一起来看看吧。
@Configuration & @Bean
现在我们不创建Spring的配置文件,那么如何将一个组件注册到容器中呢?其实,我们仍然是需要一个配置文件的,不过这个配置文件能够以一个类的形式存在:
@Configuration
public?class?MyConfiguration?{
}
@Configuration用于将一个普通的Java类变为一个Spring的配置类,现在这个类就相当于之前的配置文件了,此时如果想注册一个组件,则使用@Bean注解:
@Configuration
public?class?MyConfiguration?{
????@Bean
????public?User?user()?{
????????return?new?User();
????}
}
这里需要注意几点,若有方法被@Bean注解标注,则该方法的返回值则为需要注册的组件,而方法名则为组件在容器中的名字,当然了,这些都需要建立在代码是写在配置类的前提下。
如果想要修改组件的名字,可以修改方法名:
@Bean
public?User?myUser()?{
????return?new?User();
}
若是不想修改方法名,@Bean注解也提供了修改名字的方式:
@Bean(name?=?"myUser")
public?User?user()?{
????return?new?User();
}
@Bean中还有initMethod和destroyMethod属性,它们分别用于指定组件的两个生命周期方法:
@Bean(name?=?"myUser",initMethod?=?"init",destroyMethod?=?"destroy")
public?User?user()?{
????return?new?User();
}
@ComponentScan
@ComponentScan注解是用来完成组件扫描的,它需要标注在配置类上:
@Configuration
@ComponentScan("com.wwj.spring.demo")
public?class?MyConfiguration?{
}
它的作用等价于如下配置:
<context:component-scan?base-package="com.wwj.spring.demo"/>
我们来聊一聊关于@ComponentScan的一些高级用法,该注解是可以在扫描时指定扫描规则的,比如我们想扫描com.wwj.spring.demo 这个包,但是包里有一些类、或者一些注解的内容是我们不想要注册的,此时我们就可以指定扫描规则,如下:
@Configuration
@ComponentScan(value?=?"com.wwj.spring.demo",?excludeFilters?=?{
????????@Filter(type?=?FilterType.ASSIGNABLE_TYPE,?classes?=?{
????????????????User.class
????????})
})
public?class?MyConfiguration?{
}
在如上的配置中,excludeFilters用来配置需要排除的组件,需要借助@Filter注解,@Filter注解中的type属性用于指定以哪种方式排除组件,Spring一共提供了5种匹配方式:
-
_ANNOTATION:_以给定的注释进行匹配 -
_ASSIGNABLE_TYPE:_以给定的类型进行匹配 -
_ASPECTJ:_以给定的AspectJ表达式匹配 -
_REGEX:_以给定的正则表达式匹配 -
_CUSTOM:_以给定的自定义规则匹配
所以如果想要具体排除某个组件,则使用ASSIGNABLE_TYPE,如果想要排除某个注解标注的所有组件,则使用ANNOTATION:
@Configuration
@ComponentScan(value?=?"com.wwj.spring.demo",?excludeFilters?=?{
????????@Filter(type?=?FilterType.ANNOTATION,?classes?=?{
????????????????Service.class
????????})
})
将excludeFilters切换为includeFilters,功能将变为只扫描匹配的组件,如下:
@Configuration
@ComponentScan(value?=?"com.wwj.spring.demo",?includeFilters?=?{
????????@Filter(type?=?FilterType.ANNOTATION,?classes?=?{
????????????????Service.class
????????})
},useDefaultFilters?=?false)
public?class?MyConfiguration?{
}
以上配置的作用是扫描com.wwj.spring.demo 包下被@Service注解标注的组件,注意一点,由于Spring默认的扫描规则就是扫描所有带@Component注解的组件,所以若是想实现只扫描某个注解,则需要添加配置useDefaultFilters = false来禁用掉Spirng默认的扫描规则。
若是想实现自定义扫描规则,也非常简单,只需实现TypeFilter接口:
public?class?MyFilter?implements?TypeFilter?{
????@Override
????public?boolean?match(MetadataReader?metadataReader,?MetadataReaderFactory?metadataReaderFactory)?throws?IOException?{
????????//?获取完整注解元数据
????????AnnotationMetadata?metadata?=?metadataReader.getAnnotationMetadata();
????????//?获取类的元数据
????????ClassMetadata?classMetadata?=?metadataReader.getClassMetadata();
????????//?获取类文件的资源引用
????????Resource?resource?=?metadataReader.getResource();
????????//?获取类名
????????String?className?=?classMetadata.getClassName();
????????return?className.equals("user");
????}
}
然后进行配置即可:
@Configuration
@ComponentScan(value?=?"com.wwj.spring.demo",?includeFilters?=?{
????????@Filter(type?=?FilterType.CUSTOM,?classes?=?{
????????????????MyFilter.class
????????})
},?useDefaultFilters?=?false)
public?class?MyConfiguration?{
}
千万别忘了配置useDefaultFilters = false,此时将只能扫描到名字为user的组件。
这些内容在配置文件中也是可以进行配置的,简单举一个例子吧:
<context:component-scan?base-package="com.wwj.spring.demo"?use-default-filters="false">
??<context:include-filter?type="annotation"?expression="org.springframework.stereotype.Service"/>
</context:component-scan>
@Scope
@Scope用于指定组件的作用域,关于作用域在上一篇我们已经介绍过了, 所以用法其实非常简单:
????@Bean
????@Scope("prototype")
????public?User?user()?{
????????return?new?User();
????}
不过多介绍,但由此可以引申出一个新的注解:@Lazy ,该注解的作用是指定组件是否懒加载,默认情况下,所有组件会在容器启动的时候被创建,而如果标注@Lazy,则组件会在第一次使用时被创建。我们可以来试验一下,首先编写一个User类:
public?class?User?{
????public?User()?{
????????System.out.println("user对象被创建...");
????}
}
编写测试代码:
public?static?void?main(String[]?args)?throws?Exception?{
????ApplicationContext?context?=?new?AnnotationConfigApplicationContext(MyConfiguration.class);
}
当没有添加@Lazy注解,控制台输出:
user对象被创建...
当添加了@Lazy注解,控制台没有任何输出,只有调用了context.getBean_(_"user"_)_; User对象才会被创建。
@Conditional
@Conditional注解的功能是以指定的条件来注册组件,现在我们有两个组件:
@Configuration
public?class?MyConfiguration?{
????@Bean
????public?Watermelon?watermelon()?{
????????return?new?Watermelon();
????}
????@Bean
????public?Kiwi?kiwi()?{
????????return?new?Kiwi();
????}
}
一个是夏季水果西瓜,一个是冬季水果猕猴桃,现在有一个需求是当传入参数为夏天时,就注册西瓜,当传入参数是冬天时,就注册猕猴桃,该如何实现呢?
我们可以借助@Conditional注解来实现,首先创建类实现Condition接口:
public?class?SummerCondition?implements?Condition?{
????@Override
????public?boolean?matches(ConditionContext?context,?AnnotatedTypeMetadata?metadata)?{
????????String?season?=?System.getProperty("season");
????????return?"summer".equals(season);
????}
}
public?class?WinterCondition?implements?Condition?{
????@Override
????public?boolean?matches(ConditionContext?context,?AnnotatedTypeMetadata?metadata)?{
????????String?season?=?System.getProperty("season");
????????return?"winter".equals(season);
????}
}
接下来就可以使用它们进行配置了:
@Configuration
public?class?MyConfiguration?{
????@Bean
????@Conditional({SummerCondition.class})
????public?Watermelon?watermelon()?{
????????return?new?Watermelon();
????}
????@Bean
????@Conditional({WinterCondition.class})
????public?Kiwi?kiwi()?{
????????return?new?Kiwi();
????}
}
此时在虚拟机参数位置填写-Dseason=summer ,Watermelon将被注册,当参数被修改为-Dseason=winter 时,Kiwi将被注册,SpringBoot框架的底层就大量地使用到了这个注解,不过这是题外话了,我将在后续SpringBoot系统的文章中对其再度进行介绍。
@Import
我们已经知道,目前将一个组件注册到容器中有多种方式,使用@Bean或者组件扫描都可以,然而在某些情况下,这些方式都不太方便,比如将一个第三方的组件注册到容器中,此时我们可以借助@Import注解:
@Configuration
@Import({Cat.class,?Dog.class})
public?class?MyConfiguration?{
}
另一种方式是使用ImportSelector,创建类实现ImportSelector接口:
public?class?MyImportSelector?implements?ImportSelector?{
????@Override
????public?String[]?selectImports(AnnotationMetadata?importingClassMetadata)?{
????????return?new?String[]{
????????????????"com.wwj.spring.demo.entity.Cat",
????????????????"com.wwj.spring.demo.entity.Dog"
????????};
????}
}
将需要注册到容器中的组件全类名写到数组中,然后@Import注解只需要填写这个类的信息即可:
@Configuration
@Import({MyImportSelector.class})
public?class?MyConfiguration?{
}
还有一种方式是实现ImportBeanDefinitionRegistrar接口,它与第二种方式类似,与之不同的是,这种方式可以自定义组件注册到容器中的名字:
public?class?MyImportBeanDefinitionRegistrar?implements?ImportBeanDefinitionRegistrar?{
????@Override
????public?void?registerBeanDefinitions(AnnotationMetadata?importingClassMetadata,?BeanDefinitionRegistry?registry)?{
????????registry.registerBeanDefinition("myDog",?new?RootBeanDefinition(Dog.class));
????????registry.registerBeanDefinition("myCat",?new?RootBeanDefinition(Cat.class));
????}
}
配置如下:
@Configuration
@Import({MyImportBeanDefinitionRegistrar.class})
public?class?MyConfiguration?{
}
需要注意的是第一种和第二种方式注册的组件,其在容器中的名字是组件的全类名。
FactoryBean
FactoryBean也是Spring提供的一种注册组件的方式,不过它比较特殊,看一个例子:
public?class?User?implements?FactoryBean?{
????@Override
????public?Object?getObject()?throws?Exception?{
????????return?new?User();
????}
????@Override
????public?Class<?>?getObjectType()?{
????????return?User.class;
????}
????@Override
????public?boolean?isSingleton()?{
????????return?true;
????}
}
这三个方法非常好理解:
-
getObject:需要注册的组件 -
getObjectType:需要注册的组件类型 -
isSingleton:需要注册的组件是否是单例
那有同学提出疑问了,这种方式岂不是更加麻烦了,有必要存在吗?当然有了,它牛就牛在你可以随意篡改需要注册的组件,比如:
public?class?User?implements?FactoryBean?{
????@Override
????public?Object?getObject()?throws?Exception?{
????????return?new?Cat();
????}
????@Override
????public?Class<?>?getObjectType()?{
????????return?Cat.class;
????}
????@Override
????public?boolean?isSingleton()?{
????????return?true;
????}
}
现在看似注册的是User对象,其实注册的是Cat,不信我们试试:
public?static?void?main(String[]?args)?throws?Exception?{
????ApplicationContext?context?=?new?AnnotationConfigApplicationContext(MyConfiguration.class);
????Object?user?=?context.getBean("user");
????System.out.println(user.getClass());
}
运行结果:
class?com.wwj.spring.demo.entity.Cat
若是想要获得实现了FactoryBean接口的User对象本身,则需要在名字面前添加& :
public?static?void?main(String[]?args)?throws?Exception?{
????ApplicationContext?context?=?new?AnnotationConfigApplicationContext(MyConfiguration.class);
????Object?user?=?context.getBean("&user");
????System.out.println(user.getClass());
}
运行结果:
class?com.wwj.spring.demo.entity.User
|