SpringBoot 注解原理,自动装配原理,图文并茂,万字长文!

首先,先看SpringBoot的主配置类:

  1. @SpringBootApplication

  2. public class StartEurekaApplication

  3. {

  4.    public static void main(String[] args)

  5.    {

  6.        SpringApplication.run(StartEurekaApplication.class, args);

  7.    }

  8. }

点进@SpringBootApplication来看,发现@SpringBootApplication是一个组合注解。

  1. @Target(ElementType.TYPE)

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Inherited

  5. @SpringBootConfiguration

  6. @EnableAutoConfiguration

  7. @ComponentScan(excludeFilters = {

  8.      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),

  9.      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

  10. public @interface SpringBootApplication {

  11. }

我们先来看 @SpringBootConfiguration:

  1. @Target({ElementType.TYPE})

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Configuration

  5. public @interface SpringBootConfiguration {

  6. }

可以看到这个注解除了元注解以外,就只有一个@Configuration,那也就是说这个注解相当于@Configuration,所以这两个注解作用是一样的,它让我们能够去注册一些额外的Bean,并且导入一些额外的配置。那@Configuration还有一个作用就是把该类变成一个配置类,不需要额外的XML进行配置。所以@SpringBootConfiguration就相当于@Configuration。进入@Configuration,发现@Configuration核心是@Component,说明Spring的配置类也是Spring的一个组件。

  1. @Target({ElementType.TYPE})

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Component

  5. public @interface Configuration {

  6.    @AliasFor(

  7.        annotation = Component.class

  8.    )

  9.    String value() default '';

  10. }

继续来看下一个

@EnableAutoConfiguration

这个注解是开启自动配置的功能。

  1. @Target({ElementType.TYPE})

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Inherited

  5. @AutoConfigurationPackage

  6. @Import({AutoConfigurationImportSelector.class})

  7. public @interface EnableAutoConfiguration {

  8.    String ENABLED_OVERRIDE_PROPERTY = 'spring.boot.enableautoconfiguration';

  9.    Class<?>[] exclude() default {};

  10.    String[] excludeName() default {};

  11. }

可以看到它是由

@AutoConfigurationPackage,@Import(EnableAutoConfigurationImportSelector.class)

这两个而组成的,我们先说

@AutoConfigurationPackage

他是说:让包中的类以及子包中的类能够被自动扫描到spring容器中。

  1. @Target({ElementType.TYPE})

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Inherited

  5. @Import({Registrar.class})

  6. public @interface AutoConfigurationPackage {

  7. }

使用@Import来给Spring容器中导入一个组件 ,这里导入的是Registrar.class。来看下这个Registrar:

  1.    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

  2.        Registrar() {

  3.        }

  4.        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

  5.            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());

  6.        }

  7.        public Set<Object> determineImports(AnnotationMetadata metadata) {

  8.            return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));

  9.        }

  10.    }

就是通过以上这个方法获取扫描的包路径,可以debug查看具体的值:

那metadata是什么呢,可以看到是标注在@SpringBootApplication注解上的DemosbApplication,也就是我们的主配置类Application:

其实就是将主配置类(即@SpringBootApplication标注的类)的所在包及子包里面所有组件扫描加载到Spring容器。因此我们要把DemoApplication放在项目的最高级中(最外层目录)。

看看注解@Import(AutoConfigurationImportSelector.class),@Import注解就是给Spring容器中导入一些组件,这里传入了一个组件的选择器:AutoConfigurationImportSelector。

可以从图中看出AutoConfigurationImportSelector 继承了 DeferredImportSelector 继承了 ImportSelector,ImportSelector有一个方法为:selectImports。将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。

  1. public String[] selectImports(AnnotationMetadata annotationMetadata) {

  2.    if (!this.isEnabled(annotationMetadata)) {

  3.        return NO_IMPORTS;

  4.    } else {

  5.        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

  6.        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry =

  7. this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);

  8.        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());

  9.    }

  10. }

会给容器中导入非常多的自动配置类(xxxAutoConfiguration);就是给容器中导入这个场景需要的所有组件,并配置好这些组件。

有了自动配置类,免去了我们手动编写配置注入功能组件等的工作。那是如何获取到这些配置类的呢,看看下面这个方法:

  1. protected AutoConfigurationImportSelector.AutoConfigurationEntry

  2. getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {

  3.    if (!this.isEnabled(annotationMetadata)) {

  4.        return EMPTY_ENTRY;

  5.    } else {

  6.        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

  7.        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

  8.        configurations = this.removeDuplicates(configurations);

  9.        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);

  10.        this.checkExcludedClasses(configurations, exclusions);

  11.        configurations.removeAll(exclusions);

  12.        configurations = this.filter(configurations, autoConfigurationMetadata);

  13.        this.fireAutoConfigurationImportEvents(configurations, exclusions);

  14.        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);

  15.    }

  16. }

我们可以看到getCandidateConfigurations()这个方法,他的作用就是引入系统已经加载好的一些类,到底是那些类呢:

  1. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

  2.    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());

  3.    Assert.notEmpty(configurations,

  4. 'No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.');

  5.    return configurations;

  6. }

  1. public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {

  2.    String factoryClassName = factoryClass.getName();

  3.    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());

  4. }

会从META-INF/spring.factories中获取资源,然后通过Properties加载资源:

  1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {

  2.    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);

  3.    if (result != null) {

  4.        return result;

  5.    } else {

  6.        try {

  7.            Enumeration<URL> urls = classLoader !=

  8. null ? classLoader.getResources('META-INF/spring.factories') : ClassLoader.getSystemResources('META-INF/spring.factories');

  9.            LinkedMultiValueMap result = new LinkedMultiValueMap();

  10.            while(urls.hasMoreElements()) {

  11.                URL url = (URL)urls.nextElement();

  12.                UrlResource resource = new UrlResource(url);

  13.                Properties properties = PropertiesLoaderUtils.loadProperties(resource);

  14.                Iterator var6 = properties.entrySet().iterator();

  15.                while(var6.hasNext()) {

  16.                    Map.Entry<?, ?> entry = (Map.Entry)var6.next();

  17.                    String factoryClassName = ((String)entry.getKey()).trim();

  18.                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());

  19.                    int var10 = var9.length;

  20.                    for(int var11 = 0; var11 < var10; ++var11) {

  21.                        String factoryName = var9[var11];

  22.                        result.add(factoryClassName, factoryName.trim());

  23.                    }

  24.                }

  25.            }

  26.            cache.put(classLoader, result);

  27.            return result;

  28.        } catch (IOException var13) {

  29.            throw new IllegalArgumentException('Unable to load factories from location [META-INF/spring.factories]', var13);

  30.        }

  31.    }

  32. }

可以知道SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作。以前我们需要自己配置的东西,自动配置类都帮我们完成了。如下图可以发现Spring常见的一些类已经自动导入。

接下来看@ComponentScan注解,@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }),这个注解就是扫描包,然后放入spring容器。

  1. @ComponentScan(excludeFilters = {

  2. @Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}),

  3. @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})})

  4. public@interfaceSpringBootApplication{}

总结下@SpringbootApplication:就是说,他已经把很多东西准备好,具体是否使用取决于我们的程序或者说配置。

接下来继续看run方法:

  1. publicstaticvoid main(String[] args) {

  2.        SpringApplication.run(Application.class, args);

  3.    }

来看下在执行run方法到底有没有用到哪些自动配置的东西,我们点进run:

  1. publicConfigurableApplicationContext run(String... args) {

  2.    //计时器

  3.    StopWatch stopWatch = newStopWatch();

  4.    stopWatch.start();

  5.    ConfigurableApplicationContext context = null;

  6.    Collection<SpringBootExceptionReporter> exceptionReporters = newArrayList();

  7.    this.configureHeadlessProperty();

  8.    //监听器

  9.    SpringApplicationRunListeners listeners = this.getRunListeners(args);

  10.    listeners.starting();

  11.    Collection exceptionReporters;

  12.    try{

  13.        ApplicationArguments applicationArguments = newDefaultApplicationArguments(args);

  14.        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);

  15.        this.configureIgnoreBeanInfo(environment);

  16.        Banner printedBanner = this.printBanner(environment);

  17.        //准备上下文

  18.        context = this.createApplicationContext();

  19.        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,

  20. newClass[]{ConfigurableApplicationContext.class}, context);

  21.        //预刷新context

  22.        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

  23.        //刷新context

  24.        this.refreshContext(context);

  25.        //刷新之后的context

  26.        this.afterRefresh(context, applicationArguments);

  27.        stopWatch.stop();

  28.        if(this.logStartupInfo) {

  29.            (newStartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);

  30.        }

  31.        listeners.started(context);

  32.        this.callRunners(context, applicationArguments);

  33.    } catch(Throwable var10) {

  34.        this.handleRunFailure(context, var10, exceptionReporters, listeners);

  35.        thrownewIllegalStateException(var10);

  36.    }

  37.    try{

  38.        listeners.running(context);

  39.        return context;

  40.    } catch(Throwable var9) {

  41.        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);

  42.        thrownewIllegalStateException(var9);

  43.    }

  44. }

那我们关注的就是 refreshContext(context); 刷新context,我们点进来看。

  1. privatevoid refreshContext(ConfigurableApplicationContext context) {

  2.   refresh(context);

  3.   if(this.registerShutdownHook) {

  4.      try{

  5.         context.registerShutdownHook();

  6.      }

  7.      catch(AccessControlException ex) {

  8.         // Not allowed in some environments.

  9.      }

  10.   }

  11. }

我们继续点进refresh(context);

  1. protectedvoid refresh(ApplicationContext applicationContext) {

  2.   Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);

  3.   ((AbstractApplicationContext) applicationContext).refresh();

  4. }

他会调用 ((AbstractApplicationContext) applicationContext).refresh();方法,我们点进来看:

  1. publicvoid refresh() throwsBeansException, IllegalStateException{

  2.   synchronized(this.startupShutdownMonitor) {

  3.      // Prepare this context for refreshing.

  4.      prepareRefresh();

  5.      // Tell the subclass to refresh the internal bean factory.

  6.      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

  7.      // Prepare the bean factory for use in this context.

  8.      prepareBeanFactory(beanFactory);

  9.      try{

  10.         // Allows post-processing of the bean factory in context subclasses.

  11.         postProcessBeanFactory(beanFactory);

  12.         // Invoke factory processors registered as beans in the context.

  13.         invokeBeanFactoryPostProcessors(beanFactory);

  14.         // Register bean processors that intercept bean creation.

  15.         registerBeanPostProcessors(beanFactory);

  16.         // Initialize message source for this context.

  17.         initMessageSource();

  18.         // Initialize event multicaster for this context.

  19.         initApplicationEventMulticaster();

  20.         // Initialize other special beans in specific context subclasses.

  21.         onRefresh();

  22.         // Check for listener beans and register them.

  23.         registerListeners();

  24.         // Instantiate all remaining (non-lazy-init) singletons.

  25.         finishBeanFactoryInitialization(beanFactory);

  26.         // Last step: publish corresponding event.

  27.         finishRefresh();

  28.      }catch(BeansException ex) {

  29.         if(logger.isWarnEnabled()) {

  30.            logger.warn('Exception encountered during context initialization - '+

  31.                  'cancelling refresh attempt: '+ ex);

  32.         }

  33.         // Destroy already created singletons to avoid dangling resources.

  34.         destroyBeans();

  35.         // Reset 'active' flag.

  36.         cancelRefresh(ex);

  37.         // Propagate exception to caller.

  38.         throw ex;

  39.      }finally{

  40.         // Reset common introspection caches in Spring's core, since we

  41.         // might not ever need metadata for singleton beans anymore...

  42.         resetCommonCaches();

  43.      }

  44.   }

  45. }

由此可知,就是一个spring的bean的加载过程。继续来看一个方法叫做 onRefresh():

  1. protectedvoid onRefresh() throwsBeansException{

  2.   // For subclasses: do nothing by default.

  3. }

他在这里并没有直接实现,但是我们找他的具体实现:

 

比如Tomcat跟web有关,我们可以看到有个ServletWebServerApplicationContext:

  1. @Override

  2. protectedvoid onRefresh() {

  3.   super.onRefresh();

  4.   try{

  5.      createWebServer();

  6.   }

  7.   catch(Throwable ex) {

  8.      thrownewApplicationContextException('Unable to start web server', ex);

  9.   }

  10. }

可以看到有一个createWebServer();方法他是创建web容器的,而Tomcat不就是web容器,那是如何创建的呢,我们继续看:

  1. privatevoid createWebServer() {

  2.   WebServer webServer = this.webServer;

  3.   ServletContext servletContext = getServletContext();

  4.   if(webServer == null&& servletContext == null) {

  5.      ServletWebServerFactory factory = getWebServerFactory();

  6.      this.webServer = factory.getWebServer(getSelfInitializer());

  7.   }

  8.   elseif(servletContext != null) {

  9.      try{

  10.         getSelfInitializer().onStartup(servletContext);

  11.      }

  12.      catch(ServletException ex) {

  13.         thrownewApplicationContextException('Cannot initialize servlet context',

  14.               ex);

  15.      }

  16.   }

  17.   initPropertySources();

  18. }

factory.getWebServer(getSelfInitializer());他是通过工厂的方式创建的。

  1. publicinterfaceServletWebServerFactory{

  2.   WebServer getWebServer(ServletContextInitializer... initializers);

  3. }

可以看到 它是一个接口,为什么会是接口。因为我们不止是Tomcat一种web容器。

我们看到还有Jetty,那我们来看TomcatServletWebServerFactory:

  1. @Override

  2. publicWebServer getWebServer(ServletContextInitializer... initializers) {

  3.   Tomcat tomcat = newTomcat();

  4.   File baseDir = (this.baseDirectory != null) ? this.baseDirectory

  5.         : createTempDir('tomcat');

  6.   tomcat.setBaseDir(baseDir.getAbsolutePath());

  7.   Connector connector = newConnector(this.protocol);

  8.   tomcat.getService().addConnector(connector);

  9.   customizeConnector(connector);

  10.   tomcat.setConnector(connector);

  11.   tomcat.getHost().setAutoDeploy(false);

  12.   configureEngine(tomcat.getEngine());

  13.   for(Connector additionalConnector : this.additionalTomcatConnectors) {

  14.      tomcat.getService().addConnector(additionalConnector);

  15.   }

  16.   prepareContext(tomcat.getHost(), initializers);

  17.   return getTomcatWebServer(tomcat);

  18. }

那这块代码,就是我们要寻找的内置Tomcat,在这个过程当中,我们可以看到创建Tomcat的一个流程。

如果不明白的话, 我们在用另一种方式来理解下,大家要应该都知道stater举点例子。

  1. <dependency>

  2.    <groupId>org.springframework.boot</groupId>

  3.    <artifactId>spring-boot-starter-data-redis</artifactId>

  4. </dependency>

  5. <dependency>

  6.    <groupId>org.springframework.boot</groupId>

  7.    <artifactId>spring-boot-starter-freemarker</artifactId>

  8. </dependency>

首先自定义一个stater。

  1. <parent>

  2.    <groupId>org.springframework.boot</groupId>

  3.    <artifactId>spring-boot-starter-parent</artifactId>

  4.    <version>2.1.4.RELEASE</version>

  5.    <relativePath/>

  6. </parent>

  7. <groupId>com.zgw</groupId>

  8. <artifactId>gw-spring-boot-starter</artifactId>

  9. <version>1.0-SNAPSHOT</version>

  10. <dependencies>

  11.    <dependency>

  12.        <groupId>org.springframework.boot</groupId>

  13.        <artifactId>spring-boot-autoconfigure</artifactId>

  14.    </dependency>

  15. </dependencies>

我们先来看maven配置写入版本号,如果自定义一个stater的话必须依赖spring-boot-autoconfigure这个包,我们先看下项目目录。

  1. publicclassGwServiceImpl  implementsGwService{

  2.    @Autowired

  3.    GwProperties properties;

  4.    @Override

  5.    publicvoidHello()

  6.    {

  7.        String name=properties.getName();

  8.        System.out.println(name+'说:你们好啊');

  9.    }

  10. }

我们做的就是通过配置文件来定制name这个是具体实现。

  1. @Component

  2. @ConfigurationProperties(prefix = 'spring.gwname')

  3. publicclassGwProperties{

  4.    String name='zgw';

  5.    publicString getName() {

  6.        return name;

  7.    }

  8.    publicvoid setName(String name) {

  9.        this.name = name;

  10.    }

  11. }

这个类可以通过@ConfigurationProperties读取配置文件。

  1. @Configuration

  2. @ConditionalOnClass(GwService.class)  //扫描类

  3. @EnableConfigurationProperties(GwProperties.class) //让配置类生效

  4. publicclassGwAutoConfiguration{

  5.    /**

  6.    * 功能描述 托管给spring

  7.    * @author zgw

  8.    * @return

  9.    */

  10.    @Bean

  11.    @ConditionalOnMissingBean

  12.    publicGwService gwService()

  13.    {

  14.        returnnewGwServiceImpl();

  15.    }

  16. }

这个为配置类,为什么这么写因为,spring-boot的stater都是这么写的,我们可以参照他仿写stater,以达到自动配置的目的,然后我们在通过spring.factories也来进行配置。

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gw.GwAutoConfiguration

然后这样一个简单的stater就完成了,然后可以进行maven的打包,在其他项目引入就可以使用。

作者:stackOverFlow stackOverFlow

(0)

相关推荐

  • 使用Java方式配置Spring

    我们之前使用的Spring的XMl配置创建管理Bean,但Spring支持Java方式实现这个功能! JavaConfig原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean ...

  • Spring-IOC进阶注解

    我们之后可能用SpringBoot创建项目,但是里面有些注解其实是SpringFramework的,简单讲几个 @Configuration 此注解可以替代配置文件,就是那个Spring的xml文件配 ...

  • Solr和Spring Data Solr

    一.Solr概述与安装 1.Solr简介 Solr是一个开源搜索平台,用于构建搜索应用程序. 它建立在Lucene(全文搜索引擎)之上. Solr是企业级的,快速的和高度可扩展的.Solr可以和Had ...

  • SpringBoot启动流程总结

    一直很好奇SpringBoot这么一个大怪物,启动的时候做了哪些事情,然后看了很多老师讲的教学视频,然后自己想好好整理一下,做下学习笔记下次也方便自己阅读 1.执行main方法 public stat ...

  • spring中那些让你爱不释手的代码技巧(续集)

    作者 |因为热爱所以坚持ing 出品 |苏三说技术 大家好,我是苏三,又和大家见面了. 最近听说井上雄彦已经官宣:<灌篮高手>要出电影版了,这条消息让我激动不已. 篮球,青春,热血,爱情, ...

  • .NET Core 集成JWT认证

    JWT(Json web token)就不用过多的介绍了,在 .NET Core 开发中使用JWT进行认证也是比较常见的,而且接入过程也比较简单,随便配置配置就好了. 要想使用JWT,仅仅只需要在项目 ...

  • 淘宝一面:“讲述一下 SpringBoot 自动装配原理?”

    每次问到 Spring Boot, 面试官非常喜欢问这个问题:"讲述一下 SpringBoot 自动装配原理?". 我觉得我们可以从以下几个方面回答: 什么是 SpringBoot ...

  • SpringBoot面试杀手锏——自动配置原理

    引言 不论在工作中,亦或是求职面试,Spring Boot已经成为我们必知必会的技能项.除了某些老旧的政府项目或金融项目持有观望态度外,如今的各行各业都在飞速的拥抱这个已经不是很新的Spring启动框 ...

  • SpringBoot自动配置原理

    我们知道,SpringBoot项目创建完成后,即使不进行任何的配置,也能够顺利地运行,这都要归功于SpringBoot的自动化配置. SpringBoot默认使用application.propert ...

  • MVR蒸发器机构原理及特点(图文并茂)

    一.MVR工艺介绍 1.MVR原理 MVR是蒸汽机械再压缩技术,(mechanical vapor recompression )的简称.MVR蒸发器是重新利用它自身产生的二次蒸汽的能量,从而减少对外 ...

  • SpringBoot 好“吃”的启动原理

    原创:西狩 编写日期 / 修订日期:2020-12-30 / 2020-12-30 版权声明:本文为博主原创文章,遵循 CC BY-SA-4.0 版权协议,转载请附上原文出处链接和本声明. 不正经的前 ...

  • 万字长文丰富图文详解B-树原理

    本篇原计划在上周五发布,由于太过硬核所以才拖到了这周五.我相信大家应该能从标题当中体会到这个硬核. 周五的专题是大数据和分布式,我最初的打算是和大家分享一下LSM树在分布式存储引擎当中的应用.但是想要 ...

  • SpringMVC:注解@ControllerAdvice的工作原理

    不装逼的程序员 今天 Spring MVC中,通过组合使用注解@ControllerAdvice和其他一些注解,我们可以为开发人员实现的控制器类做一些全局性的定制,具体来讲,可作如下定制 : 结合@E ...

  • 五分钟说清楚 Spring Boot的自动配置原理

    前言 Spring Boot没有火起来之前,使用SSM架构的项目那是相当的多,现在也有不少项目还是使用这种架构.在使用SSM架构的时候,大家是否还记得大量配置的烦恼郁闷,各种配置,搞得人都不是很爽.各 ...

  • 万字长文 | 短波通信原理,这可能是讲短波讲的最透彻的一篇文章!

    尽管当前新型无线电通信系统不断涌现,短波这一古老和传统的通信方式仍然受到全世界普遍重视,不仅没有被淘太,还在快速发展.其原因主要有三: 一.短波是唯一不受网络枢钮和有源中继体制约的远程通信手段 一但发 ...