Spring注解驱动开发系列:
Spring注解驱动开发1:组件注册
在Spring中可以通过XML文件的方式配置Bean,但是在后来的SpringBoot中,使用更多的是通过注解来配置Spring,因此本系列文章主要介绍Spring注解的使用。
@Configuration和@Bean
在项目中编写如下配置类,使用@Configuration
表示这是Spring的配置类,使用@Bean
表示方法返回的类是一个bean,注册到spring容器中。
//配置类==配置文件
@Configuration
public class MainConfigure {
//给容器中注册一个Bean,类型为返回值的类型,id默认是用方法名作为id
@Bean
public User user() {
return new User("jinchengll", 18);
}
}
在MainTest中编写如下代码来启动Spring和获取Bean:
public class MainTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(MainConfigure.class);
//User user = (User) applicationContext.getBean("user");
User user = (User) applicationContext.getBean(User.class);
System.out.println(user);
}
}
可以通过bean名称和类型进行获取。
输入如下:
User{name='jinchengll', age=18}
如果需要指定Bean的名称,可以有如下两种方法:
- 修改配置类中对应Bean的方法名,例如修改user()为user1(),则Bean的名称就会变成user1。
- 修改配置类中对应Bean的方法的注解,@Bean注解可以指定name,例如
@Bean(name = "user2")
就可以将Bean名称指定为user2。
@ComponentScan-自动包扫描
在上面的例子中,对于Bean需要手动在Configure文件中创建,所以有了@ComponentScan
注解,让Spring自动扫描Bean并装载到容器中。
在配置类中增加@ComponentScan注解,修改MainConfigure类为如下代码:
//配置类==配置文件
@Configuration
//开启自动包扫描,传入要扫描的包路径
@ComponentScan(basePackages = "com.lin.springL")
public class MainConfigure {
}
创建dao、service、controller文件夹,并创建如下代码:
//让Spring扫描到
@Controller
public class UserController {
}
//让Spring扫描到
@Service
public class UserService {
}
//让Spring扫描到
@Component
public class UserDao {
}
在MainTest中进行验证是否被Spring加载:
public class MainTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(MainConfigure.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
得到输入如下:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigure
userController
userDao
userService
其中除了spring自己的一些Bean,我们自定义的Bean都被自动扫描到并加入容器中。其中MainConfigure
也被扫描到,通过进入Configuration
注解类可以看到他也是被@Component
所注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@ComponentScan的属性参数
进入@ComponentScan注解类可以看到其有如下参数:
- Filter[] includeFilters() default {};表示扫描只包含哪些注解
- Filter[] excludeFilters() default {};表示扫描不要包含哪些注解
例子:
要排除Controller和Service注解,则将MainConfigure类更改为如下代码:
//配置类==配置文件
@Configuration
//开启自动包扫描,传入要扫描的包路径
//在这里使用FilterType.ANNOTATION规则来过滤,可以有多种方案,具体进入Filter类查看
@ComponentScan(basePackages = "com.lin.springL", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,
value = {Controller.class, Service.class})})
public class MainConfigure {
}
再次运行MainTest类,得到输入如下:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigure
userDao
很明显,UserController和UserService已经被排除在外了。
自定义Filter过滤规则
Spring允许我们自定义包扫描时候的Filter规则,这时候需要将type指定为CUSTOM,例子如下:
实现org.springframework.core.type.filter.TypeFilter:
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);
return "com.lin.springL.dao.UserDao".equals(className);
}
}
更改MainConfigure类为:
//配置类==配置文件
@Configuration
//开启自动包扫描,传入要扫描的包路径
@ComponentScan(basePackages = "com.lin.springL", excludeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, value = {MyTypeFilter.class})})
public class MainConfigure {
}
执行MainTest得到结果如下:
===>com.lin.springL.MainTest
===>com.lin.springL.config.MyTypeFilter
===>com.lin.springL.controller.UserController
===>com.lin.springL.dao.UserDao
===>com.lin.springL.pojo.User
===>com.lin.springL.service.UserService
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigure
userController
userService
说明自定义的Filter起作用,并且将匹配到的Bean过滤掉。
@Scope-设置组件作用域
在上面我们使用的注解默认是单例模式,也就是多次获取同一个名称的Bean,得到的都是同一个Bean,更改MainTest代码如下:
public class MainTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(MainConfigure.class);
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
UserDao userDao1 = (UserDao) applicationContext.getBean("userDao");
System.out.println(userDao == userDao1);
}
}
// 输出结果:true
如果需要多实例,可以使用@Scope来设置,修改UserDao类为如下代码:
@Component
//设置作用域
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class UserDao {
}
再次运行MainTest,将会得到结果:false。
scopeName属性参数
总共有四种作用域:
- ConfigurableBeanFactory.SCOPE_PROTOTYPE:多实例模式
- ConfigurableBeanFactory.SCOPE_SINGLETON:单实例模式(默认模式)
- org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST:基于Web,一次请求新建一个Bean
- org.springframework.web.context.WebApplicationContext.SCOPE_SESSION:基于Web,一次Session中新建一个Bean
@Lazy-bean懒加载
对于上面提到的Bean,都是在容器启动的时候就会创建对象。懒加载的意思就是在容器启动的时候不创建对象,而是等到第一次使用(getBean)的时候创建对象,避免空间的浪费。
非懒加载例子
更改UserDao的代码如下:
@Component
public class UserDao {
public UserDao() {
System.out.println("UserDao init======");
}
}
MainTest的代码如下:
public class MainTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(MainConfigure.class);
}
}
运行MainTest,得到结果如下:
UserDao init======
说明在容器启动时候,UserDao就已经被创建对象。
懒加载例子
在UserDao类上加入@Lazy注解:
@Component
@Lazy
public class UserDao {
public UserDao() {
System.out.println("UserDao init======");
}
}
再次运行MainTest发现没有的输出,代表着时候UserDao并没有被创建对象。
更改MainTest代码如下:
public class MainTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(MainConfigure.class);
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
}
}
得到输入如下:
UserDao init======
说明在获取Bean的时候,UserDao才被创建对象。
@Conditional-按照条件注册
上面提到的@Bean注解是都会往容器中注册bean,但是@Conditional可以先判断一个条件,条件成立才进行注册。
例子
编写两个Condition:
public class LinCondition implements Condition {
/**
* @param context 判断条件能使用的上下文环境
* @param metadata 当前类的注释信息
* @return 是否匹配上
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//在这里返回false,让其不通过
return false;
}
}
public class KeCondition implements Condition {
/**
* @param context 判断条件能使用的上下文环境
* @param metadata 当前类的注释信息
* @return 是否匹配上
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//在这里返回true,让其通过
return true;
}
}
更改MainConfigure代码如下:
//配置类==配置文件
@Configuration
public class MainConfigure {
@Conditional(value = {LinCondition.class})
@Bean
public User user1() {
return new User("lin", 33);
}
@Conditional(value = {KeCondition.class})
@Bean
public User user2() {
return new User("ke", 23);
}
}
更改MainTest代码如下:
public class MainTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(MainConfigure.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
运行MainTest得到结果:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigure
user2
可以看到Condition返回true的Bean会被加载。
@Import-给容器中快速导入一个组件
通常如果是我们自己编写的类,可以使用上面提到的@Controller等注解来注册到容器中,但是如果需要导入第三方jar包里面的类的时候,这种方法就不适用了,对于这种情况,Spring提供了两种方式,一种是在配置文件中new出对应的类并使用@Bean注释方法,还有一种是使用@Import注解来帮助导入。
例子
在配置文件中使用Import,更改MainConfigure代码如下:
//配置类==配置文件
@Configuration
@Import(value = {User.class})
public class MainConfigure {
}
运行MainTest得到如下结果:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfigure
com.lin.springL.pojo.User
包含了User类,id默认为全类名。
FactoryBean创建Bean
可以用FactoryBean来包装一个对象给容器
例子
编写UserFactoryBean代码如下:
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User("factorybean", 33);
}
@Override
public Class<?> getObjectType() {
return User.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
更改MainConfigure文件代码如下:
//配置类==配置文件
@Configuration
public class MainConfigure {
@Bean
public UserFactoryBean userFactoryBean() {
return new UserFactoryBean();
}
}
更改MainTest代码如下:
public class MainTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(MainConfigure.class);
Object userFactoryBean = applicationContext.getBean("userFactoryBean");
System.out.println(userFactoryBean.getClass());
}
}
运行MainTest得到结果如下:
class com.lin.springL.pojo.User
虽然我们注册的是一个FactoryBean,但是Spring对通过内部的getObject方法获得真实的Bean。