Spring源码分析之二 IOC容器底层注解使用

二:Spring IOC 容器底层注解使用

xml配置文件的形式 VS 配置类的形式

①:基于xml的形式定义Bean的信息

<?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的信息 
    <bean id="car" class="com.silas.compent.Car"> </bean> 
</beans> 

去容器中读取Bean

 public static void main( String[] args ){ 
         ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");         
         System.out.println(ctx.getBean("car"));     
 }

②:基于读取配置类的形式定义Bean信息

@Configuration public class MainConfig { 
    @Bean     
    public Person person(){
             return new Person();
    }
 }

注意: 通过@Bean的形式是使用的话, bean的默认名称是方法名,若@Bean(value=“bean的名称”) 那么bean的名称是指定的 去容器中读取Bean的信息(传入配置类)

    public static void main( String[] args ) {
             AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);                  
             System.out.println(ctx.getBean("person"));     
   }

在配置类上写@CompentScan注解来进行包扫描

@Configuration 
@ComponentScan(basePackages = {"com.silas.testcompentscan"}) 
public class MainConfig { }

①:排除用法 excludeFilters(排除@Controller注解的,和SilasService的)

@Configuration 
@ComponentScan(basePackages = {"com.silas.testcompentscan"},excludeFilters = {
         @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),         
         @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {SilasService.class}) 
}) 
public class MainConfig { } 

②:包含用法 includeFilters ,注意,若使用包含的用法,需要把useDefaultFilters属性设置为false(true表示扫描全部的)

@Configuration 
@ComponentScan(basePackages = {"com.silas.testcompentscan"},includeFilters = {         
	@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class, Service.class}) },useDefaultFilters = false) 
public class MainConfig { }

③ @ComponentScan.Filter type的类型
a) 注解形式的FilterType.ANNOTATION

@Controller @Service @Repository @Compent

b) 指定类型的 FilterType.ASSIGNABLE_TYPE

@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {SilasService.class})

c) aspectj类型的

FilterType.ASPECTJ(不常用)

d) 正则表达式的

FilterType.REGEX(不常用)

e) 自定义的

FilterType.CUSTOM

public enum FilterType { 

    //注解形式  比如@Controller @Service @Repository  @Compent  
   	 	ANNOTATION, 
   	 	
    //指定的类型  
    	ASSIGNABLE_TYPE, 
    	
    //aspectJ形式的  
    	ASPECTJ, 
    	
    //正则表达式的  
    	REGEX, 
    
    //自定义的  
    	CUSTOM 
}

③.①FilterType.CUSTOM 自定义类型如何使用

public class SilasFilterType implements TypeFilter { 
    @Override     
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获取当前类的注解源信息         
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); 
        //获取当前类的class的源信息         
        ClassMetadata classMetadata = metadataReader.getClassMetadata();         
        //获取当前类的资源信息         
        Resource resource =  metadataReader.getResource(); 
        if(classMetadata.getClassName().contains("dao")) {
             return true;         
        }         
        return false; 
        }    
 }
@ComponentScan(basePackages = {"com.silas.testcompentscan"},includeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM,value = SilasFilterType.class) 
   	},useDefaultFilters = false) 
public class MainConfig { }

配置Bean的作用域对象

①:在不指定@Scope的情况下,所有的bean都是单实例的bean,而且是饿汉加载(容器启动实例就创建 好了)

 @Bean     
 public Person person() {
          return new Person();
 }

②:指定@Scope为 prototype 表示为多实例的,而且还是懒汉模式加载(IOC容器启动的时候,并不会创建对象,而是 在第一次使用的时候才会创建)

 @Bean     
 @Scope(value = "prototype")     
 public Person person() {         
 	return new Person();     
 }

③:@Scope指定的作用域方法取值
a) singleton 单实例的(默认)
b) prototype 多实例的
c) request 同一次请求
d) session 同一个会话级别

Bean的懒加载@Lazy

(主要针对单实例的bean 容器启动的时候,不创建对象,在第一次使用的时候才会创建该对象)

@Bean     
@Lazy     
public Person person() {         
	return new Person();     
} 

@Conditional进行条件判断等.

场景,有二个组件SilasAspect 和SilasLog ,我的SilasLog组件是依赖于SilasAspect的组件
应用:自己创建一个SilasCondition的类 实现Condition接口

public class SilasCondition implements Condition { 
    /** 
      * @param context     
      * @param metadata     
      * @return      
     */     
     @Override     
     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {         
     	//判断容器中是否有silasAspect的组件         
     	if(context.getBeanFactory().containsBean("silasAspect")) {             
     		return true;         
     	}         
     return false;     
     } 
 } 
 
 public class MainConfig { 
    @Bean     
    public SilasAspect silasAspect() {         
    	return new SilasAspect();     
    }          
    //当容器中有silasAspect的组件,那么silasLog才会被实例化.     
    @Bean     
    @Conditional(value = SilasCondition.class)     
    public SilasLog silasLog() {         
    	return new SilasLog();     
    } 
}

往IOC 容器中添加组件的方式

  • ①:通过@CompentScan +@Controller @Service @Respository @compent
    适用场景: 针对我们自己写的组件可以通过该方式来进行加载到容器中。

  • ②:通过@Bean的方式来导入组件(适用于导入第三方组件的类)

  • ③:通过@Import来导入组件 (导入组件的id为全类名路径)

@Configuration 
@Import(value = {Person.class, Car.class}) 
public class MainConfig { }

通过@Import 的ImportSeletor类实现组件的导入 (导入组件的id为全类名路径)

public class SilasImportSelector implements ImportSelector {     
	//可以获取导入类的注解信息     
	@Override     
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {         
		return new String[]{"com.silas.testimport.compent.Dog"};     
	} 
} 
@Configuration 
@Import(value = {Person.class, Car.class, SilasImportSelector.class}) 
public class MainConfig { }

通过@Import的 ImportBeanDefinitionRegister导入组件 (可以指定bean的名称)

public class SilaseanDefinitionRegister implements ImportBeanDefinitionRegistrar {     
	@Override     
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {         
		//创建一个bean定义对象         
		RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Cat.class);         
		//把bean定义对象导入到容器中         
		registry.registerBeanDefinition("cat",rootBeanDefinition);     
	} 
} 
@Configuration 
//@Import(value = {Person.class, Car.class}) 
//@Import(value = {Person.class, Car.class, SilasmportSelector.class}) 
@Import(value = {Person.class, Car.class, SilasImportSelector.class, SilasBeanDefinitionRegister.class}) 
public class MainConfig { }
  • ④:通过实现FacotryBean接口来实现组件注册
public class CarFactoryBean implements FactoryBean<Car> {          
	//返回bean的对象     
	@Override     
	public Car getObject() throws Exception {         
		return new Car();     
	}          
	//返回bean的类型     
	@Override     
	public Class<?> getObjectType() {         
		return Car.class;     
	}          
	//是否为单利     
	@Override     
	public boolean isSingleton() {         
		return true;     
	} 
}

Bean的初始化方法和销毁方法

  • ①:什么是bean的生命周期?
    bean的创建----->初始化----->销毁方法
    由容器管理Bean的生命周期,我们可以通过自己指定bean的初始化方法和bean的销毁方法。
 @Configuration public class MainConfig {          
 	//指定了bean的生命周期的初始化方法和销毁方法. 
    @Bean(initMethod = "init",destroyMethod = "destroy")     
    public Car car() {         
    	return new Car();     
    } 
 }

针对单实例bean的话,容器启动的时候,bean的对象就创建了,而且容器销毁的时候,也会调用Bean的销毁方法。
针对多实例bean的话,容器启动的时候,bean是不会被创建的而是在获取bean的时候被创建,而且bean的销毁不受 IOC容器的管理。

  • ②:通过 InitializingBean和DisposableBean 的二个接口实现bean的初始化以及销毁方法
@Component public class Person implements InitializingBean,DisposableBean { 
    public Person() {         
    	System.out.println("Person的构造方法");     
    } 
    @Override     
    public void destroy() throws Exception {         
    	System.out.println("DisposableBean的destroy()方法 ");     
    } 
    @Override     
    public void afterPropertiesSet() throws Exception {         
    	System.out.println("InitializingBean的 afterPropertiesSet方法");     
    } 
}
  • ③:通过JSR250规范 提供的注解@PostConstruct 和@ProDestory标注的方法
@Component public class Book { 
    public Book() {         
    	System.out.println("book 的构造方法");     
    } 
    @PostConstruct     
    public void init() {         
    	System.out.println("book 的PostConstruct标志的方法");     
    } 
    @PreDestroy     public void destory() {         
    	System.out.println("book 的PreDestory标注的方法");     
    } 
}
  • ④:通过Spring的BeanPostProcessor的 bean的后置处理器会拦截所有bean创建过程
    postProcessBeforeInitialization 在init方法之前调用
    postProcessAfterInitialization 在init方法之后调用
@Component public class SilasBeanPostProcessor implements BeanPostProcessor { 
    @Override     
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {         
	System.out.println("SilasBeanPostProcessor...postProcessBeforeInitialization:"+beanName);         
		return bean;     
	} 
    @Override     
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 
		System.out.println("SilasBeanPostProcessor...postProcessAfterInitialization:"+beanName);         
    	return bean;     
    } }

BeanPostProcessor的执行时机

populateBean(beanName, mbd, instanceWrapper) 
initializeBean{     
	applyBeanPostProcessorsBeforeInitialization()     
	invokeInitMethods{         
		isInitializingBean.afterPropertiesSet         
		自定义的init方法     
	}     
	applyBeanPostProcessorsAfterInitialization()方法 
}

通过@Value + @PropertySource来给组件赋值

public class Person {          
	//通过普通的方式     
	@Value("云雨")     
	private String firstName;          
	//spel方式来赋值     
	@Value("#{28-8}")     
	private Integer age;     
	//通过读取外部配置文件的值     
	@Value("${person.lastName}")     
	private String lastName;      
}     
@Configuration 
@PropertySource(value = {"classpath:person.properties"}) //指定外部文件的位置 public class 
MainConfig { 
    @Bean     
    public Person person() {         
    	return new Person();     
    } 

自动装配

@AutoWired的使用
自动注入:

//一个Dao 
@Repository 
public class SilasDao { 
} 

@Service 
public class SilasService { 
    @Autowired     
    private SilasDao silasDao;

结论:

  • a:自动装配首先时按照类型进行装配,若在IOC容器中发现了多个相同类型的组件,那么就按照 属性名称来进行装配
    @Autowired private
    SilasDao silasDao;
    比如,我容器中有两个SilasDao类型的组件 一个叫silasDao 一个叫silasDao2
    那么我们通过@AutoWired 来修饰的属性名称时silasDao,那么拿就加载容器的silasDao组件,若属性名称为 silasDao2 那么他就加载的时silasDao2组件
  • b:假设我们需要指定特定的组件来进行装配,我们可以通过使用@Qualifier(“tulingDao”)来指定装配的组件
    或者在配置类上的@Bean加上@Primary注解
  @Autowired     
  @Qualifier("silasDao")     
  private SilasDao silasDao2;
  • c:假设我们容器中没有silasDao 和silasao2,那么在装配的时候就会抛出异常
    No qualifying bean of type ‘com.tuling.testautowired.TulingDao’ available
    若我们想不抛异常 ,我们需要指定 required为false的时候可以了
    @Autowired(required = false)     
    @Qualifier("silasDao")     
    private SilasDao silasDao2; 
  • d: @Resource(JSR250规范)
    功能和@AutoWired的功能差不多一样,但是不支持@Primary 和@Qualifier的支持
  • e: @InJect(JSR330规范)
    需要导入jar包依赖
    功能和支持@Primary功能 ,但是没有Require=false的功能
        <dependency>             
        	<groupId>javax.inject</groupId>             
        	<artifactId>javax.inject</artifactId>             
        	<version>1</version>         
        </dependency>
  • f:使用autowired 可以标注在方法上
    标注在set方法上
    @Autowired     
    public void setSilasLog(SilasLog silasLog) {         
    	this.silasLog = silasLog;     
    }

标注在构造方法上

    @Autowired     
    public SilasAspect(SilasLog silasLog) {         
    	this.silasLog = silasLog;     
    }

标注在配置类上的入参中(可以不写)

    @Bean     
    public SilasAspect silasAspect(@Autowired SilasLog silasLog) {         
    	SilasAspect silasAspect = new SilasAspect(silasLog);         
    	return silasAspect;     
    }

XXXAware接口

我们自己的组件 需要使用spring ioc的底层组件的时候,比如 ApplicationContext等
我们可以通过实现XXXAware接口来实现。

@Component 
public class SilasCompent implements ApplicationContextAware,BeanNameAware { 
    private ApplicationContext applicationContext; 
    @Override     
    public void setBeanName(String name) {         
    	System.out.println("current bean name is :【"+name+"】");     
    } 
    @Override     
    public void setApplicationContext(ApplicationContext  applicationContext) throws BeansException {         
    this.applicationContext = applicationContext;     
    } 
}

profile环境

通过@Profile注解 来根据环境来激活标识不同的Bean
@Profile标识在类上,那么只有当前环境匹配,整个配置类才会生效
@Profile标识在Bean上 ,那么只有当前环境的Bean才会被激活
没有标志为@Profile的bean 不管在什么环境都可以被激活

@Configuration @PropertySource(value = {"classpath:ds.properties"}) 
public class MainConfig implements EmbeddedValueResolverAware { 
    @Value("${ds.username}")     
    private String userName; 
    @Value("${ds.password}")     
    private String password; 
    private String jdbcUrl; 
    private String classDriver; 
    @Override     
    public void setEmbeddedValueResolver(StringValueResolver resolver) {         
    	this.jdbcUrl = resolver.resolveStringValue("${ds.jdbcUrl}");         
    	this.classDriver = 
    	resolver.resolveStringValue("${ds.classDriver}");     
    }          
    //标识为测试环境才会被装配     
    @Bean     
    @Profile(value = "test")     
    public DataSource testDs() {         
    	return buliderDataSource(new DruidDataSource());     
    }          
    //标识开发环境才会被激活     
    @Bean     
    @Profile(value = "dev")     
    public DataSource devDs() {         
    	return buliderDataSource(new DruidDataSource());     
    }          
    //标识生产环境才会被激活     
    @Bean     
    @Profile(value = "prod")     
    public DataSource prodDs() {         
    	return buliderDataSource(new DruidDataSource());     
    } 
    private DataSource buliderDataSource(DruidDataSource dataSource) {         
	    dataSource.setUsername(userName);         
	    dataSource.setPassword(password);         
	    dataSource.setDriverClassName(classDriver);         
	    dataSource.setUrl(jdbcUrl);         
	    return dataSource;     
	} 
} 

激活切换环境的方法
方法一:通过运行时jvm参数来切换 -Dspring.profiles.active=test|dev|prod
方法二:通过代码的方式来激活

  public static void main(String[] args) {          
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();         
        ctx.getEnvironment().setActiveProfiles("test","dev"); 
        ctx.register(MainConfig.class);         
        ctx.refresh();         
        printBeanName(ctx);     
  } 

相关学习路线

Spring源码分析之三

JAVA资深架构师成长路线->开源框架解读->Spring5源码解读

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值