Spring 注解回顾
在Spring框架里面常用的注解其实就那么几个,但是深入源码分析如果不懂很多的注解,源码是很难看懂的。
- 一、@Configuration
- 二、@Bean
- 三、@ComponentScan
- 四、@Scope
- 五、@Condition
- 六、@Import
- 七、使用FactoryBean注册组件
- 八.在这里也算上@Compont组件,为了后面好区分@Configuration
给容器中注册组件常用
包扫描+组件标注注解(@Component、@Service、@Controller、@Repository),主要是自己写的类,这三个用于不同的场景。
@Bean [导入的第三方包里面的组件]
@Import [快速给容器中导入一个组件]
Import(类名.class),容器中就会自动注册这个组件,id默认是组件的全名
ImportSelector:返回需要导入的组件的全类名的数组
ImportBeanDefinitionRegistrar:手动注册bean
使用Spring提供的FactoryBean(工厂bean)
默认获取到的是工厂bean调用getObject创建的对象
要获取到bean本身,需要给id前面加个&标识
@Conditional({Condition}) :按照一定的条件判断,满足条件给容器中注册bean
@Scope
prototype:多例的 ioc容器启动并不会去调用方法创建对象在容器中,而是每次获取时才会调用方法创建对象
singleton:单例的(默认值) ioc容器启动会调用方法创建对象放到ioc容器中,以后每次获取就是从容器中拿
一、@Configuration
@Configuration : 把一个类标记为spring的配置类,相当于之前的applicationContext.xml文件
1、看看之前通过applicationContext.xml配置文件来创建类的实例
public class SomeBean {}
<?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"> <!-- 创建SomeBean的实例,交给IoC来管理 --> <bean id="somebean" class="com.zy._01_hello.SomeBean"/> </beans>
测试方法
public class SomeBeanTest { /* Spring XML Config */ @Test public void test(){ // 加载配置文件 ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); // 从Spring容器中获取SomeBean的实例 SomeBean someBean = ctx.getBean("somebean", SomeBean.class); System.out.println(someBean); } }
这种方式是通过Spring XML Config的方式来将类交给Spring容器处理; 但是后来发现有很多这样的xml不容易管理,形成了配置类; 于是就有了后来的SpringBoot 在SpringBoot中几乎看不到配置文件了,取而代之的是Spring Java Config的配置类的形式!
2、创建配置类
// 把一个类标记为spring的配置类; (类名Config可以简单理解为XML中的 beans)
@Configuration public class Config { @Bean public SomeBean somebean(){ return new SomeBean(); } }
测试方法
public class SomeBeanTest { /* Spring Java Config */ @Test public void test1(){ ApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); SomeBean someBean = ctx.getBean(SomeBean.class); System.out.println(someBean); } }
这里要通过AnnotationConfigApplicationContext(config.class)来加载配置类,然后拿到配置类中SomeBean的实例即可(将组件的创建交给Spring容器处理, 也就是将组件注册到容器中)!
二、@Bean
@Bean
相当于在配置文件中写的<bean id="" class="" />
, 将一个类的创建实例交给Spring IoC来处理;
在配置文件
中写的bean
<bean id="" class="" name="" init-method="" destory-method="" scope="">
<property name="" value=""/>
<property name="" ref=""/>
</bean>
在配置类
中写的bean
@Configuration public class Config { @Bean public SomeBean someBean1() { return new SomeBean(); } @Bean public SomeBean someBean2() { return new SomeBean(); } @Bean(name = {"sb", "sbb"}) public SomeBean someBean3() { return new SomeBean(); } }
1、在配置类中@Bean的含义
- 被@Bean标注的
方法的名字
—> bean的id - 方法的
返回值类型
—> bean的class类型 - 除了默认使用方法的名字作为id外, 还可以通过
@Bean(name={"xxx1", "xxx2"})
来指定多个id名
@Test public void test2() { ApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); SomeBean someBean1 = ctx.getBean("someBean1", SomeBean.class); // bean的id去找 System.out.println(someBean1); SomeBean someBean2 = ctx.getBean("someBean2", SomeBean.class); System.out.println(someBean2); SomeBean someBean3 = ctx.getBean("sb", SomeBean.class); System.out.println(someBean3); SomeBean someBean4 = ctx.getBean("sbb", SomeBean.class); System.out.println(someBean4); }
2、配置initMethod、destroyMethod的方法
构造(对象创建)
- 单例: 在容器启动(加载配置类/加载配置文件)的时候创建对象;
容器启动先创建对象, 然后调用init(初始化方法);
- 多例: 在每次获取bean(getBean())的时候创建对象;
容器创建完成之后, 才创建对象完成init(初始化);
方式一: 可以在@Bean中的属性initMethod, destroyMethod来指定初始化,销毁方法
@Bean(name="sb", initMethod = "init", destroyMethod = "destory")
方式二: @PostConstruct
,@PreDestroy
来指定初始化,销毁方法
public class SomeBean { // 方式二: 配置init,destory @PostConstruct,@PreDestroy @PostConstruct public void init() { System.out.println("SomeBean.init"); } @PreDestroy public void destory() { System.out.println("SomeBean.destory"); } }
测试方法
public class SomeBeanTest { /* Spring Java Config */ @Test void test(){ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); SomeBean someBean = ctx.getBean("sb", SomeBean.class); System.out.println(someBean); ctx.close(); // 非Spring Test,容器不会正常关闭,调用close方法才可以 } }
3、@Bean依赖注入的方式
otherBean实体
public class OtherBean { } @Setter @Getter public class SomeBean { private OtherBean otherBean; }
方式一: 通过类似内部bean
的方式
@Configuration public class Config { /* 方式一: 相当于内部Bean的形式 <bean id="" class=""> <property name="otherBean"> <bean class="" /> 内部bean的方式 </property> </bean> 这种方式用的很少! */ @Bean public SomeBean someBean() { SomeBean sb = new SomeBean(); sb.setOtherBean(new OtherBean()); return sb; } }
方式二: 通过调用需要注入的Bean的方式名()
即可
@Configuration public class Config { @Bean public SomeBean someBean() { SomeBean sb = new SomeBean(); sb.setOtherBean(otherBean()); return sb; } @Bean public SomeBean someBean2() { SomeBean sb = new SomeBean(); sb.setOtherBean(otherBean()); return sb; } @Bean public OtherBean otherBean() { return new OtherBean(); } }
方式三: 需要依赖的Bean, 放入到参数列表中,会自动注入;
当有多个OtherBean的实例时,可以使用 @Qualifier("bean的id")来指定
有多个OtherBean时,在某个bean上添加 @Primary, 会优先注入该bean
有多个OtherBean时,在参数列表中通过形参名称来指定对应的bean
@Bean // public SomeBean someBean(@Qualifier("otherBean") OtherBean ob) { public SomeBean someBean(OtherBean otherBean) { SomeBean sb = new SomeBean(); sb.setOtherBean(otherBean); return sb; } @Bean //@Primary public OtherBean otherBean() { return new OtherBean("ob1"); } @Bean public OtherBean otherBean2() { return new OtherBean("ob2"); }
三、@ComponentScan
可以完成Spring组件的自动扫描
(默认情况下,会去扫描被标注的类的对应的包(及其子包中)的所有的类;
配置类Config
@Configuration // 可以完成Spring组件的自动扫描(默认情况下,会去扫描被标注的类的对应的包(及其子包中)的所有的类 //@ComponentScan(basePackages = "com.zy._04_componentscan") // 也可以自己指定扫描的范围 @ComponentScan public class Config { }
OtherBean和SomeBean类
@Component //设置该类作为Spring管理的组件 public class OtherBean { } @Component public class SomeBean { @Autowired // 将Spring通过@Component创建好的OtherBean的实例,注入到下面的属性中 private OtherBean otherBean; }
组件注册时的过滤条件
- @ComponentScan value:指定要扫描的包
- excludeFilters=Filter[]:指定扫描包的时候按照什么规则排除哪些组件
- includeFilters=Filter[]:指定扫描包的时候要包含哪些组件,需将useDefaultFilters置false
- FilterType.ANNOTATION:按照注解
- FilterType.ASSIGNABLE_TYPE:按照指定的类型
- FilterType.REGEX:使用正则指定
- FilterType.CUSTOM:使用自定义规则
// 配置类 == 配置文件 @Configuration // 告诉Spring这是一个配置类 //@ComponentScan(value = "com.zy") // 不写默认扫描当前类所在包(及其子包)下的所有类(指定要扫描的包) //@ComponentScan(value = "com.zy", excludeFilters = { // // excludeFilters: FilterType是过滤条件(这里是根据注解来过滤); 将Controller,Service的bean过滤掉(不扫描它们) // @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}) //}) @ComponentScan(value = "com.zy", includeFilters = { // excludeFilters: FilterType是过滤条件(这里是根据注解来过滤); 将Controller,Service的bean过滤掉(不扫描它们) @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}), @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class}) }, useDefaultFilters = false) // @ComponentScan value: 指定扫描的包 // excludeFilters = Filter[] : 指定扫描的时候按照什么规则排除哪些组件 // includeFilters = Filter[] : 指定扫描的时候只需要包含哪些组件 // FilterType.ANNOTATION: 按照注解作为过滤规则 // FilterType.ASSIGNABLE_TYPE: 按照给定的类型作为过滤规则 public class MainConfig { // 给容器中注册一个Bean, 类型为返回值类型, id默认是方法名 @Bean("person") public Person person(){ return new Person("lisi", 22); } }
测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = MainConfig.class) public class IoCTest { @Test public void test1(){ ApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class); // 看容器中有哪些bean,返回这些bean的名称 String[] beanDefinitionNames = ctx.getBeanDefinitionNames(); for (String name : beanDefinitionNames) { System.out.println(name); } } }
四、@Scope
- 用来表示bean的范围(是单例还是多例)
- prototype: 多例的 : IoC容器启动 并不会去调用方法创建对象 放在容器中, 每次获取bean的时候才会调用方法创建对象;
- singleton: 单例的 : IoC容器启动就会 调用方法创建对象 放到IoC容器中;
- request: 同一次请求创建一个实例
- session: 同一个session创建一个实例
@Scope("prototype") //@Scope // 默认不写value就是singleton @Bean public Person person(){ System.out.println("给容器中添加Person..."); return new Person("张三", 22); }
@Test public void test2(){ ApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig2.class); // 看容器中有哪些bean,返回这些bean的名称 // String[] beanDefinitionNames = ctx.getBeanDefinitionNames(); // for (String name : beanDefinitionNames) { // System.out.println(name); // } // 默认是单例的 System.out.println("IoC容器创建完成..."); Object person1 = ctx.getBean("person"); Object person2 = ctx.getBean("person"); System.out.println(person1 == person2); }
@Lazy
第一次使用Bean的时候创建,不使用则不创建(即使IoC容器启动了) ;
//@Scope("prototype") @Scope @Lazy //第一次使用Bean的时候创建,不使用则不创建(即使IoC容器启动了) @Bean public Person person(){ System.out.println("给容器中添加Person..."); return new Person("张三", 22); }
五、@Condition
@Conditional
: 按照一定的条件进行判断,满足条件给容器中注册bean
MacOSXCondition
// 判断是否Mac系统 public class MacOSXConditaion implements Condition { /** * * @param conditionContext :判断条件能使用的上下文(环境) * @param annotatedTypeMetadata : 注释信息 * @return */ @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { // 是否Mac系统 //1. 能获取到IoC使用的beanfactory ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory(); //2. 获取类加载器 ClassLoader classLoader = conditionContext.getClassLoader(); //3. 获取当前环境信息(封住系统的的环境信息等,虚拟机等信息) Environment environment = conditionContext.getEnvironment(); //4. 获取到bean定义的注册类 BeanDefinitionRegistry registry = conditionContext.getRegistry(); String property = environment.getProperty("os.name"); if (property.contains("Mac OS X")) return true; return false; } }
WindowsCondition
// 判断是否windows系统 public class WindowsCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { Environment environment = conditionContext.getEnvironment(); String property = environment.getProperty("os.name"); if (property.contains("Windows")) return true; return false; } }
测试:
@Test public void test3(){ // 根据Person类型来获取容器中bean的名称 String[] beanNamesForType = ctx.getBeanNamesForType(Person.class); // 动态获取环境变量的值: Mac OS X Environment environment = ctx.getEnvironment(); String property = environment.getProperty("os.name"); System.out.println(property); for (String name : beanNamesForType) { System.out.println(name); } Map<String, Person> persons = ctx.getBeansOfType(Person.class); System.out.println(persons); }
六、@Import
- 作用:用于导入
其他的配置类
。 - 也可以导入一个需要注册的
组件
(类), id默认是全类名
;
DataSource类
public class DataSource { }
RedisTemplate类
public class RedisTemplate { }
DataSourceConfig配置类
@Configuration public class DataSourceConfig { @Bean public DataSource dataSource(){ return new DataSource(); } }
RedisConfig配置类
@Configuration public class RedisConfig { @Bean public RedisTemplate redisTemplate(){ return new RedisTemplate(); } }
AppConfig配置类
@Configuration @Import({DataSourceConfig.class, RedisConfig.class}) public class AppConfig { }
ImportTest测试类
@RunWith(SpringJUnit4ClassRunner.class) //@ContextConfiguration(classes = {DataSourceConfig.class, RedisConfig.class}) @ContextConfiguration(classes = AppConfig.class) public class ImportTest { @Autowired private DataSource ds; @Autowired private RedisTemplate rt; @Test public void test() { System.out.println(ds); System.out.println(rt); } }
1、ImportSelector 接口
返回需要导入的组件
的全类名
- 需要导入的组件
自定义MyImportSelector
// 自定义逻辑返回需要导入的组件 public class MyImportSelector implements ImportSelector { /** * * @param annotationMetadata 当前标注@Import注解类的所有注解信息 * @return 返回要导入到容器中的组件的全类名 */ @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{"com.zy.beans.Blue","com.zy.beans.Red"}; } }
配置类
@Configuration @Import(MyImportSelector.class) public class MainConfig2 { }
测试
@Test public void testImport(){ ApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig2.class); printBeans((AnnotationConfigApplicationContext) ctx); Blue bean = ctx.getBean(Blue.class); System.out.println(bean); // 这样就可以成功在Spring 容器中获取到bean了 } private void printBeans(AnnotationConfigApplicationContext atx){ String[] definitionNames = atx.getBeanDefinitionNames(); for (String name : definitionNames) { System.out.println(name); } }
2、@ImportResource
@ImportResource
来引入xml配置文件
, 使xml和javaconfig共同使用;
public class OtherBean { } @Setter @Getter public class SomeBean { private OtherBean otherBean; }
配置类
@Configuration @ImportResource("classpath:applicationContext.xml") public class AppConfig { @Bean public SomeBean someBean(OtherBean otherBean){ SomeBean sb = new SomeBean(); sb.setOtherBean(otherBean); return sb; } }
测试类
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = AppConfig.class) public class AppTest { @Autowired private SomeBean someBean; @Test public void test() { System.out.println(someBean.getOtherBean()); } }
七、使用FactoryBean注册组件
/ 创建一个Spring定义的FactoryBean public class ColorFactoryBean implements FactoryBean<Color> { // 返回一个Color对象,这个对象会添加到容器中 @Override public Color getObject() throws Exception { System.out.println("ColorFactoryBean.getObject"); return new Color(); } @Override public Class<?> getObjectType() { return Color.class; } // 是否是单例: true, 在容器中只会保留一份 @Override public boolean isSingleton() { return true; } }
配置类
@Configuration public class MainConfig{ // 实际返回的是getObject()方法返回的对象 @Bean public ColorFactoryBean colorFactoryBean(){ return new ColorFactoryBean(); } }
测试
@Test public void testImport() { ApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig2.class); printBeans((AnnotationConfigApplicationContext) ctx); Blue bean = ctx.getBean(Blue.class); System.out.println(bean); // 工厂Bean获取的是调用getObject创建的对象 Object bean2 = ctx.getBean("colorFactoryBean"); Object bean3 = ctx.getBean("colorFactoryBean"); System.out.println("bean的类型:" + bean2.getClass()); System.out.println(bean2 == bean3); // 获取ColorFactorybean的本身 Object bean4 = ctx.getBean("&colorFactoryBean"); System.out.println(bean4.getClass()); } private void printBeans(AnnotationConfigApplicationContext atx) { String[] definitionNames = atx.getBeanDefinitionNames(); for (String name : definitionNames) { System.out.println(name); } }