常用组件对spring的应用

mybatis篇

mybatis3.5.3
mybatis-spring2.0.3

用过mybatis的同学应该对@MapperScan这个注解都不陌生吧,为什么启用了这个注解就能将mybatis集成到了spring中了呢。带着这个疑问我们先来看下这个注解里面有什么。

@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
    
  String[] basePackages() default {};

  Class<?>[] basePackageClasses() default {};
}

代码里面唯一能和spring扯上关系的应该就是 @Import(MapperScannerRegistrar.class) 这一行了吧。先来看下spring官方对Import这个注解是怎样解释的。

Provides functionality equivalent to the element in Spring XML. Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations

翻译过来就是:这个注解的功能类似于早期xml配置里的import标签,可以用来导入带@Configuration注解的配置类或者实现了ImportSelector,ImportBeanDefinitionRegistrar 接口的实现类。

再来看下 MapperScannerRegistrar类里的核心逻辑

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes mapperScanAttrs = AnnotationAttributes
            .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        if (mapperScanAttrs != null) {
            registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
        }
    }
    void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
        if (!Annotation.class.equals(annotationClass)) {
            builder.addPropertyValue("annotationClass", annotationClass);
        }

        Class<?> markerInterface = annoAttrs.getClass("markerInterface");
        if (!Class.class.equals(markerInterface)) {
            builder.addPropertyValue("markerInterface", markerInterface);
        }
        ....
        
        builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    }
}

整个类的逻辑总结出来就是:根据@MapperScan的属性生成一个MapperScannerConfigurer 类型的BeanDefinition注册到spring中,此时只是生成一个BeanDefinition还没被spring实例化。再来看下MapperScannerConfigurer 的核心逻辑:

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
            if (this.processPropertyPlaceHolders) {
                processPropertyPlaceHolders();
            }
            ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
            scanner.setAddToConfig(this.addToConfig);
            scanner.setAnnotationClass(this.annotationClass);
            scanner.setSqlSessionFactory(this.sqlSessionFactory);
            scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
            ...
            scanner.registerFilters();
            scanner.scan(
                StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
        }
    }

MapperScannerConfigurer 实现了spring的一个生命周期接口BeanDefinitionRegistryPostProcessor,这个接口很重要是spring的一等公民,它是BeanPostProcessor的子接口,只要是集成spring基本都绕不过它。mybatis就是在这里扫描Mapper接口然后注入到spring中。在这里定义了一个 ClassPathMapperScanner,是ClassPathBeanDefinitionScanner的一个子类。我们先来展开了解ClassPathBeanDefinitionScanner,从这个类的名字就能看出来这个是用来扫描类路径下的bean的,它根据过滤规则扫描类路径下所有的jar包来找出候选者,我们常用 @Component, @Repository, @Service, @Controller 注解的类spring内部也是通过这个类扫描出来的。我们再回到ClassPathMapperScanner来看下它是怎么实现的。

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {


    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        processBeanDefinitions(beanDefinitions);
        return beanDefinitions;
    }

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            ...
            //mapperFactoryBeanClass为MapperFactoryBean
            definition.setBeanClass(this.mapperFactoryBeanClass);
            ...
        }
    }
    public void registerFilters() {
        boolean acceptAllInterfaces = true;
        if (this.annotationClass != null) {
            //扫描带有@Mapper的类
            addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
            acceptAllInterfaces = false;
        }
        ...
    }
}

这个类在MapperScannerConfigurer实例化后调用了registerFilters(),就是在这里指定了扫描的过滤规则,过滤出了只带有@Mapper的类,然后用父类扫描出来BeanDefinition,因为我们扫描出来的类都是接口,如果直接把BeanDefinition的类型设置为接口类型spring在后面是实例化不了的,所以这里设置的类型是一个工厂类也就是MapperFactoryBean,spring实例化时如果识别到这个类是FactoryBean时会调用工厂方法去生成代理类来实例化。到这里所有的Mapper接口都已经注册到spring中了。

最后总结下整个调用流程:

dubbo篇

dubbo2.7.0

参考mybatis的集成思路,先来看下@EnableDubbo的定义

@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
    
    @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

}

这回并没有看到熟悉的@import , 接着继续查看EnableDubbo上里的两个注解的定义

@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {

    boolean multiple() default false;

}
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
  String[] basePackages() default {};  
}

万变不离其宗,果然有DubboConfigConfigurationRegistrarDubboComponentScanRegistrar。先来看DubboComponentScanRegistrar的实现:

public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
        registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
        registerReferenceAnnotationBeanPostProcessor(registry);
    }
}

DubboComponentScanRegistrar 的作用主要是注册两个注解处理器ServiceAnnotationBeanPostProcessorReferenceAnnotationBeanPostProcessor。看到这两个类的名字估计能猜出来这两个类的主要作用了,没错就是用来扫描带有dubbo包里@Reference@Service注解的。先来看ServiceAnnotationBeanPostProcessor的扫描组件的代码:

DubboClassPathBeanDefinitionScanner scanner =
    new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
scanner.setBeanNameGenerator(beanNameGenerator);
//这里的Service是dubbo里的注解,
scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
for (String packageToScan : packagesToScan) {
    scanner.scan(packageToScan);
    Set<BeanDefinitionHolder> beanDefinitionHolders =
    findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
    for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
        registerServiceBean(beanDefinitionHolder, registry, scanner);
    }
}

这里注意的是,调用scan()方法时扫描出来的BeanDefinition会自动注册到spring中,这也就是为什么用spring的@Autowired 也能装配dubbo的@Service注解的bean的原因。虽然已经注册到了spring中,但是这些bean还要注册到注册中心暴露出来给消费者调用的,dubbo没有在原有的BeanDefinition进行标记这是个DubboService,而是根据原有的BeanDefinition再次注册类型为ServiceBeanBeanDefinition
再看下ServiceBeanDefinition的代码逻辑

BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
...
List<RuntimeBeanReference> registryRuntimeBeanReferences = toRuntimeBeanReferences(registryConfigBeanNames);
//配置为@service注解指定的注册中心
if (!registryRuntimeBeanReferences.isEmpty()) {
    builder.addPropertyValue("registries", registryRuntimeBeanReferences);
}
String[] protocolConfigBeanNames = service.protocol();

List<RuntimeBeanReference> protocolRuntimeBeanReferences = toRuntimeBeanReferences(protocolConfigBeanNames);
//配置为@service注解指定的dubbo协议
if (!protocolRuntimeBeanReferences.isEmpty()) {
    builder.addPropertyValue("protocols", protocolRuntimeBeanReferences);
}

到这里所有的DubboService已经定义好了,那这些service是什么时候注册到注册中心的呢。接着来看ServiceBean的定义:

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
    ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
    ApplicationEventPublisherAware {
    
    public void afterPropertiesSet() throws Exception {
        ...
        //前面一大堆set注册中心配置,协议配置,监控配置和元数据配置实例的逻辑
        export();
    }
}

可以看到ServiceBean实现了多个spring的生命周期接口,其中InitializingBean接口会在bean初始换完成调用,dubbo就是在这个阶段调用export()方法暴露到注册中心,export()方法就不展开说了,感兴趣的同学可以自行去看源码。
到这里DubboService已经注册完成了,接下来我们看看消费端是怎样使用的。在spring中要使用一个bean常常会用到一个注解那就是@Autowired,那为什么dubbo为什么不用spring的这个注解非要搞一个@Reference出来呢,那是因为dubbo识别不出来用@Autowired注解的字段是bean引用还是一个dubbo引用,那dubbo的引用注入逻辑又是怎样的呢。我们先来了解spring的一个接口:InstantiationAwareBeanPostProcessor,翻译过来就是实例化感知后置处理器。看一下它的定义:

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {

	//bean在初始化前调用该方法
	@Nullable
	default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		return null;
	}

    //bean在初始化之后调用该方法
	default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
		return true;
	}

	//bean在实例化的时候调用该方法,pvs就是属性的元信息。
	@Nullable
	default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
			throws BeansException {

		return null;
	}

}

基于这个接口dubbo封装了一个 AnnotationInjectedBeanPostProcessor,之前我们说的ReferenceAnnotationBeanPostProcessor就是它的一个子类。先来看下dubbo是怎样封装的:

public abstract class AnnotationInjectedBeanPostProcessor<A extends Annotation> extends
    InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered,
    BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware, DisposableBean{

    @Override
    public PropertyValues postProcessPropertyValues(
        PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
        //找出带有@Reference注解的字段或者方法。
        InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
        try {
            //注入带有@Reference的字段或方法,
            //metadata里有AnnotatedFieldElement和AnnotatedMethodElement
            metadata.inject(bean, beanName, pvs);
        } catch (BeanCreationException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getName()
                                            + " dependencies is failed", ex);
        }
        return pvs;
    }

      protected Object getInjectedObject(A annotation, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {

        String cacheKey = buildInjectedObjectCacheKey(annotation, bean, beanName, injectedType, injectedElement);

        Object injectedObject = injectedObjectsCache.get(cacheKey);

        if (injectedObject == null) {
            //调用子类来获取注入的实例
            injectedObject = doGetInjectedBean(annotation, bean, beanName, injectedType, injectedElement);
            // Customized inject-object if necessary
            injectedObjectsCache.putIfAbsent(cacheKey, injectedObject);
        }

        return injectedObject;

    }

    // 这个类是dubbo封装的内部类,抽象出了当前带@Reference字段的所有信息
    public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement {

        private final Field field;

        private final A annotation;

        private volatile Object bean;

        protected AnnotatedFieldElement(Field field, A annotation) {
            super(field, null);
            this.field = field;
            this.annotation = annotation;
        }

        @Override
        protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {

            Class<?> injectedType = field.getType();
            //获取被注入的对象
            Object injectedObject = getInjectedObject(annotation, bean, beanName, injectedType, this);
            ReflectionUtils.makeAccessible(field);
            field.set(bean, injectedObject);

        }

    }

}

总结下这段代码的流程:先获取需要注入的元数据InjectionMetadata对象,里面包含了被@Reference注解的字段和方法,也就是InjectedElement,然后对每个InjectedElement调用inject()方法,要注入字段前提要先拿到注入的值,也就是调用了getInjectedObject()方法获取了被注入的值,这个方法实际调用了抽象方法doGetInjectedBean(),看下具体实现:

    protected Object doGetInjectedBean(Reference reference, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {

        String referencedBeanName = buildReferencedBeanName(reference, injectedType);

        ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referencedBeanName, reference, injectedType, getClassLoader());

        cacheInjectedReferenceBean(referenceBean, injectedElement);

        Object proxy = buildProxy(referencedBeanName, referenceBean, injectedType);

        return proxy;
    }

代码的核心逻辑就是构造一个代理对象返回给spring注入到带@Reference注解的字段中。

总结下:spring会在bean实例化时会调用生命周期接口InstantiationAwareBeanPostProcessor来解决依赖,dubbo实时生成代理对象注入到字段当中。

来看下InstantiationAwareBeanPostProcessor这个接口有多重要:

实现类作用
AutowiredAnnotationBeanPostProcessor注入@Autowired @Value所在的字段
ReferenceAnnotationBeanPostProcessor注入@Reference所在的字段
NacosValueAnnotationBeanPostProcessor注入@NacosValue所在的字段
SpyPostProcessor注入@SpyBean所在的字段
XxlRpcSpringInvokerFactory注入@XxlRpcReference所在的字段
RequiredAnnotationBeanPostProcessor注入@Required所在的字段

spring boot篇

springboot2.4.0

不知道大家有没有疑问,为什么用了@SpringBootApplication这个注解后,类路径下spring boot标准的starter配置的bean都会被注入到spring容器中?我们先来看下spring boot官方是怎样加载一个starter的:

Spring Boot checks for the presence of a META-INF/spring.factories file within your published jar. The file should list your configuration classes under the EnableAutoConfiguration key, as shown in the following example:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 
    com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\ 
    com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration

翻译过来就是:spring boot会加载所有jar包META-INF文件夹下的spring.factories文件,并且所有的自动配置类要定义在org.springframework.boot.autoconfigure.EnableAutoConfiguration key下面。
了解了spring boot的spi机制后我们先来看下@SpringBootApplication的定义:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    
}

根据以往的经验来看,带有Enable前缀的往往都是一个比较重要的注解,我们先来看@EnableAutoConfiguration注解的定义:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    
}

在mybatis篇中我们知道@Import是可以用来导入实现了ImportSelectorImportBeanDefinitionRegistrar 接口的类。
ImportSelector也是spring很重要的一个接口,先来看下它的定义:

public interface ImportSelector {

    //根据注解的元数据获取需要导入的类,返回的是包含全类名的数组
	String[] selectImports(AnnotationMetadata importingClassMetadata);
}

但是查看的AutoConfigurationImportSelector源码发现它实现的是DeferredImportSelector

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    
}

DeferredImportSelectorImportSelector区别很大,从源码可以得知这个类是spring4.0版本加入的,而spring-boot第一个版本0.5.0.M1使用的spring版本正是4.0,可以看出来这个接口是专门为spring-boot打造的。
先来看的DeferredImportSelector定义

public interface DeferredImportSelector extends ImportSelector {

	/**
	 * @since 5.0
	 */
	@Nullable
	default Class<? extends Group> getImportGroup() {
		return null;
	}

	/**
	 * @since 5.0
	 */
	interface Group {
	     /**
		 * Process the {@link AnnotationMetadata} of the importing @{@link Configuration}
		 * class using the specified {@link DeferredImportSelector}.
		 */
		void process(AnnotationMetadata metadata, DeferredImportSelector selector);
	     /**
		 * An entry that holds the {@link AnnotationMetadata} of the importing
		 * {@link Configuration} class and the class name to import.
		 */
		Iterable<Entry> selectImports();
    }
}

根据注释可以看到实际处理配置类的方法是DeferredImportSelector内部类里的process()方法,先来看下在AutoConfigurationImportSelector的实现:

   public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    ...
    //调用下面的 getAutoConfigurationEntry  
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(annotationMetadata);
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
    }

	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
	    ...
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        //调用下面的getCandidateConfigurations
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		...
		return new AutoConfigurationEntry(configurations, exclusions);
	}

    //从spring.factories文件获取配置类
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		return configurations;
	}

通过调用栈可以看到最终是通过SpringFactoriesLoader.loadFactoryNames()来加载配置类的,加载的文件就是开头说的spring.factories文件。
虽然所有的配置类在这里已经被加载到,但是配置类往往是存在加载的先后顺序的。我们经常会在spring boot starter中看到@AutoConfigureAfter@AutoConfigureBeforer这两个注解,很明显这些注解是用来给我们控制加载顺序用的,我们接下来看看这些注解是在哪里起作用的。
我们刚才看的DeferredImportSelector内部类里的process()方法是用来加载配置类,剩下还有一个方法selectImports(),没错这个就是获取最终的配置类的,顺序也是在这里控制的,先来看它的实现:

public Iterable<Entry> selectImports() {
    Set<String> allExclusions = this.autoConfigurationEntries.stream()
        .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
    Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
        .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
        .collect(Collectors.toCollection(LinkedHashSet::new));
    //过滤掉排除的类
    processedConfigurations.removeAll(allExclusions);
    //排序
    return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
        .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
        .collect(Collectors.toList());
}

private List<String> sortAutoConfigurations(Set<String> configurations,
                                            AutoConfigurationMetadata autoConfigurationMetadata) {
    return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
        .getInPriorityOrder(configurations);
}

排序规则都在AutoConfigurationSorter这个类的getInPriorityOrder()里了,直接上代码:

	List<String> getInPriorityOrder(Collection<String> classNames) {
		AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
				this.autoConfigurationMetadata, classNames);
		List<String> orderedClassNames = new ArrayList<>(classNames);
		// 先按照字母排序
		Collections.sort(orderedClassNames);
		// 按照实现了Order接口的order值排序
		orderedClassNames.sort((o1, o2) -> {
			int i1 = classes.get(o1).getOrder();
			int i2 = classes.get(o2).getOrder();
			return Integer.compare(i1, i2);
		});
		// 最后根据@AutoConfigureBefore和@AutoConfigureAfter排序
		orderedClassNames = sortByAnnotation(classes, orderedClassNames);
		return orderedClassNames;
	}

具体的排序算法有兴趣的同学可以看AutoConfigurationSorter这个类的源码,此处不再展开叙述了。

总的来说,spring boot 只是提供了一种自动配置的能力,让我们免去配置bean的各种繁琐达到开箱即用的效果。但是随之而来的问题是单元测试变得更加麻烦,虽然spring boot提供了个@SpringBootTest用来启动测试容器,但是由于’自动配置’会导致容器里的bean越来越多,特别是遇到和网络挂钩的dubbo,测试案例执行时间一言难尽。面对这种情况,spring boot 官方也提供了更多的注解来避免加载所有的bean来进行测试,如下表:
点击查看完整列表

@JooqTest
@DataJdbcTest
@DataJpaTest
@DataMongoTest
@WebMvcTest

遗憾的是这些注解灵活性和适应性太差了,碰到“本土化项目”启动sping容器失败的概率很大。了解了spring boot自动配置的原理后我们就能解决这个问题了。我们先来看一个常用的spring boot测试案例:

@SpringBootTest(classes = Application.class)
@RunWith(SpringRunner.class)
public class SimpleServiceTest {
    @Autowired
    SimpleServie simpleService;

    @Test
    public void excuteTest() {
        Object result= simpleService.excute();
        Assert.assertEquals(result, "success");
    }


}

由于启动@SpringBootTest指定的类是我们spring boot容器的启动类(带有@SpringBootApplication注解的那个类),所以就和我们启动项目的流程差不多,所有的bean还是会加载进来。我们可以考虑改成这样:

@SpringBootTest(classes = SimpleServiceTest.class)
@RunWith(SpringRunner.class)
@Import(value = {NacosConfigAutoConfiguration.class,
        DruidDataSourceAutoConfigure.class,
        MybatisPlusAutoConfiguration.class})
@MapperScan("xxx.mapper")
@ComponentScan("xxx.xxx")    
public class SimpleServiceTest {
    @Autowired
    SimpleServie simpleService;

    @Test
    public void excuteTest() {
        Object result= simpleService.excute();
        Assert.assertEquals(result, "success");
    }

}

指定启动类为我们当前的测试类就不会加载所有的bean了,然后再通过@import按需加载我们的基础设施配置,例如NacosConfigAutoConfigurationDruidDataSourceAutoConfigure之类的,最后还要指定我们需要测试的bean的包为组件扫描路径。

总结

通过mybatis、dubbo和spring boot的使用注解,我们可以看出它们都是通过spring提供的@Import注解和sping进行交互的。期间我们也提到了几个spring的核心接口,这些接口具体是怎么工作的,后面的spring源码系列再和大家探讨。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值