1.spring组件与注册的概念
理解这两个概念的前提是得了解spring的控制反转
,依赖注入
,IOC容器
这三个概念。
- spring的组件其实就是ioc容器管理的每个bean对象
- 组件注册其实就是告诉spring这个bean 是否要给ioc容器托管。
2.spring中常用的五中组件注册方法
2.1 使用@Bean注解
这种方式是见得最多的一种方式,该注解作用在方法
和注解
定义上。但这限于我们自定义的bean组件
如下:
public class MyConfing {
/*
* 给ioc容器中注入一个Blue的组件,
* 如果不设置value的话默认是采用方法名 blue为组件名
*/
@Bean(initMethod = "init",destroyMethod = "destroy")
public Blue blue(){
return new Blue();
}
}
2.2 使用@ComponentScan注解
这个注解是一个组件扫描注解,通过扫描某些包下的组件批量的注册。该注解有几个比较重要的属性
- basePackages/value 表示扫描的路径是一个String[]
- includeFilters 包含过滤器Filter[] ,指定扫描符合条件的组件
- excludeFilters 排除过滤器Filter[] ,不扫描不符合条件的组件
- useDefaultFilters 默认的扫描策略默认为true,如果想要用自定义的策略 该值要设为false
其中Filter[] 的Filter是ComponentScan内部的注解
@interface Filter {
/**
* The type of filter to use.
* <p>Default is {@link FilterType#ANNOTATION}.
* @see #classes
* @see #pattern
*/
FilterType type() default FilterType.ANNOTATION;
/**
* Alias for {@link #classes}.
* @see #classes
*/
@AliasFor("classes")
Class<?>[] value() default {};
/**
* The class or classes to use as the filter.
* <p>The following table explains how the classes will be interpreted
* based on the configured value of the {@link #type} attribute.
* <table border="1">
* <tr><th>{@code FilterType}</th><th>Class Interpreted As</th></tr>
* <tr><td>{@link FilterType#ANNOTATION ANNOTATION}</td>
* <td>the annotation itself</td></tr>
* <tr><td>{@link FilterType#ASSIGNABLE_TYPE ASSIGNABLE_TYPE}</td>
* <td>the type that detected components should be assignable to</td></tr>
* <tr><td>{@link FilterType#CUSTOM CUSTOM}</td>
* <td>an implementation of {@link TypeFilter}</td></tr>
* </table>
* <p>When multiple classes are specified, <em>OR</em> logic is applied
* — for example, "include types annotated with {@code @Foo} OR {@code @Bar}".
* <p>Custom {@link TypeFilter TypeFilters} may optionally implement any of the
* following {@link org.springframework.beans.factory.Aware Aware} interfaces, and
* their respective methods will be called prior to {@link TypeFilter#match match}:
* <ul>
* <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
* </ul>
* <p>Specifying zero classes is permitted but will have no effect on component
* scanning.
* @since 4.2
* @see #value
* @see #type
*/
@AliasFor("value")
Class<?>[] classes() default {};
/**
* The pattern (or patterns) to use for the filter, as an alternative
* to specifying a Class {@link #value}.
* <p>If {@link #type} is set to {@link FilterType#ASPECTJ ASPECTJ},
* this is an AspectJ type pattern expression. If {@link #type} is
* set to {@link FilterType#REGEX REGEX}, this is a regex pattern
* for the fully-qualified class names to match.
* @see #type
* @see #classes
*/
String[] pattern() default {};
}
从源码中可以看出FilterType是一个enum,其值可以为以下几种,如要是Custom 的话 value里面的Class 要是TypeFilter
的实现类。
public enum FilterType {
/**
* Filter candidates marked with a given annotation.
* @see org.springframework.core.type.filter.AnnotationTypeFilter
*/
ANNOTATION,
/**
* Filter candidates assignable to a given type.
* @see org.springframework.core.type.filter.AssignableTypeFilter
*/
ASSIGNABLE_TYPE,
/**
* Filter candidates matching a given AspectJ type pattern expression.
* @see org.springframework.core.type.filter.AspectJTypeFilter
*/
ASPECTJ,
/**
* Filter candidates matching a given regex pattern.
* @see org.springframework.core.type.filter.RegexPatternTypeFilter
*/
REGEX,
/** Filter candidates using a given custom
* {@link org.springframework.core.type.filter.TypeFilter} implementation.
*/
CUSTOM
}
我自定义一个扫描组件过滤器如下
@Component
public class MyFilter implements TypeFilter {
/**
*
* @param metadataReader 当前被扫描的类的相关信息
* @param metadataReaderFactory 工厂里其他类的信息
* @return
* @throws IOException
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//获取当前类的类型信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//如果当前类的类名含Red就不扫描
if (classMetadata.getClassName().contains("Red")) {
return true;
}
return false;
}
}
在代码中使用@ComponentScan。该注解的定义上有@Repeatable(ComponentScans.class)
表示如果想多个扫描器的话可以在一个类上重复使用@ComponentScan
或者是 @ComponenetScans({@ComponentScan})
@Configuration
@Import({Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@ComponentScans({@ComponentScan(value = {"com.javxuan.boot"},
includeFilters = {@ComponentScan.Filter(type=FilterType.ANNOTATION,value = {Controller.class})},
excludeFilters = {@ComponentScan.Filter(type =FilterType.CUSTOM,value = {MyFilter.class})},
useDefaultFilters = false)})
public class MyConfing {
2.3 使用@Import注解
@Import主要是用来引用第三方组件,当然也可以导入自定义的组件。这个注解分别可以导入以下注解
- 引入普通的组件
package com.javxuan.boot.entity;
/**
* @Description
* @Author xiaoqx <Javxuan@163.com>
* @Version V1.0.0
* @Since 1.0
* @Date 2018/5/20
*/
public class Red {
}
使用@Import注解
@Configuration
@Import({Red.class)//引入普通组件
public class MyConfing {
}
- 引入ImportSelect的实现类
Interface to be implemented by types that determine which @{@link Configuration} class(es) should be imported based on a given selection criteria, usually one or more
annotation attributes.
ImportSelect这个接口的实现类一般是用来引入配置文件的。
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
自定义实现类
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.javxuan.boot.entity.Yellow"};
}
}
使用@Import来引入
@Configuration
@Import({MyImportSelector.class})
public class MyConfing {
}
- 引入ImportBeandeinitionRegistrart
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.core.type.AnnotationMetadata;
/**
* Interface to be implemented by types that register additional bean definitions when
* processing @{@link Configuration} classes. Useful when operating at the bean definition
* level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
*
* <p>Along with {@code @Configuration} and {@link ImportSelector}, classes of this type
* may be provided to the @{@link Import} annotation (or may also be returned from an
* {@code ImportSelector}).
*
* <p>An {@link ImportBeanDefinitionRegistrar} may implement any of the following
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
* methods will be called prior to {@link #registerBeanDefinitions}:
* <ul>
* <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
* <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
* <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
* </ul>
*
* <p>See implementations and associated unit tests for usage examples.
*
* @author Chris Beams
* @since 3.1
* @see Import
* @see ImportSelector
* @see Configuration
*/
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
自定义ImportBeanDefinitionRegistrar
@Component
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Yellow.class);
beanDefinitionRegistry.registerBeanDefinition("yellow",rootBeanDefinition);
}
}
使用注解引入
@Configuration
@Import({MyImportBeanDefinitionRegistrar.class})
public class MyConfing {
}
2.4 使用@Conditional注解
这个是条件注解,只有满足一定的条件才注入组件。这个条件要实现Condition接口,这是一个功能注解接口,然后重写match方法,符合条件就注入。
/**
* A single {@code condition} that must be {@linkplain #matches matched} in order
* for a component to be registered.
*
* <p>Conditions are checked immediately before the bean-definition is due to be
* registered and are free to veto registration based on any criteria that can
* be determined at that point.
*
* <p>Conditions must follow the same restrictions as {@link BeanFactoryPostProcessor}
* and take care to never interact with bean instances. For more fine-grained control
* of conditions that interact with {@code @Configuration} beans consider the
* {@link ConfigurationCondition} interface.
*
* @author Phillip Webb
* @since 4.0
* @see ConfigurationCondition
* @see Conditional
* @see ConditionContext
*/
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
自定义我的Condition
package com.javxuan.boot.registry;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @Description
* @Author xiaoqx <Javxuan@163.com>
* @Version V1.0.0
* @Since 1.0
* @Date 2018/5/21
*/
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//获得项目相关环境
Environment environment = conditionContext.getEnvironment();
//获取bean的定义注册中心
BeanDefinitionRegistry registry = conditionContext.getRegistry();
//获取resource
ResourceLoader resourceLoader = conditionContext.getResourceLoader();
//如果容器中没有Boot bean 就注册否则不注册
if(registry.containsBeanDefinition("boot")){
return true;
}
return false;
}
}
在方法或者类上使用@Conditional注解,如果是在类上标注说明这个注解是全局的,在方法上就是局部的
@Conditional({MyCondition.class}) //可以在类与方法上进行注解
@Bean
public Boot boot(){
return new Boot();
}
2.5 使用FactroyBean
使用FactoryBean接口的实现类来给容器中注入组件,这一方法通常是springboot整合第三方组件用的,如Mybatis。
该接口里面的getObject()方法可以返回该工厂生产的Bean
T 代表该工厂要生产的bean
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
//设置该bean 是单例还是多实例
default boolean isSingleton() {
return true;
}
}
自定义一个WhiteFactoryBean
public class WhiteFactoryBean implements FactoryBean<White> {
@Override
public White getObject() throws Exception {
return new White();
}
@Override
public Class<?> getObjectType() {
return White.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
在配置类中使用FactoryBean来注册
/**
* 该方法一眼看过去是在ioc容器中注入WhiteFactoryBean 的bean
* 其实真正注册的是White 的bean,我可以通过 getBean("bean的id")
* 在有些情况下我们就是有获取WhiteFactoryBean的实例 那么我们通过getBean("&bean的id")
*
* @return
*/
@Bean
public WhiteFactoryBean whiteFactoryBean(){
return new WhiteFactoryBean();
}
如下我们用测试类分别获取
@Test
public void test(){
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MyConfing.class);
//获取White的bean
Object whiteFactoryBean = annotationConfigApplicationContext.getBean("whiteFactoryBean");
System.out.println(whiteFactoryBean.getClass());
//获取WhiteFactoryBean的bean
Object bean = annotationConfigApplicationContext.getBean("&whiteFactoryBean");
System.out.println(bean.getClass());
}
结果
整合mybatis
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
//利用sqlSessionFacotyBean来的SqlSessionFactory
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if(StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
factory.setConfiguration(this.properties.getConfiguration());
if(!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if(this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if(StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if(StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if(!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
return factory.getObject();
}
3.测试一下5中注册注解的效果
package com.javxuan.boot.configuration;
import com.javxuan.boot.entity.Blue;
import com.javxuan.boot.entity.Boot;
import com.javxuan.boot.entity.Red;
import com.javxuan.boot.registry.*;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
/**
* @Description
* @Author xiaoqx <Javxuan@163.com>
* @Version V1.0.0
* @Since 1.0
* @Date 2018/5/20
*/
@Configuration //代表该类是一个配置类
@Import({Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@ComponentScans({@ComponentScan(value = {"com.javxuan.boot"},
includeFilters = {@ComponentScan.Filter(type=FilterType.ANNOTATION,value = {Controller.class})},
excludeFilters = {@ComponentScan.Filter(type =FilterType.CUSTOM,value = {MyFilter.class})},
useDefaultFilters = false)})
public class MyConfing {
@Bean(initMethod = "init",destroyMethod = "destroy")
public Blue blue(){
return new Blue();
}
@Conditional({MyCondition.class}) //可以在类与方法上进行注解
@Bean
public Boot boot(){
return new Boot();
}
/**
* 该方法一眼看过去是在ioc容器中注入WhiteFactoryBean 的bean
* 其实真正注册的是White 的bean,我可以通过 getBean("bean的id")
* 在有些情况下我们就是有获取WhiteFactoryBean的实例 那么我们通过getBean("&bean的id")
*
* @return
*/
@Bean
public WhiteFactoryBean whiteFactoryBean(){
return new WhiteFactoryBean();
}
}