所有的组件都应该放进IOC容器中,组件之间的关系通过容器实现自动装配,也就是依赖注入。对于如何将组件注册到容器中,本文从使用的角度出发详细阐述配置文件和注解的实现方式。涉及的注解还是挺多的,不过还是需要记忆一下,尤其是设置bean作用域的注解,面试中被问到过如何设置为多例。
新建一个maven
工程,引入spring-context
依赖。
1. @Configuration & @Bean给容器注册组件
以往的方式注册一个bean
新建一个实体类Person:
1 2 3 4 5 6 7 8 @Data @AllArgsConstructor @NoArgsConstructor @ToString public class Person { private String name; private Integer age; }
那么,我们可以在beans.xml中注册这个bean,给他赋值。
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="person" class ="com.swg.bean.Person" > <property name ="age" value ="10" /> <property name ="name" value ="张三" /> </bean > </beans >
那么,我们就可以拿到张三这个人了:
1 2 3 4 5 6 7 public class MainTest { public static void main (String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml" ); Person person = (Person) applicationContext.getBean("person" ); System.out.println(person); } }
注解的方式注册bean
配置类 = 配置文件
@Configuration 告诉spring这是一个配置类
@Bean 给容器注册一个Bean,类型为返回值类型,id默认是方法名
1 2 3 4 5 6 7 @Configuration public class MainConfig { @Bean public Person person () { return new Person("李四" ,20 ); } }
如何获取这个bean呢?
1 2 3 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); Person person = applicationContext.getBean(Person.class); System.out.println(person);
我们还可以根据类型来获取这个bean在容器中名字是什么:
1 2 3 4 String[] names = applicationContext.getBeanNamesForType(Person.class); for (String name:names){ System.out.println(name); }
上面提到,id默认是方法名。如果我们修改MainConfig中的person这个方法名,果然打印结果也随着这个方法名改变而改变;也可以自己另外指定这个bean在容器中的名字:@Bean(“hello”),那么这个bean的名字就变成了hello.
2. @ComponentScan自动扫描组件以及扫描规则
配置文件中配置包扫描时这样配置的:
1 2 <context:component-scan base-package ="com.swg" />
现在用注解来实现这个功能:
只需要加上注解即可:
1 2 @ComponentScan (value = "com.swg" )
我们增加BookController.java,BookService.java以及BookDao.java三个类,并且分别加上注解:@Controller,@Service,@Repository;那么包扫描就可以把这些类全部注册到IOC容器中了。
我们来打印一下目前所有注册到IOC容器的类的名称:
1 2 3 4 5 6 7 8 9 @Test public void shouldAnswerWithTrue () { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); String[] names = applicationContext.getBeanDefinitionNames(); for (String name:names){ System.out.println(name); } }
输出结果:
1 2 3 4 5 6 7 8 9 10 11 12 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 //以上是spring IOC容器自身需要的组件,下面是我们自定义的组件 mainConfig//主配置类,因为有注解@Configuration,而这个注解本身是有@Component的,所以也是一个bean bookController//@Controller bookDao//@Repository bookService//@Service person//这是由自己@Bean注册进去的
上面的扫描路径是扫描所有的,有的时候我们需要排除掉一些扫描路径或者只扫描某个路径,如何做到呢?
用excludeFilters
来排除,里面可以指定排除规则,这里是按照ANNOTATION
来排除,排除掉所有@Controller
注解的类。classes
也是个数组,可以排除很多。
1 2 3 @ComponentScan (value = "com.swg" ,excludeFilters = { @ComponentScan .Filter(type = FilterType.ANNOTATION,classes = Controller.class) })
那么效果就是controller
没有了,但是service
和dao
都在。
那如果我想只包含controller
呢?
1 2 3 @ComponentScan (value = "com.swg" , includeFilters = { @ComponentScan .Filter(type = FilterType.ANNOTATION,classes = Controller.class) },useDefaultFilters = false )
注意要useDefaultFilters = false
,因为默认为true
,就是扫描所有,不设置为false
无效。
3. 自定义TypeFilter制定过滤规则
上面包扫描是按照FilterType.ANNOTATION
规则来实现的,他还有其他几种规则:
1 2 3 4 5 6 7 8 9 10 public enum FilterType { ANNOTATION, ASSIGNABLE_TYPE, ASPECTJ, REGEX, CUSTOM; private FilterType () { } }
对于最后的CUSTOM
,这里着重说一说怎么用。
首先是要求实现FilterType
接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class MyTypeFilter implements TypeFilter { @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 false ; } }
返回false,表示不匹配,返回true的就匹配。这里默认是false;
在mainConfig
类中配置这个自定义的过滤规则:
1 2 3 @ComponentScan (value = "com.swg" , includeFilters = {@ComponentScan .Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})},useDefaultFilters = false )
那么此时输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ---->com.swg.AppTest ---->com.swg.bean.Person ---->com.swg.controller.BookController ---->com.swg.dao.BookDao ---->com.swg.FilterType.MyTypeFilter ---->com.swg.MainTest ---->com.swg.service.BookService 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 mainConfig person
就是显示了所有他处理的类,最后由于都返回fasle,那么那些controller,service都将被过滤掉。
下面指定通过一个:
1 2 3 if (className.contains("er" )){ return true ; }
输出:
1 2 3 4 5 mainConfig person bookController//new myTypeFilter//new bookService//new
4. @Scope-设置组件作用域
spring的bean默认是单实例,下面佐证一下:
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test02 () { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); String[] names = applicationContext.getBeanDefinitionNames(); for (String name:names){ System.out.println(name); } Object bean = applicationContext.getBean("person" ); Object bean1 = applicationContext.getBean("person" ); System.out.println(bean == bean1); }
那么我们可以配置bean为多例吗?显然是可以的:
1 2 3 4 5 @Bean @Scope ("prototype" )public Person person () { return new Person("李四" ,20 ); }
@Scope
注解中有四个选项:
prototype
:多例
singleton
:单例,默认
request
:同一次请求创建一个实例
session
:同一个session创建一个实例
着重看一下singleton
和prototype
,他们的加载时机?
⭐⭐⭐singleton
:IOC容器启动时调用方法创建对象放到IOC容器中,以后每次获取都直接从容器中拿,类似于map.get();
⭐⭐⭐prototype
:IOC容器启动时不会创建对象,而是在每次获取时才会调用方法创建对象;并且是新new出来的对象,都是不一样的。
5. @lazy-bean-懒加载
单实例bean,默认在容器启动时创建对象。
即只要执行了:
1 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
Person这个对象就会加载在容器中。测试一下:
1 2 3 4 5 6 7 8 @Configuration public class MainConfig2 { @Bean public Person person () { System.out.println("创建对象Person" ); return new Person("李四" ,20 ); } }
懒加载:容器启动时不创建对象,第一次使用(获取)Bean创建对象。
1 2 3 4 5 6 7 8 9 @Configuration public class MainConfig2 { @Bean @Lazy public Person person () { System.out.println("创建对象Person" ); return new Person("李四" ,20 ); } }
这个时候,就不会在容器一启动的时候就加载了。那什么时候加载呢?
我获取一下这个对象:
1 2 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); Object bean = applicationContext.getBean("person" );
这个时候,@Bean就被创建了。这就是懒加载。
6. @Conditional-按照条件注册bean
按照一定的条件进行判断,满足条件给容器注册bean。
先创建三个bean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Configuration public class MainConfig2 { @Bean public Person person () { return new Person("李四" ,20 ); } @Bean ("bill" ) public Person person01 () { return new Person("Bill" ,60 ); } @Bean ("linus" ) public Person person02 () { return new Person("linus" ,50 ); } }
打印一下创建的bean:
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test03 () { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); String[] names = applicationContext.getBeanNamesForType(Person.class); for (String name:names){ System.out.println("--->" +name); } Map<String,Person> types = applicationContext.getBeansOfType(Person.class); System.out.println(types); }
打印结果:
1 2 3 4 --->person --->bill --->linus {person=Person(name=李四, age=20), bill=Person(name=Bill, age=60), linus=Person(name=linus, age=50)}
那假设一个场景:如果系统是windows,给容器注册“bill”;如果系统是linux,给容器注册“linus”;
至于获取操作系统是什么,我们可以:
1 2 3 ConfigurableEnvironment environment = (ConfigurableEnvironment) applicationContext.getEnvironment(); String osName = environment.getProperty("os.name" ); System.out.println(osName);
那么我们如何根据条件来注册bean呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class LinuxCondition implements Condition { @Override public boolean matches (ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { ConfigurableListableBeanFactory factory = conditionContext.getBeanFactory(); ClassLoader classLoader = conditionContext.getClassLoader(); Environment environment = conditionContext.getEnvironment(); BeanDefinitionRegistry registry = conditionContext.getRegistry(); String osName = environment.getProperty("os.name" ); if (osName.contains("Linux" )){ return true ; } return false ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration public class MainConfig2 { @Bean public Person person () { return new Person("李四" ,20 ); } @Conditional ({WindowsCondition.class}) @Bean ("bill" ) public Person person01 () { return new Person("Bill" ,60 ); } @Conditional (LinuxCondition.class) @Bean ("linus" ) public Person person02 () { return new Person("linus" ,50 ); } }
那么运行结果可以预测到:由于我们是windows系统,所以linux的就不能注册进容器了。
1 2 3 4 Windows 7 --->person --->bill {person=Person(name=李四, age=20), bill=Person(name=Bill, age=60)}
7. @Import-给容器中快速导入一个组件
上面所说得给容器注册组件的方式是:
包扫描+组件标注注解:@Controller
,@Service
,@Repository
,
@Component
比较方便,但是有局限性:如果是注册第三方包怎么办呢?
有一种是:@Bean
[导入第三方包里面的组件],对于简单的可用这样用
还有一种是:@Import
,快速给容器导入一个组件
比如我随便新建一个类叫Dog
,里面啥注解和内容都不写。默认他是不会导入进去的。但是我在webconfig
类上增加注解:
@Import(Dog.class)
那么再次打印出所有注册进容器的组件时,会出现
com.swg.bean.Dog
可见,@import
注解可以方便快速地导入一个组件,并且id默认是组件的全类名
那如何导入多个呢?
@Import({Dog.class, Cat.class})
8. @Import-使用ImportSelector
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{"com.swg.bean.Dog" ,"com.swg.bean.Cat" ,"com.swg.bean.pig" }; } }
然后打上注解导入进来即可:
@Import(MyImportSelector.class)
这里导入的实际上不是MyImportSelector.class
这个类,而是他返回的组件全类名
9. @Import-使用ImportBeanDefinitionRegistrar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions (AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) { boolean definition = registry.containsBeanDefinition("com.swg.bean.Pig" ); boolean definition2 = registry.containsBeanDefinition("com.swg.bean.Cat" ); if (definition && definition2){ RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Bull.class); registry.registerBeanDefinition("bull" ,rootBeanDefinition); } } }
然后打上注解导入进来即可:
@Import(MyImportBeanDefinitionRegistrar.class)
10. 使用FactoryBean注册组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class AnimalFactory implements FactoryBean { @Override public Object getObject () throws Exception { System.out.println("AnimalFactory...getObject()..." ); return new Pig(); } @Override public Class<?> getObjectType() { return Pig.class; } @Override public boolean isSingleton () { return true ; } }
我们先将它用@Bean添加进容器看看:
1 2 3 4 @Bean public AnimalFactory animalFactory () { return new AnimalFactory(); }
显示的id是animalFactory,我们根据这个id获取一下这个bean的类型:
1 2 Object bean = applicationContext.getBean("animalFactory" ); System.out.println("bean的类型:" +bean.getClass());
结果显示:
1 2 AnimalFactory...getObject()... bean的类型:class com.swg.bean.Pig
就是说,这个bean的类型就是getObject方法中返回的Pig对象。
那如果我们想获取这个工厂对象呢?也是可以的,id前面加上&即可。
1 2 Object bean = applicationContext.getBean("&animalFactory" ); System.out.println("bean的类型:" +bean.getClass());
原因是在BeanFactory中定义了一个前缀&,只要是以&为前缀,表示拿FactoryBean本身。
1 2 public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&" ;
11. 总结
给容器中注册组件:
包扫描+组件标注注解:@Controller,@Service,@Repository,@Component
@Bean[导入第三方包里面的组件]
@Import,快速给容器导入一个组件—重要
1).@Import(要导入到容器中的组件);容器会自动注册这个组件,id默认是全类名
2).@ImportSelector:返回要导入的组件的全类名数组
3).@ImportBeanDefinitionRegistrar:手动注册bean到容器中
使用spring提供的@FactoryBean(工厂bean)来注册bean
@Conditional按照条件注册bean—重要
@Scope作用域
懒加载,单例和多例是不一样的