SpringBoot2源码1-嵌入式Tomcat启动和@SpringBootApplication注解原理

news2024/11/19 2:41:07

1. 测试案例

1.1 引入依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.5</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core -->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-core</artifactId>
    <version>8.5.64</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-jasper -->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <version>8.5.64</version>
</dependency>

1.2 Servlet处理类

public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello tomcat");
    }
}

1.3 启动类

public class Main {

    public static void main(String[] args) throws LifecycleException {
        //自己写Tomcat的启动源码
        Tomcat tomcat = new Tomcat();

        tomcat.setPort(8888);
        tomcat.setHostname("localhost");
        tomcat.setBaseDir(".");

        Context context = tomcat.addWebapp("/boot", System.getProperty("user.dir") + "/src/main");

        //给Tomcat里面添加一个Servlet
        Wrapper hello = tomcat.addServlet("/boot", "hello", new HelloServlet());
        hello.addMapping("/66"); //指定处理的请求

        tomcat.start(); //启动tomcat 注解版MVC利用Tomcat SPI机制

        tomcat.getServer().await(); //服务器等待
    }
}

直接运行main方法,在浏览器中输入 http://localhost:8888/boot/66 ,输出为:

hello tomcat

以上只是一个Tomcat的启动并使用HelloServlet处理一个请求的案例。下面我们结合SpringMVC,看如何优雅的启动。

2. 嵌入式Tomcat启动SpringMVC

2.1 最简单的方法

public class Main {

    public static void main(String[] args) throws LifecycleException {
        //自己写Tomcat的启动源码
        Tomcat tomcat = new Tomcat();

        tomcat.setPort(8888);
        tomcat.setHostname("localhost");
        tomcat.setBaseDir(".");

        Context context = tomcat.addWebapp("/boot", System.getProperty("user.dir") + "/src/main");

        //自己创建 DispatcherServlet 对象,并且创建ioc容器,DispatcherServlet里面有ioc容器
        DispatcherServlet servlet = new DispatcherServlet();
        Wrapper hello = tomcat.addServlet("/boot", "hello", servlet);

        tomcat.start(); //启动tomcat 注解版MVC利用Tomcat SPI机制

        tomcat.getServer().await(); //服务器等待
    }
}

当然,如果使用这种方法启动,需要自己往容器中注入DispatcherServlet的各种初始化(九大组件等),所以这种方式比较麻烦,我们不采用这种方式。

2.2 Tomcat的SPI机制启动

利用tomcat的SPI启动机制,SPI机制下 QuickAppStarter生效创建 ioc容器配置DispatcherServlet等各种组件。代码如下:

Main.java:

public class Main {

    public static void main(String[] args) throws LifecycleException {
        //自己写Tomcat的启动源码
        Tomcat tomcat = new Tomcat();

        tomcat.setPort(8888);
        tomcat.setHostname("localhost");
        tomcat.setBaseDir(".");

        Context context = tomcat.addWebapp("/boot", System.getProperty("user.dir") + "/src/main");

        tomcat.start(); //启动tomcat 注解版MVC利用Tomcat SPI机制

        tomcat.getServer().await(); //服务器等待

    }

}

QuickAppStarter类:

/**
 * 最快速的整合注解版SpringMVC和Spring的
 */
public class QuickAppStarter extends AbstractAnnotationConfigDispatcherServletInitializer {
   @Override //根容器的配置(Spring的配置文件===Spring的配置类)
   protected Class<?>[] getRootConfigClasses() {
      return new Class<?>[]{SpringConfig.class};
   }

   @Override //web容器的配置(SpringMVC的配置文件===SpringMVC的配置类)
   protected Class<?>[] getServletConfigClasses() {
      return new Class<?>[]{SpringMVCConfig.class};
   }

   @Override //Servlet的映射,DispatcherServlet的映射路径
   protected String[] getServletMappings() {
      return new String[]{"/"};
   }

   @Override
   protected void customizeRegistration(ServletRegistration.Dynamic registration) {
//    super.customizeRegistration(registration);

//    registration.addMapping("");//
   }
}

启动后,在浏览器访问http://localhost:8888/boot/hello66 ,输出为:

66666666~~~~~

SpringBoot就是采用上面的这种方式来启动Tomcat的。SpringBoot封装了功能的自动配置,导入各种starter依赖,SpringBoot封装了很多的自动配置,帮我们给容器中放了很多组件。

3. @SpringBootApplication 注解原理

1.@SpringBootApplication注解

对于Springboot项目,它有一个启动类。一般如下(此处以Springboot演示案例为例):

@SpringBootApplication
public class DemoSpringbootApplication{
    public static void main(String[] args) {
        SpringApplication.run(DemoSpringbootApplication.class, args);
    }
}

从上述代码里看出,启动类引用了一个注解 @SpringBootApplication,而
@SpringBootApplication实际上是一个复合注解,它的类定义如下所示:

@Target(ElementType.TYPE)         
@Retention(RetentionPolicy.RUNTIME)
@Documented                     
@Inherited                        
@SpringBootConfiguration             // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration             // 开启自动配置
@ComponentScan(excludeFilters = {    // 扫描组件
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}

实际上 @SpringBootApplication内的核心注解有三个: @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

3.1 @SpringBootConfiguration

@SpringBootConfiguration实际上是引入的@Configuration,而@Configuration是Spring的注解类,用于定义配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到srping容器中,并且实例名就是方法名,等同于spring的XML配置文件。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

@Configuration与XML配置的简单对比如下:

@Configuration
public class SpringDemo{

}

等价于XML形式的<beans></beans>:

<?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-3.0.xsd">
</beans>

3.2 @EnableAutoConfiguration

@EnableAutoConfiguration是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,它也是复合注解,定义如下所示:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage      //自动配置包
@Import({AutoConfigurationImportSelector.class})  //借助AutoConfigurationImportSelector自动配置类
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

@EnableAutoConfiguration里重要的注解分别是@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class),看名知意,@AutoConfigurationPackage:自动配置包,AutoConfigurationImportSelector:自动配置组件的导入。

3.2.1 @AutoConfigurationPackage

@AutoConfigurationPackage具体定义如下所示:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

@AutoConfigurationPackage定义可以看出,实际是借助@Import导入了Registrar,而Registrar中主要调用了registerBeanDefinition方法来进行bean定义的注册。Registrar类定义如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            //此处是注册了一个Bean的定义。
            //getPackageName()其实返回了当前主程序类的 同级以及子级的包组件。
            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());

        }
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
        }
    }

RegistrarregisterBeanDefinitions实际是调用AutoConfigurationPackagesregister方法,其定义如下:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
   if (registry.containsBeanDefinition(BEAN)) {
      BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
      beanDefinition.addBasePackages(packageNames);
   }
   else {
      registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
   }
}

可以看到这里主要面有一个注册bean定义的方法,主要就是把被org.springframework.boot.autoconfigure.SpringBootApplication注解标记的包的定义信息注册到容器中中 。

3.2.2 AutoConfigurationImportSelector

AutoConfigurationImportSelector实现了DeferredImportSelector,所以@EnableAutoConfiguration什么时候处理已经清楚了: ConfigurationClassParser解析启动类这个配置类过程中处理了该注解,由于AutoConfigurationImportSelector是一个DeferredImportSelector,所以会将AutoConfigurationImportSelector暂存在DeferredImportSelectorHandler#deferredImportSelectors一个List中,等当前一轮配置类都解析完了才会处理AutoConfigurationImportSelector

DeferredImportSelector

// ImportSelector
public interface ImportSelector {

   String[] selectImports(AnnotationMetadata importingClassMetadata);

   @Nullable
   default Predicate<String> getExclusionFilter() {
      return null;
   }
}

public interface DeferredImportSelector extends ImportSelector {

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

    interface Group {
        void process(AnnotationMetadata metadata, DeferredImportSelector selector);

        Iterable<Entry> selectImports();

        class Entry {

            private final AnnotationMetadata metadata;
            private final String importClassName;

            // constructor, getters and setters
        }
    }
}

ImportSelector比较简单,就是一个selectImports方法,就是基于当前配置类的AnnotationMetadata返回一些需要import的类名。DeferredImportSelector扩展了ImportSelector,增加了getImportGroup方法,作用是什么?

由于DeferredImportSelector是先暂存,延迟到一批配置类处理完后会统一处理这些DeferredImportSelector,此时这些不同的DeferredImportSelector导入哪些类可以放在一起做一定的逻辑处理。每个DeferredImportSelector的会属于一个Group,属于同一个Group的DeferredImportSelector会统一处理,Group为null则默认为DefaultDeferredImportSelectorGroup。Entry保存了需要import的类名和配置类的AnnotationMetadata的映射关系,因为多个DeferredImportSelector可能是由多个配置类导入的,最后返回的时候要知道import进来的类是由哪个配置类import进来的

DeferredImportSelector调用上与ImportSelector不同,对于ImportSelector,会直接调用selectImports;而对于DeferredImportSelector,外部先按照Group对所有DeferredImportSelector进行分组,属于同一组的DeferredImportSelector,调用这个组的Group#process方法处理每个DeferredImportSelector,Group#process里面一般会调用DeferredImportSelector#selectImports将导入进来的类暂存一下,然后调用一次Group#selectImports方法,返回这一组DeferredImportSelector最重要import的类

DefaultDeferredImportSelectorGroup

先看一下默认的DefaultDeferredImportSelectorGroup实现细节

private static class DefaultDeferredImportSelectorGroup implements Group {

        private final List<Entry> imports = new ArrayList<>();

        @Override
        public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
                for (String importClassName : selector.selectImports(metadata)) {
                        this.imports.add(new Entry(metadata, importClassName));
                }
        }

        @Override
        public Iterable<Entry> selectImports() {
                return this.imports;
        }
}

imports保存了导入类的Entry。process方法将DeferredImportSelector导入了的类缓存一下;然后selectImports返回每个DeferredImportSelector导入的类。

既然多个DeferredImportSelector属于一个Group,那么哪些DeferredImportSelector属于AutoConfigurationGroup

  • AutoConfigurationImportSelectorImportAutoConfigurationImportSelector都属于AutoConfigurationGroup,分别对应@EnableAutoConfiguration注解和@ImportAutoConfiguration注解

AutoConfigurationGroup

private static class AutoConfigurationGroup
                implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {

        private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();

        private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();

        @Override
        public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
                Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                                () -> String.format("Only %s implementations are supported, got %s",
                                                AutoConfigurationImportSelector.class.getSimpleName(),
                                                deferredImportSelector.getClass().getName()));
                AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                                .getAutoConfigurationEntry(annotationMetadata);
                this.autoConfigurationEntries.add(autoConfigurationEntry);
                for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                        this.entries.putIfAbsent(importClassName, annotationMetadata);
                }
        }
}

AutoConfigurationGroup#process过程

AutoConfigurationGroup#process方法调用了AutoConfigurationImportSelector或者ImportAutoConfigurationImportSelector的selectImports方法,拿到AutoConfigurationEntry,暂存在autoConfigurationEntries里,并且每个导入进来的类都暂存在entries里。

两个ImportSelector的selectImports做了什么?

AutoConfigurationImportSelector#selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
                return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 从 spring.factories 里面去读取配置的好的AutoConfiguration类
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
}

可以看到这里主要做了三件事:

  1. 加载需要自动配置的类信息;
  2. 根据条件筛选自动配置类信息;
  3. 发布事件

接下来,我们看一下getCandidateConfigurations()方法:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                        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;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
}

接下来,我们看一下SpringFactoriesLoader.loadFactoryNames()方法:

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
   Map<String, List<String>> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   result = new HashMap<>();
   try {
      Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryTypeName = ((String) entry.getKey()).trim();
            String[] factoryImplementationNames =
                  StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
            for (String factoryImplementationName : factoryImplementationNames) {
               result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                     .add(factoryImplementationName.trim());
            }
         }
      }

      // Replace all lists with unmodifiable lists containing unique elements
      result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
            .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
      cache.put(classLoader, result);
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
   return result;
}

AutoConfigurationImportSelector#selectImports实际上就是调用了SpringFactoriesLoaderspring.factories里面去读取配置的好的AutoConfiguration类。

我们来看一下 META-INF/spring.factories下的文件:

我们看其中一个类:

可以看到这里面定义了一堆需要自动装配的bean,而这些ben里面都会有一个或多个条件,也就是说他要装配这个bean,需要满足已经达成这些条件才进行自动装配

AutoConfigurationGroup#selectImports过程

AutoConfigurationGroup#selectImports方法主要做了两个事情

  • 导进来的类排掉exclusion
  • 排序,返回
private static class AutoConfigurationGroup
                implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {

        private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();

        private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();

@Override
        public Iterable<Entry> selectImports() {
                if (this.autoConfigurationEntries.isEmpty()) {
                        return Collections.emptyList();
                }
                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来排序的,看一下AutoConfigurationSorter#getInPriorityOrder方法:

    List<String> getInPriorityOrder(Collection<String> classNames) {
            AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
                            this.autoConfigurationMetadata, classNames);
            List<String> orderedClassNames = new ArrayList<>(classNames);
            // Initially sort alphabetically
            Collections.sort(orderedClassNames);
            // Then sort by order
            orderedClassNames.sort((o1, o2) -> {
                    int i1 = classes.get(o1).getOrder();
                    int i2 = classes.get(o2).getOrder();
                    return Integer.compare(i1, i2);
            });
            // Then respect @AutoConfigureBefore @AutoConfigureAfter
            orderedClassNames = sortByAnnotation(classes, orderedClassNames);
            return orderedClassNames;
    }
    
    

    注释上:先按字母顺序排序,再按order排序,最后按@AutoConfigureBefore @AutoConfigureAfter两个注解排序,由于排序是稳定排序,所有三种排序方法中:注解定义的顺序优先;注解没定义顺序则按order来;order相同则按字母顺序

    order排序怎么排?

    • classes.get(o1)获取到了AutoConfigurationClass,AutoConfigurationClass#getOrder实际上获取的是AutoConfigureOrder定义的order,跟ordered接口、@Order注解都没关系,主要是为了不影响bean注册的顺序
    private int getOrder() {
            if (wasProcessed()) {
                    return this.autoConfigurationMetadata.getInteger(this.className, "AutoConfigureOrder",
                                    AutoConfigureOrder.DEFAULT_ORDER);
            }
            Map<String, Object> attributes = getAnnotationMetadata()
                            .getAnnotationAttributes(AutoConfigureOrder.class.getName());
            return (attributes != null) ? (Integer) attributes.get("value") : AutoConfigureOrder.DEFAULT_ORDER;
    }
    
    
  • getAutoConfigurationMetadata()封装自动配置元数据信息

private AutoConfigurationMetadata getAutoConfigurationMetadata() {
   if (this.autoConfigurationMetadata == null) {
      this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
   }
   return this.autoConfigurationMetadata;
}

AutoConfigurationMetadataLoader类:

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
   try {
      Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
            : ClassLoader.getSystemResources(path);
      Properties properties = new Properties();
      while (urls.hasMoreElements()) {
         properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
      }
      return loadMetadata(properties);
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
   }
}

可以看到这里主要就是去加载spring-autoconfigure-metadata.properties文件中的信息进行封装返回,那这个文件里都定义了啥呢:

可以看到这里主要就是定义了一些需要自动配置的类自动装配需要满足的一些条件

3.3 @ComponentScan

@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。

注意:开发者可以通过basePakage等属性来细粒度的定制@ComponentScan自动扫描的范围。如果不指定,则默认Spring框架实现会从声明·@ComponentScan·所在类的·package·进行扫描。这就是SpringBoot的启动类为什么最好放在root package包下面的原因

3.4 流程框图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/159820.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

看完RabbitMQ了的几种消息模型,我对RabbitMQ有了新的认识

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;曾经在某央企公司实习&#xff0c;目前在某税务公司。前面已经记录了两种消息模型知识&#xff0c;因此本篇文章将记录和分享剩下的RabbitMQ几种消息模型相关知识点。 本篇文章记录的基础知识&am…

锁策略和synchronized

1.常见的锁策略&#xff08;1&#xff09;乐观锁 和 悲观锁乐观锁&#xff1a;预测锁竞争的情况不激烈&#xff08;工作量较少&#xff09;悲观锁&#xff1a;预测锁竞争的情况很激烈&#xff08;工作量较多&#xff09;&#xff08;2&#xff09;轻量级锁 和 重量级锁轻量级锁…

IDEA Alibaba代码格式化(EclipseCodeFormatter)

1.阿里模板下载地址https://github.com/alibaba/p3c/tree/master/p3c-formatter2.下载阿里配置文件&#xff08;eclipse-codestyle.xml&#xff09;&#xff0c;因为此文件是针对ecplice的&#xff0c;在IDEA中使用配置文件&#xff0c;需要安装Eclipse Code Formatter插件3.配…

文档控件 Aspose.PDF for Java 授权须知

Aspose.PDF是一款高级PDF处理API&#xff0c;可以在跨平台应用程序中轻松生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现&#xff0c;保护和打印文档。无需使用Adobe Acrobat。此外&#xff0c;API提供压缩选项&#xff0c;表创建和处理&#xff0c;图形和图像功能&…

马蹄集 卡罗尔数

卡罗尔数 难度&#xff1a;白银 0时间限制&#xff1a;1秒 巴占用内存&#xff1a;64M 卡罗尔数是其值满足4n-2(n1)-1的整数(n为正整数)。输入正整 数N判断它是不是卡罗尔数&#xff0c;输出YES或者NO。 #include <bits/stdc.h>> using namespace std; int main(…

SpringMVC超详解

SpringMVC超详解1、SpringMVC简介1.1、什么是MVCMVC是一种软件架构的思想&#xff0c;将软件按照模型、视图、控制器来划分M&#xff1a;Model&#xff0c;模型层&#xff0c;指工程中的JavaBean&#xff0c;作用是处理数据JavaBean分为两类&#xff1a;一类称为实体类Bean&…

算法拾遗二十四之暴力递归到动态规划二

算法拾遗二十四之暴力递归到动态规划二背包问题一优化题目二优化题目三&#xff08;贴纸拼词&#xff09;优化题目四&#xff1a;最长公共子序列优化背包问题一 weights[i]和values[i]的值都是大于等于0的&#xff0c;不存在负数的情况。 可以从尝试入手&#xff0c;改动态规划…

基于智能矿山电力监控系统的设计与应用方法

摘要&#xff1a;随着煤矿建设的智能化程度越来越高&#xff0c;构建智能电力监控系统实现对矿山生产的有效监控至关重要。首先分析了矿山电力监控系统存在的主要问题&#xff0c;其次重点介绍了基于智能矿山电力监控系统的设计过程&#xff0c;后提出了加强智能电力监控系统的…

Inspur KOS 龙蜥衍生版面向智慧新媒体转型的探索与实践 | 龙蜥案例

编者按&#xff1a;日前&#xff0c;龙蜥社区理事单位浪潮信息正式对外发布基于龙蜥操作系统&#xff08;Anolis OS&#xff09;的服务器操作系统 Inspur KOS&#xff0c;并基于 Inspur KOS 推出可视化迁移方案 C2K&#xff0c;该方案能够将用户应用安全可靠地切换到 Inspur KO…

【系列02】Java流程控制 scanner 选择结构 循环结构语句使用 [有目录]

Scanner输入 Next和NextLine区别 NextLine 用的会多点 因为Next遇到空格就断开了 next语法使用 package com.SunAo.scanner; import java.util.Scanner; public class Demo01 {public static void main(String[] args) {//创建扫描器用来接收 键盘输入Scanner scanner new …

李宏毅ML-批次与动量

批次与动量 文章目录批次与动量1. Small batch or Large batch?2. Gradient descent Momentum3. 总结1. Small batch or Large batch? 在使用 gradient descent 进行 optimization 时&#xff0c;在每一次 epoch 前&#xff0c;要 shuffle 所有的资料&#xff0c;然后再分成…

无桌面Centos7系统安装Cypress@9.0.0并运行

一、安装Cypress 安装前准备 1、安装npm 下载安装包 cd /usr/local mkdir node cd node wget https://npm.taobao.org/mirrors/node/v15.8.0/node-v15.8.0-linux-x64.tar.gz ls -l解压这个包 tar -zxvf node-v15.8.0-linux-x64.tar.gz 你会发现已经有一个node的目录解压…

11、Javaweb_JSPMVCELJSTL三层架构用户列表案例

JSP: 1. 指令 * 作用&#xff1a;用于配置JSP页面&#xff0c;导入资源文件 * 格式&#xff1a; <% 指令名称 属性名1属性值1 属性名2属性值2 ... %> * 分类&#xff1a; 1. page &#xff1a; 配置JSP页面的 * content…

①【Spring】一文了解IOC容器

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 一文掌握IOC一、IOC二、IOC容器的实现BeanFact…

一文掌握fastapi微服务开发

目录 一、概述 1.1 微服务 1.1.1 微服务的优势 1.1.2 微服务的缺点 1.2 为何使用Python开发微服务 1.3 FastAPI概述 二、开发 2.1 安装FastAPI 2.1.1 安装虚拟环境 2.1.2 创建虚拟环境 2.1.3 激活虚拟环境 2.1.4 安装FastAPI 2.2 FastAPI简单使用 2.2.1 查询 2.…

java注解以及如何利用反射获取注解的属性值

一、什么是注解 1.Annotation是从JDK5.0开始引入的新技术 2.Annotation的作用 : &#xff08;1&#xff09;不是程序本身&#xff0c;可以对程序作出解释(这一点和注释(comment)没什么区别)&#xff08;2&#xff09;可以被其他程序(比如:编译器等)读取 3.Annotation的格式…

【每日一题】【LeetCode】【第十一天】杨辉三角

解决之路 题目描述 测试案例&#xff08;部分&#xff09; 第一次 杨辉三角感觉还是挺经典的代码题目&#xff1f;之前大一学C语言好像写过一次。 不过&#xff0c;自己当时就不会写&#xff0c;这次自己先试试能不能想出解决方案。 输入数字是几&#xff0c;那就要输出几…

亚马逊云科技帮助Gemsouls在云上快速实现技术验证与部署

元宇宙热度居高不下&#xff0c;它所创造的虚拟世界进一步拉近了人与人之间的距离&#xff0c;用数字化的形式消除地理与空间上的隔阂。而高度拟真化的虚拟人与AI虚拟社交&#xff0c;是元宇宙落地的重要领域&#xff0c;打造以人工智能驱动的虚拟人社交平台已成为行业大势。 …

DNS 的一些基础知识,以及 DNS 转换域名的过程

DNS(Domain Name System)&#xff0c;主要作用是将域名转成 IP&#xff0c;本文主要讲解了 DNS 的一些基础知识&#xff0c;以及 DNS 转换域名的过程。DNS 是什么dig命令绝大多数网络通信都是基于 TCP/IP 协议&#xff0c;而 TCP/IP 协议的基础是 IP&#xff0c;所以网络中的计…

获取未来时间 一年或N年

需求 展示从本月初开始 一年的时间 或N年的时间 以便用户选择思路 一年12个月 是已知的 从本月到12月可以生成本年的每天数据从1月至上月可以生成所需得到最后一年的数据今年加最后一年的月份可拼接一年时间 所以中间年份是所需年分-1的数组数据中间年份都是1-12月 可以通过年份…