Spring Boot 自动配置原理分析

【Linux内核】Linux内核裁剪完全指南:从理论到实战的系统优化 在嵌入式设备、专用服务器和物联网系统日益普及的今天,如何让Linux系统在资源受限的环境中高效运行成为开发者必须面对的问题。Linux内核裁剪正是解决这一问题的关键技术,它通过移除不必要的内核组件,打造出更精简、更安全、更高效的自定义操作系统。本文将系统全面地介绍Linux内核裁剪的完整流程和实用技巧,帮助您掌握这一核心技能。Linux内核裁剪是一项结合技术知识与实践经验的系统工程。明确需求:裁剪前必须清晰了解目标系统的硬件配置和功能需求。循序渐进:采取小步修改、反复测试的策略,避免一次性大幅修改。 阅读详情

Spring Boot是Spring旗下众多的子项目之一,其理念是约定优于配置,它通过实现了自动配置(大多数用户平时习惯设置的配置作为默认配置)的功能来为用户快速构建出标准化的应用。Spring Boot的特点可以概述为如下几点:

  • 内置了嵌入式的Tomcat、Jetty等Servlet容器,应用可以不用打包成War格式,而是可以直接以Jar格式运行。
  • 提供了多个可选择的”starter”以简化Maven的依赖管理(也支持Gradle),让您可以按需加载需要的功能模块。
  • 尽可能地进行自动配置,减少了用户需要动手写的各种冗余配置项,Spring Boot提倡无XML配置文件的理念,使用Spring Boot生成的应用完全不会生成任何配置代码与XML配置文件。
  • 提供了一整套的对应用状态的监控与管理的功能模块(通过引入spring-boot-starter-actuator),包括应用的线程信息、内存信息、应用是否处于健康状态等,为了满足更多的资源监控需求,Spring Cloud中的很多模块还对其进行了扩展。

有关Spring Boot的使用方法就不做多介绍了,如有兴趣请自行阅读官方文档Spring Boot或其他文章。

如今微服务的概念愈来愈热,转型或尝试微服务的团队也在如日渐增,而对于技术选型,Spring Cloud是一个比较好的选择,它提供了一站式的分布式系统解决方案,包含了许多构建分布式系统与微服务需要用到的组件,例如服务治理、API网关、配置中心、消息总线以及容错管理等模块。可以说,Spring Cloud”全家桶”极其适合刚刚接触微服务的团队。似乎有点跑题了,不过说了这么多,我想要强调的是,Spring Cloud中的每个组件都是基于Spring Boot构建的,而理解了Spring Boot的自动配置的原理,显然也是有好处的。

Spring Boot的自动配置看起来神奇,其实原理非常简单,背后全依赖于@Conditional注解来实现的。

本文作者为SylvanasSun(sylvanas.sun@gmail.com),首发于SylvanasSun’s Blog。
原文链接:https://sylvanassun.github.io/2018/01/08/2018-01-08-spring_boot_auto_configure/
(转载请务必保留本段声明,并且保留超链接。)

什么是@Conditional?


@Conditional是由Spring 4提供的一个新特性,用于根据特定条件来控制Bean的创建行为。而在我们开发基于Spring的应用的时候,难免会需要根据条件来注册Bean。

例如,你想要根据不同的运行环境,来让Spring注册对应环境的数据源Bean,对于这种简单的情况,完全可以使用@Profile注解实现,就像下面代码所示:

@Configuration

public class AppConfig {

    @Bean

    @Profile("DEV")

    public DataSource devDataSource() {

        ...

    }

     

    @Bean

    @Profile("PROD")

    public DataSource prodDataSource() {

        ...

    }

}

剩下只需要设置对应的Profile属性即可,设置方法有如下三种:

  • 通过context.getEnvironment().setActiveProfiles("PROD")来设置Profile属性。
  • 通过设定jvm的spring.profiles.active参数来设置环境(Spring Boot中可以直接在application.properties配置文件中设置该属性)。
  • 通过在DispatcherServlet的初始参数中设置。

<servlet>

    <servlet-name>dispatcher</servlet-name>

    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

    <init-param>

        <param-name>spring.profiles.active</param-name>

        <param-value>PROD</param-value>

    </init-param>

</servlet>

但这种方法只局限于简单的情况,而且通过源码我们可以发现@Profile自身也使用了@Conditional注解。

package org.springframework.context.annotation;

@Target({ElementType.TYPE, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Conditional({ProfileCondition.class}) // 组合了Conditional注解

public @interface Profile {

    String[] value();

}

package org.springframework.context.annotation;

class ProfileCondition implements Condition {

    ProfileCondition() {

    }

    // 通过提取出@Profile注解中的value值来与profiles配置信息进行匹配

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        if(context.getEnvironment() != null) {

            MultiValueMap attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());

            if(attrs != null) {

                Iterator var4 = ((List)attrs.get("value")).iterator();

                Object value;

                do {

                    if(!var4.hasNext()) {

                        return false;

                    }

                    value = var4.next();

                } while(!context.getEnvironment().acceptsProfiles((String[])((String[])value)));

                return true;

            }

        }

        return true;

    }

}

在业务复杂的情况下,显然需要使用到@Conditional注解来提供更加灵活的条件判断,例如以下几个判断条件:

  • 在类路径中是否存在这样的一个类。
  • 在Spring容器中是否已经注册了某种类型的Bean(如未注册,我们可以让其自动注册到容器中,上一条同理)。
  • 一个文件是否在特定的位置上。
  • 一个特定的系统属性是否存在。
  • 在Spring的配置文件中是否设置了某个特定的值。

举个栗子,假设我们有两个基于不同数据库实现的DAO,它们全都实现了UserDao,其中JdbcUserDAO与MySql进行连接,MongoUserDAO与MongoDB进行连接。现在,我们有了一个需求,需要根据命令行传入的系统参数来注册对应的UserDao,就像java -jar app.jar -DdbType=MySQL会注册JdbcUserDao,而java -jar app.jar -DdbType=MongoDB则会注册MongoUserDao。使用@Conditional可以很轻松地实现这个功能,仅仅需要在你自定义的条件类中去实现Condition接口,让我们来看下面的代码。(以下案例来自:https://dzone.com/articles/how-springboot-autoconfiguration-magic-works)

public interface UserDAO {

    ....

}

public class JdbcUserDAO implements UserDAO {

    ....

}

public class MongoUserDAO implements UserDAO {

    ....

}

public class MySQLDatabaseTypeCondition implements Condition {

    @Override

    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {

        String enabledDBType = System.getProperty("dbType"); // 获得系统参数 dbType

        // 如果该值等于MySql,则条件成立

        return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MySql"));

    }

}

// 与上述逻辑一致

public class MongoDBDatabaseTypeCondition implements Condition {

    @Override

    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {

        String enabledDBType = System.getProperty("dbType");

        return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MongoDB"));

    }

}

// 根据条件来注册不同的Bean

@Configuration

public class AppConfig {

    @Bean

    @Conditional(MySQLDatabaseTypeCondition.class)

    public UserDAO jdbcUserDAO() {

        return new JdbcUserDAO();

    }

     

    @Bean

    @Conditional(MongoDBDatabaseTypeCondition.class)

    public UserDAO mongoUserDAO() {

        return new MongoUserDAO();

    }

}

现在,我们又有了一个新需求,我们想要根据当前工程的类路径中是否存在MongoDB的驱动类来确认是否注册MongoUserDAO。为了实现这个需求,可以创建检查MongoDB驱动是否存在的两个条件类。

public class MongoDriverPresentsCondition implements Condition {

    @Override

    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {

        try {

            Class.forName("com.mongodb.Server");

            return true;

        } catch (ClassNotFoundException e) {

            return false;

        }

    }

}

public class MongoDriverNotPresentsCondition implements Condition {

    @Override

    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {

        try {

            Class.forName("com.mongodb.Server");

            return false;

        } catch (ClassNotFoundException e) {

            return true;

        }

    }

}

假如,你想要在UserDAO没有被注册的情况下去注册一个UserDAOBean,那么我们可以定义一个条件类来检查某个类是否在容器中已被注册。

public class UserDAOBeanNotPresentsCondition implements Condition {

    @Override

    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {

        UserDAO userDAO = conditionContext.getBeanFactory().getBean(UserDAO.class);

        return (userDAO == null);

    }

}

如果你想根据配置文件中的某项属性来决定是否注册MongoDAO,例如app.dbType是否等于MongoDB,我们可以实现以下的条件类。

public class MongoDbTypePropertyCondition implements Condition {

    @Override

    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {

        String dbType = conditionContext.getEnvironment().getProperty("app.dbType");

        return "MONGO".equalsIgnoreCase(dbType);

    }

}

我们已经尝试并实现了各种类型的条件判断,接下来,我们可以选择一种更为优雅的方式,就像@Profile一样,以注解的方式来完成条件判断。首先,我们需要定义一个注解类。

@Target({ ElementType.TYPE, ElementType.METHOD })

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Conditional(DatabaseTypeCondition.class)

public @interface DatabaseType {

    String value();

}

具体的条件判断逻辑在DatabaseTypeCondition类中,它会根据系统参数dbType来判断注册哪一个Bean。

public class DatabaseTypeCondition implements Condition {

    @Override

    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {

        Map<String, Object> attributes = metadata

                                            .getAnnotationAttributes(DatabaseType.class.getName());

        String type = (String) attributes.get("value");

        // 默认值为MySql

        String enabledDBType = System.getProperty("dbType", "MySql");

        return (enabledDBType != null && type != null && enabledDBType.equalsIgnoreCase(type));

    }

}

最后,在配置类应用该注解即可。

@Configuration

@ComponentScan

public class AppConfig {

    @Bean

    @DatabaseType("MySql")

    public UserDAO jdbcUserDAO() {

        return new JdbcUserDAO();

    }

    @Bean

    @DatabaseType("mongoDB")

    public UserDAO mongoUserDAO() {

        return new MongoUserDAO();

    }

}

AutoConfigure源码分析


通过了解@Conditional注解的机制其实已经能够猜到自动配置是如何实现的了,接下来我们通过源码来看看它是怎么做的。本文中讲解的源码基于Spring Boot 1.5.9版本(最新的正式版本)。

使用过Spring Boot的童鞋应该都很清楚,它会替我们生成一个入口类,其命名规格为ArtifactNameApplication,通过这个入口类,我们可以发现一些信息。

@SpringBootApplication

public class DemoApplication {

    public static void main(String[] args) {

        SpringApplication.run(DemoApplication.class, args);

    }

}

首先该类被@SpringBootApplication注解修饰,我们可以先从它开始分析,查看源码后可以发现它是一个包含许多注解的组合注解。

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan(

    excludeFilters = {@Filter(

    type = FilterType.CUSTOM,

    classes = {TypeExcludeFilter.class}

), @Filter(

    type = FilterType.CUSTOM,

    classes = {AutoConfigurationExcludeFilter.class}

)}

)

public @interface SpringBootApplication {

    @AliasFor(

        annotation = EnableAutoConfiguration.class,

        attribute = "exclude"

    )

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

    @AliasFor(

        annotation = EnableAutoConfiguration.class,

        attribute = "excludeName"

    )

    String[] excludeName() default {};

    @AliasFor(

        annotation = ComponentScan.class,

        attribute = "basePackages"

    )

    String[] scanBasePackages() default {};

    @AliasFor(

        annotation = ComponentScan.class,

        attribute = "basePackageClasses"

    )

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

}

该注解相当于同时声明了@Configuration、@EnableAutoConfiguration与@ComponentScan三个注解(如果我们想定制自定义的自动配置实现,声明这三个注解就足够了),而@EnableAutoConfiguration是我们的关注点,从它的名字可以看出来,它是用来开启自动配置的,源码如下:

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@AutoConfigurationPackage

@Import({EnableAutoConfigurationImportSelector.class})

public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

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

    String[] excludeName() default {};

}

我们发现@Import(Spring 提供的一个注解,可以导入配置类或者Bean到当前类中)导入了EnableAutoConfigurationImportSelector类,根据名字来看,它应该就是我们要找到的目标了。不过查看它的源码发现它已经被Deprecated了,而官方API中告知我们去查看它的父类AutoConfigurationImportSelector。

/** @deprecated */

@Deprecated

public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {

    public EnableAutoConfigurationImportSelector() {

    }

    protected boolean isEnabled(AnnotationMetadata metadata) {

        return this.getClass().equals(EnableAutoConfigurationImportSelector.class)?((Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, Boolean.valueOf(true))).booleanValue():true;

    }

}

由于AutoConfigurationImportSelector的源码太长了,这里我只截出关键的地方,显然方法selectImports是选择自动配置的主入口,它调用了其他的几个方法来加载元数据等信息,最后返回一个包含许多自动配置类信息的字符串数组。

public String[] selectImports(AnnotationMetadata annotationMetadata) {

    if(!this.isEnabled(annotationMetadata)) {

        return NO_IMPORTS;

    } else {

        try {

            AutoConfigurationMetadata ex = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

            List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

            configurations = this.removeDuplicates(configurations);

            configurations = this.sort(configurations, ex);

            Set exclusions = this.getExclusions(annotationMetadata, attributes);

            this.checkExcludedClasses(configurations, exclusions);

            configurations.removeAll(exclusions);

            configurations = this.filter(configurations, ex);

            this.fireAutoConfigurationImportEvents(configurations, exclusions);

            return (String[])configurations.toArray(new String[configurations.size()]);

        } catch (IOException var6) {

            throw new IllegalStateException(var6);

        }

    }

}

重点在于方法getCandidateConfigurations()返回了自动配置类的信息列表,而它通过调用SpringFactoriesLoader.loadFactoryNames()来扫描加载含有META-INF/spring.factories文件的jar包,该文件记录了具有哪些自动配置类。(建议还是用IDE去看源码吧,这些源码单行实在太长了,估计文章中的观看效果很差)

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

    List configurations = SpringFactoriesLoader

                                        .loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());

    Assert.notEmpty(configurations, "No auto configuration classes

    found in META-INF spring.factories.

    If you are using a custom packaging, make sure that file is correct.");

    return configurations;

}

     

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

    String factoryClassName = factoryClass.getName();

    try {

        Enumeration ex = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");

        ArrayList result = new ArrayList();

        while(ex.hasMoreElements()) {

            URL url = (URL)ex.nextElement();

            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));

            String factoryClassNames = properties.getProperty(factoryClassName);

            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));

        }

        return result;

    } catch (IOException var8) {

        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);

    }

}

自动配置类中的条件注解


接下来,我们在spring.factories文件中随便找一个自动配置类,来看看是怎样实现的。我查看了MongoDataAutoConfiguration的源码,发现它声明了@ConditionalOnClass注解,通过看该注解的源码后可以发现,这是一个组合了@Conditional的组合注解,它的条件类是OnClassCondition。

@Configuration

@ConditionalOnClass({Mongo.class, MongoTemplate.class})

@EnableConfigurationProperties({MongoProperties.class})

@AutoConfigureAfter({MongoAutoConfiguration.class})

public class MongoDataAutoConfiguration {

    ....

}

@Target({ElementType.TYPE, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Conditional({OnClassCondition.class})

public @interface ConditionalOnClass {

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

    String[] name() default {};

}

然后,我们开始看OnClassCondition的源码,发现它并没有直接实现Condition接口,只好往上找,发现它的父类SpringBootCondition实现了Condition接口。

class OnClassCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {

    .....

}

public abstract class SpringBootCondition implements Condition {

    private final Log logger = LogFactory.getLog(this.getClass());

    public SpringBootCondition() {

    }

    public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        String classOrMethodName = getClassOrMethodName(metadata);

        try {

            ConditionOutcome ex = this.getMatchOutcome(context, metadata);

            this.logOutcome(classOrMethodName, ex);

            this.recordEvaluation(context, classOrMethodName, ex);

            return ex.isMatch();

        } catch (NoClassDefFoundError var5) {

            throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.getMessage() + " not found. Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package (e.g. if you put a @ComponentScan in the default package by mistake)", var5);

        } catch (RuntimeException var6) {

            throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);

        }

    }

    public abstract ConditionOutcome getMatchOutcome(ConditionContext var1, AnnotatedTypeMetadata var2);

}

SpringBootCondition实现的matches方法依赖于一个抽象方法this.getMatchOutcome(context, metadata),我们在它的子类OnClassCondition中可以找到这个方法的具体实现。

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {

    ClassLoader classLoader = context.getClassLoader();

    ConditionMessage matchMessage = ConditionMessage.empty();

    // 找出所有ConditionalOnClass注解的属性

    List onClasses = this.getCandidates(metadata, ConditionalOnClass.class);

    List onMissingClasses;

    if(onClasses != null) {

        // 找出不在类路径中的类

        onMissingClasses = this.getMatches(onClasses, OnClassCondition.MatchType.MISSING, classLoader);

        // 如果存在不在类路径中的类,匹配失败

        if(!onMissingClasses.isEmpty()) {

            return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, onMissingClasses));

        }

        matchMessage = matchMessage.andCondition(ConditionalOnClass.class, new Object[0]).found("required class", "required classes").items(Style.QUOTE, this.getMatches(onClasses, OnClassCondition.MatchType.PRESENT, classLoader));

    }

    // 接着找出所有ConditionalOnMissingClass注解的属性

    // 它与ConditionalOnClass注解的含义正好相反,所以以下逻辑也与上面相反

    onMissingClasses = this.getCandidates(metadata, ConditionalOnMissingClass.class);

    if(onMissingClasses != null) {

        List present = this.getMatches(onMissingClasses, OnClassCondition.MatchType.PRESENT, classLoader);

        if(!present.isEmpty()) {

            return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, new Object[0]).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));

        }

        matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class, new Object[0]).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, this.getMatches(onMissingClasses, OnClassCondition.MatchType.MISSING, classLoader));

    }

    return ConditionOutcome.match(matchMessage);

}

// 获得所有annotationType注解的属性

private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<?> annotationType) {

    MultiValueMap attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);

    ArrayList candidates = new ArrayList();

    if(attributes == null) {

        return Collections.emptyList();

    } else {

        this.addAll(candidates, (List)attributes.get("value"));

        this.addAll(candidates, (List)attributes.get("name"));

        return candidates;

    }

}

private void addAll(List<String> list, List<Object> itemsToAdd) {

    if(itemsToAdd != null) {

        Iterator var3 = itemsToAdd.iterator();

        while(var3.hasNext()) {

            Object item = var3.next();

            Collections.addAll(list, (String[])((String[])item));

        }

    }

}   

// 根据matchType.matches方法来进行匹配

private List<String> getMatches(Collection<String> candidates, OnClassCondition.MatchType matchType, ClassLoader classLoader) {

    ArrayList matches = new ArrayList(candidates.size());

    Iterator var5 = candidates.iterator();

    while(var5.hasNext()) {

        String candidate = (String)var5.next();

        if(matchType.matches(candidate, classLoader)) {

            matches.add(candidate);

        }

    }

    return matches;

}

关于match的具体实现在MatchType中,它是一个枚举类,提供了PRESENT和MISSING两种实现,前者返回类路径中是否存在该类,后者相反。

private static enum MatchType {

    PRESENT {

        public boolean matches(String className, ClassLoader classLoader) {

            return OnClassCondition.MatchType.isPresent(className, classLoader);

        }

    },

    MISSING {

        public boolean matches(String className, ClassLoader classLoader) {

            return !OnClassCondition.MatchType.isPresent(className, classLoader);

        }

    };

    private MatchType() {

    }

    // 跟我们之前看过的案例一样,都利用了类加载功能来进行判断

    private static boolean isPresent(String className, ClassLoader classLoader) {

        if(classLoader == null) {

            classLoader = ClassUtils.getDefaultClassLoader();

        }

        try {

            forName(className, classLoader);

            return true;

        } catch (Throwable var3) {

            return false;

        }

    }

    private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException {

        return classLoader != null?classLoader.loadClass(className):Class.forName(className);

    }

    public abstract boolean matches(String var1, ClassLoader var2);

}

现在终于真相大白,@ConditionalOnClass的含义是指定的类必须存在于类路径下,MongoDataAutoConfiguration类中声明了类路径下必须含有Mongo.class, MongoTemplate.class这两个类,否则该自动配置类不会被加载。

在Spring Boot中到处都有类似的注解,像@ConditionalOnBean(容器中是否有指定的Bean),@ConditionalOnWebApplication(当前工程是否为一个Web工程)等等,它们都只是@Conditional注解的扩展。当你揭开神秘的面纱,去探索本质时,发现其实Spring Boot自动配置的原理就是如此简单,在了解这些知识后,你完全可以自己去实现自定义的自动配置类,然后编写出自定义的starter。

告别版本过低提示!微信3.9登录修复与聊天记录无缝迁移全攻略 本文详细解析了微信3.9版本过低提示的根源,并提供了完整的修复方案和聊天记录迁移指南。通过专用修复工具绕过版本检测,以及新旧版本聊天记录目录对比,帮助用户实现无缝迁移。特别提醒备份重要数据,确保操作安全。 阅读详情

相关推荐

centos7安装db2 version11.1

centos7安装db2 version11.1

AKA石头 1062

Spring Boot注解的运行原理

Spring Boot注解的运行原理

Smile的博客 2914

专题一:Labview表格控件 及 应用(三)在表格中插入一行数据

专题一:Labview表格控件 及 应用(三) 在表格中插入一行数据 最近一段时间,做试验台上位机编程,老是用到表格控件,这几天抽时间专门写一个表格控件的专题。有用到的朋友可以参考一下。 今天接着讲一下,表格控件的数据插入,和相关的操作 1.表格控件的数据插入原理: 表格控件是一个 2D 字符串数组。 在表格插入一行,就是在2D数组插入一行。 行、列插入类似** 以下以成绩录入为例说明 2.1 不要表头直接插入 知识点1:创建数组:把数学,语文,英语三个数值,连成一个一位数组(数值型)。 知

Labview十年工控_702老师的博客 1万+

springboot注解及原理详解

7.默认参数的key名称是方法里的参数名称,但通过@RequestParam可以自定义另外一个,因为在某些条件下,方法名称固定下来不可以修改了,但请求的参数又需要通过另外一个参数key名称来传递,这样就可以通过这样的方式来实现。6.@RequestParam还可以在参数为空的情况下,为我们设置一个参数的默认值,如图所示,可以看出来,没有参数的情况下,获取到的就是默认值。2.通过idea工具启动web项目,然后通过浏览器访问测试效果,不带参数则获取的参数为null,但页面可以正常显示,请求可以正常的执行。

m0_61346425的博客 4460

资产监测设备中130dBm的GPS信号注入天线端口,C/N0一般是多少?

笔者的团队一直从事于资产监测设备的开发,在开发过程中,涉及到很多细节的问题,针对新入职的同事来说较为陌生。同时,在团队外,还有需要对智能硬件终端开发感兴趣的用户。基于此背景下,笔者将在下面的内容主要介绍资产监测设备以及对设备中的GPS相关的问题:130dBm的GPS信号注入天线端口,C/N0一般是多少?作出详细的解答。 资产监测设备概述: 资产监测设备是基于物联网通信技术基础上的智能硬件终端,其主要作用是帮助用户监测资产在仓库、运输途中、交付整个流通环节中,提供资产的实时位置与状态参数。用户只需要登录资产

瑞奇聊通信模组 1229

基于结构的虚拟筛选模型 gnina 从源码编译

所需要的软件安装包: 安装 CUDA 装的时候看着点,第二个要你装驱动的选择,回答N,不装。 sudo sh cuda_10.0.130_410.48_linux.run sudo vim ~/.bashrc 在 ~/.bashrc 文件最后加上 export PATH=/usr/bin:/usr/local/cuda-10.0/bin${PATH} export LD_LIBRARY_PATH=/usr/local/cuda-10.0/lib64:${LD_LIBRARY_PATH} source ~

周迪新的博客 3188

spring常用注解整理

一、启动注解 @SpringBootApplication @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @...

weixin_44736968的博客 378

STM32H7多通道ADC数据采集实战:CubeMX配置ADC1/2/3+DMA/BDMA+Cache避坑全记录

本文详细解析了STM32H7多通道ADC数据采集的实战配置,涵盖CubeMX中ADC1/2/3与DMA/BDMA的协同设置及Cache一致性处理。通过硬件架构分析、DMA优化技巧和Cache避坑指南,帮助开发者实现高精度数据采集,适用于电机控制、环境监测等嵌入式系统。

weixin_28744613的博客 292

Springboot工作原理详解

5、在解析@Import注解的时候,会有一个getImport()方法,从主类开始递归解析注解,把所有包含@Import的注解都解析到,然后在processImport()方法中对import的类进行分类,例如AutoConfigurationImportSelect归属于ImportSelect的子类,在后续的过程中会调用DeferredImportSelectorHandler类里面的process方法,来完成整个EnableAutoConfiguration的加载。

huangtenglong的博客 1万+

168、OIS光学防抖系统设计:陀螺仪标定、音圈马达驱动与滚珠式/悬丝式结构选型

本文总结了OIS光学防抖系统设计中的关键问题与解决方案。作者通过实际案例指出,陀螺仪标定需重视零偏、灵敏度和温度补偿问题,音圈马达驱动需采用电流环控制并优化相位补偿。在机械结构选型方面,滚珠式适合大角度补偿但存在回差问题,悬丝式精度高但受重力影响,需根据应用场景选择。系统联调建议从开环到闭环逐步验证,注意陀螺仪与驱动频率匹配。最后给出5条经验建议:关注陀螺仪信噪比而非分辨率、预留标定余量、重视机械结构公差、配备专业调试工具、协调OIS与电子防抖的分工配合。文章强调OIS调试需针对不同模组特性进行反复测试与标

zzfmlxs的博客 45

uboot引导vxworks6.9(T4240) 启动

vxworks内核配置下搜索RAM_LOW_ADRS, 值为0x01000000, 是vxworks下载启动的地址,uboot启动时会用到。vxworks6.9 t4240用的是32地址空间,uboot下用的是64位地址空间,去uboot下修改law物理地址空间为32位。vxworks下flash基地址为 0xff000000, 将vx下的地址修改为0xe0000000,和uboot下地址一致。uboot下flash基地址为0xe000000,vxworks内核配置为build-in 符号表。

珂珂可爱多 3236

网易花19亿买地,要在上海建总部大楼,团队至少2000人

点击上方“程序员大咖”,选择“置顶公众号”关键时刻,第一时间送达!上海网之易璀璨网络科技有限公司以底价 19.6 亿元竞得徐汇区黄浦江南延伸段 WS5 单元 188S-Q...

<sdffdsfsdfdfs>sfsfsfsdfsdffds</sdfsDS>Fsd 7736

OpenFeign整合Sentinel实现熔断降级

常用于实现熔断降级的框架有Hystrix、Sentinel,我们常说的Spring Cloud项目说的其实是Spring Cloud Netflix,Hystrix以及前面学习过的Ribbon都是Netflix系的家族成员,所以使用Hystrix可以非常简单的与当前项目中使用到的OpenFeign、Ribbon整合,但笔者并没有选择Hystrix,而是选择阿里系的Sentinel。 为什么选择Sentinel而不是Hystrix?从接入简单考虑可能选择Hystrix是不错的选择,但笔者对Hyst...

吴就业 6718

【Spring】Spring Boot 自动配置原理分析

本文深入分析了SpringBoot自动配置机制的核心原理,围绕三个关键问题展开:1)自动配置的条件判断(通过@Conditional系列注解实现);2)需要创建的Bean定义(通过@Bean注解标记);3)Bean属性值的来源(通过@ConfigurationProperties绑定配置文件)。文章详细解析了自动配置类的加载过程(通过spring.factories文件)、条件注解的演进(从@Profile到@Conditional)以及属性绑定的实现机制。

小七的博客 1086

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Spring Boot自动配置原理分析

  我们知道,spring boot自动配置功能可以根据不同情况来决定spring配置应该用哪个,不应该用哪个,举个例子:   Spring的JdbcTemplate是不是在Classpath里面?如果是,并且DataSource也存在,就自动配置一个JdbcTemplate的Bean;   Thymeleaf是不是在Classpath里面?如果是,则自动配置Thymeleaf的模板解析器、视...

weixin_30685047的博客 77

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Spring Boot自动配置原理—简单源码分析

文章目录引言环境准备Spring Boot 2.x 原理分析@SpringBootApplication:@SpringBootConfiguration@EnableAutoConfiguration@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)Spring Boot 1.5.x 分析总结 引言 众所...

Frank的博客 515

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

spring boot实战自动配置原理分析_spring boot自动配置原理、实战

转载 :http://blog.youkuaiyun.com/liaokailin/article/details/49559951。

2401_87557053的博客 949

Spring boot自动配置原理分析

Spring boot自动配置就是把一些默认的东西配置进去 首先找到我们的引导类,按住CTRL键,点击@SpringBootApplication注解 (部分) @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @Enable...

qq_44549134的博客 269

Spring Boot 自动配置实现原理(源码分析)

这里我们只简单贴出两个重点方法,process 方法和 selectImports 方法,我们在 process 方法中看到了 AutoConfigurationImportSelector#getAutoConfigurationEntry 方法,这个方法在项目启动时候是调用了的,因此我们可以进一步猜测,项目启动的时候是调用了 AutoConfigurationImportSelector.AutoConfigurationGroup#process 方法。下面我们从源码层面去剖析。

weixin_42118323的博客 1913

Spring Boot的自动配置原理源码分析

Spring Boot的主方法会有一个注解 @SpringBootApplication 点进去看以后 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterTy

NewBeeMu的博客 502

03 Spring Boot 自动配置及原理分析

Spring Boot 自动配置 文章目录Spring Boot 自动配置@[toc]自动配置原理@EnableAutoConfiguration 作用以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理总结@Conditional派生注解查看那些自动配置类生效了 自动配置原理 配置文件到底能写什么?怎么写?自动配置原理; 配置文件能配置的属性参照 SpringBoot启动的时候加载主配置类,开启了自动配置功能 @SpringBootApplication

kevinkda的博客 345

深入Spring Boot :从源码分析自动配置原理

通过上图中的代码解释,可以看到DispatcherServletAutoConfiguration会自动实例化一个dispatcherServlet,但是必须满足一定的条件,如当前是web应用、存在spring-webmvc的jar包、当前Spring应用上下文中不存在DispatcherServlet实例、存在servlet的jar包等,自动配置类只有在被满足条件的情况下才可以被触发,执行一些bean的实例化操作,代替一些我们经常通过代码或配置实现的初始化或实例化bean的操作。...

AS011x的博客 934

spring boot 特性一:自动配置原理分析

Spring Boot中引入了自动配置,让开发者利用起来更加的简便、快捷,本篇分析下Spring Boot中的自动配置原理。 注解结构: 文章目录一、启动类:二、@SpringBootApplication注解三、@EnableAutoConfiguration 一、启动类: 二、@SpringBootApplication注解 进入@SpringBootApplication的注解类,会发现...

严老板的技术梦想博客 8万+

Spring Boot自动配置原理分析

一、写在前面 随着时间的迁移Spring Boot 越来越多的出现在Java 后端程序员的视野中,Spring Boot 之所以会那么流行,很大的一个原因是自身集成了很多的Bean,简化了传统Sring 项目琐碎的文件配置。这些自动配置的Bean 在入口类启动的时候完成注入,并由Spring 的IoC 容器管理。所以在使用某些Bean的时候,不需要再由我们自动配置。下面我们就通过源码的方式来看看...

点滴记录 957
上一篇: 使用 losetup 帮你创建虚拟磁盘
下一篇: JAVA 同步实现原理
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值