Spring Boot 入门

news2025/10/29 10:02:28

37) Boot 骨架项目

https://start.spring.io/pom.xml

38) Boot War项目

步骤1:创建模块,区别在于打包方式选择 war

步骤2:编写控制器

@Controller
public class MyController {
​
    @RequestMapping("/hello")
    public String abc() {
        System.out.println("进入了控制器");
        return "hello";
    }
}

步骤3:编写 jsp 视图,新建 webapp 目录和一个 hello.jsp 文件,注意文件名与控制器方法返回的视图逻辑名一致

src
    |- main
        |- java
        |- resources
        |- webapp
            |- hello.jsp

步骤4:配置视图路径,打开 application.properties 文件

spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp

将来 prefix + 控制器方法返回值 + suffix 即为视图完整路径

测试

内置Tomcat测试 如果用 mvn 插件 mvn spring-boot:run 或 main 方法测试

  • 必须添加如下依赖,因为此时用的还是内嵌 tomcat,而内嵌 tomcat 默认不带 jasper(用来解析 jsp)

<!-- 解析 jsp -->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>

也可以使用 Idea 配置 tomcat 来测试,此时用的是外置 tomcat

  • 骨架生成的代码中,多了一个 ServletInitializer,它的作用就是配置外置 Tomcat 使用的,在外置 Tomcat 启动后,去调用它创建和运行 SpringApplication

对于 jar 项目,若要支持 jsp,也可以在加入 jasper 依赖的前提下,把 jsp 文件置入 META-INF/resources

39) Boot 启动过程

阶段一:SpringApplication 构造

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		// 记录 BeanDefinition 源
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// 推断应用类型
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
		// 记录 ApplicationContext 初始化器
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 记录监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 推断主启动类
		this.mainApplicationClass = deduceMainApplicationClass();
	}
  1. 记录 BeanDefinition 源

  2. 推断应用类型

  3. 记录 ApplicationContext 初始化器

  4. 记录监听器

  5. 推断主启动类

阶段二:执行 run 方法

public ConfigurableApplicationContext run(String... args) {
		// ...

        // 1.得到 SpringApplicationRunListeners,名字取得不好,实际是事件发布器
		SpringApplicationRunListeners listeners = getRunListeners(args);
        // 发布 application starting 事件1️⃣,代表Spring Boot 应用开始启动了
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
            // 2.封装启动 args 把参数分为选项参与非选项参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 3.
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
            // 7.打印 banner(*)
			Banner printedBanner = printBanner(environment);
            // 8.创建容器
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			// 9.准备容器
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			// 11.refresh 容器 
            refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
            // 11.1 发布 application started 事件5️⃣
			listeners.started(context);
            // 12.执行 runner
			callRunners(context, applicationArguments);
		}catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

		try {
            // 持续运行
			listeners.running(context);
		}
        // ....
}
        


// 3.
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 根据参数信息封装成一个propertiesSource并添加到 Environment -基于命令行的参数
        // 4.ConfigurationPropertySources 处理(*)
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		// 对命名不规范的键统一处理成 ‘-’分割
        ConfigurationPropertySources.attach(environment);
        // 4.1发布 application environment 已准备事件2️⃣
        // 5.通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理(*)

        //* application.properties,由 StandardConfigDataLocationResolver 解析
        // * spring.application.json
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		// 6.绑定 spring.main 到 SpringApplication 对象(*)
        bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

// 9.
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		// 9.1发布 application context 已初始化事件3️⃣
        applyInitializers(context);
		listeners.contextPrepared(context);
		bootstrapContext.close(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
        // 10.加载 bean 定义
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
        // 10.1发布 application prepared 事件4️⃣
		listeners.contextLoaded(context);
	}

// 11.1初始化每一个单例
protected void refresh(ConfigurableApplicationContext applicationContext) {
		applicationContext.refresh();
	}

// 12.1 调用实现了 ApplicationRunner或者CommandLineRunner接口的Bean
private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}
  1. 得到 SpringApplicationRunListeners,名字取得不好,实际是事件发布器

    • 发布 application starting 事件1️⃣

  2. 封装启动 args

  3. 准备 Environment 添加命令行参数(*)

  4. ConfigurationPropertySources 处理(*)

    • 发布 application environment 已准备事件2️⃣

  5. 通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理(*)

    • application.properties,由 StandardConfigDataLocationResolver 解析

    • spring.application.json

  6. 绑定 spring.main 到 SpringApplication 对象(*)

  7. 打印 banner(*)

  8. 创建容器

  9. 准备容器

    • 发布 application context 已初始化事件3️⃣

  10. 加载 bean 定义

    • 发布 application prepared 事件4️⃣

  11. refresh 容器

    • 发布 application started 事件5️⃣

  12. 执行 runner

    • 发布 application ready 事件6️⃣

    • 这其中有异常,发布 application failed 事件7️⃣

41) Boot 自动配置

public static void main(String[] args) throws IOException {
        GenericApplicationContext context = new GenericApplicationContext();
        // 设置是否应该通过注册具有相同名称的不同定义(自动替换前者)来覆盖bean定义
        context.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false);
        context.registerBean("config", Config.class);
        // 用于@Configuration类的引导处理
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        System.out.println(context.getBean(Bean1.class));
    }

    @Configuration // 本项目的配置类,如果配置重复,优先处理本项目的Bean
    @Import(MyImportSelector.class)
    static class Config {
        @Bean
        public Bean1 bean1() {
            return new Bean1("本项目");
        }
    }
    // 在处理完所有@Configuration bean后运行的ImportSelector
    static class MyImportSelector implements DeferredImportSelector {
        // SpringFactoriesLoader 会扫描当前项目下 src/main/resources/META-INF/spring.factories 的配置信息
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//            System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
//            for (String name : SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, null)) {
//                System.out.println(name);
//            }
//            System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
            return names.toArray(new String[0]);
        }
    }

使用@Import + ImportSelector.class  结合 SpringFactoriesLoader.class 扫描所有依赖包 src/main/resources/META-INF/spring.factories 的配置信息 可以轻松的引入第三方的配置。

        对于Bean类型重复的问题 需要检查 org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition

检查DefaultListableBeanFactor的属性  allowBeanDefinitionOverriding(默认允许覆盖),可通过配置。

# 不允许注册具有相同名称的bean
spring.main.allow-bean-definition-overriding = false

        当不许同名覆盖注册时,若想要优先本项目中的配置文件生效则可以配合使用 DeferredImportSelector.class 与 @Conditional 开头的注解配合使用。

 BeanDefinitionRegistryPostProcessor如何动态注册Bean到Spring_java_脚本之家 (jb51.net)https://www.jb51.net/article/242116.htm

Aop 自动配置

    @Configuration
    @Import(MyImportSelector.class)
    static class Config {

    }

    static class MyImportSelector implements DeferredImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{AopAutoConfiguration.class.getName()};
        }
    }

解析spring.factries 以spring.aop开头的配置信息

spring.aop.auto=true 或者缺失了spring.aop.auto 会进入以下逻辑
AopAutoConfiguration
    # 类路径下是否存在 org.aspectj.weaver.Advice.class; springBoot加入了AOP的功能
	AspectJAutoProxyingConfiguration
        # spring.aop.proxy-target-class = false 使用JDK代理
		JdkDynamicAutoProxyConfiguration
		#  spring.aop.proxy-target-class = true 或者缺失此键 使用Cglib代理
        CglibAutoProxyConfiguration
    
    #缺失 org.aspectj.weaver.Advice.class
	ClassProxyingConfiguration
        
		forceAutoProxyCreatorToUseClassProxying
            使用InfrastructureAdvisorAutoProxyCreator 或者         org.springframework.aop.config.internalAutoProxyCreator

在引入第三方配置时候

其他自动配置类

org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration

org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

#MVC
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration

总结

AopAutoConfiguration

Spring Boot 是利用了自动配置类来简化了 aop 相关配置

  • AOP 自动配置类为 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

  • 可以通过 spring.aop.auto=false 禁用 aop 自动配置

  • AOP 自动配置的本质是通过 @EnableAspectJAutoProxy 来开启了自动代理,如果在引导类上自己添加了 @EnableAspectJAutoProxy 那么以自己添加的为准

  • @EnableAspectJAutoProxy 的本质是向容器中添加了 AnnotationAwareAspectJAutoProxyCreator 这个 bean 后处理器,它能够找到容器中所有切面,并为匹配切点的目标类创建代理,创建代理的工作一般是在 bean 的初始化阶段完成的

 

DataSourceAutoConfiguration

  • 对应的自动配置类为:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

  • 它内部采用了条件装配,通过检查容器的 bean,以及类路径下的 class,来决定该 @Bean 是否生效

简单说明一下,Spring Boot 支持两大类数据源:

  • EmbeddedDatabase - 内嵌数据库连接池

  • PooledDataSource - 非内嵌数据库连接池

PooledDataSource 又支持如下数据源

  • hikari 提供的 HikariDataSource

  • tomcat-jdbc 提供的 DataSource

  • dbcp2 提供的 BasicDataSource

  • oracle 提供的 PoolDataSourceImpl

如果知道数据源的实现类类型,即指定了 spring.datasource.type,理论上可以支持所有数据源,但这样做的一个最大问题是无法订制每种数据源的详细配置(如最大、最小连接数等)

MybatisAutoConfiguration

  • MyBatis 自动配置类为 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

  • 它主要配置了两个 bean

    • SqlSessionFactory - MyBatis 核心对象,用来创建 SqlSession 1

    • SqlSessionTemplate - SqlSession 的实现,此实现会与当前线程绑定 2

    • 用 ImportBeanDefinitionRegistrar 的方式扫描所有标注了 @Mapper 注解的接口 3

    • 用 AutoConfigurationPackages 来确定扫描的包 4

  • 还有一个相关的 bean:MybatisProperties,它会读取配置文件中带 mybatis. 前缀的配置项进行定制配置 5

@MapperScan 注解的作用与 MybatisAutoConfiguration 类似,会注册 MapperScannerConfigurer 有如下区别

  • @MapperScan 扫描具体包(当然也可以配置关注哪个注解)

  • @MapperScan 如果不指定扫描具体包,则会把引导类范围内,所有接口当做 Mapper 接口

  • MybatisAutoConfiguration 关注的是所有标注 @Mapper 注解的接口,会忽略掉非 @Mapper 标注的接口

这里有同学有疑问,之前介绍的都是将具体类交给 Spring 管理,怎么到了 MyBatis 这儿,接口就可以被管理呢?

  • 其实并非将接口交给 Spring 管理,而是每个接口会对应一个 MapperFactoryBean,是后者被 Spring 所管理,接口只是作为 MapperFactoryBean 的一个属性来配置

TransactionAutoConfiguration

  • 事务自动配置类有两个:

    • org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration

    • org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

  • 前者配置了 DataSourceTransactionManager 用来执行事务的提交、回滚操作

  • 后者功能上对标 @EnableTransactionManagement,包含以下三个 bean

    • BeanFactoryTransactionAttributeSourceAdvisor 事务切面类,包含通知和切点

    • TransactionInterceptor 事务通知类,由它在目标方法调用前后加入事务操作

    • AnnotationTransactionAttributeSource 会解析 @Transactional 及事务属性,也包含了切点功能

  • 如果自己配置了 DataSourceTransactionManager 或是在引导类加了 @EnableTransactionManagement,则以自己配置的为准

 

ServletWebServerFactoryAutoConfiguration

  • 提供 ServletWebServerFactory

DispatcherServletAutoConfiguration

  • 提供 DispatcherServlet

  • 提供 DispatcherServletRegistrationBean

WebMvcAutoConfiguration

  • 配置 DispatcherServlet 的各项组件,提供的 bean 见过的有

    • 多项 HandlerMapping

    • 多项 HandlerAdapter

    • HandlerExceptionResolver

ErrorMvcAutoConfiguration

  • 提供的 bean 有 BasicErrorController

MultipartAutoConfiguration

  • 它提供了 org.springframework.web.multipart.support.StandardServletMultipartResolver

  • 该 bean 用来解析 multipart/form-data 格式的数据

HttpEncodingAutoConfiguration

  • POST 请求参数如果有中文,无需特殊设置,这是因为 Spring Boot 已经配置了 org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter

  • 对应配置 server.servlet.encoding.charset=UTF-8,默认就是 UTF-8

  • 当然,它只影响非 json 格式的数据

自动配置类实现步骤

        不能直接使用 @Import(AutoConfigurationImportSelector.class) 通过使用 @EnableAutoConfiguration,或者使用其子类 ImportAutoConfigurationImportSelector

AutoConfigurationImportSelector
AutoConfigurationImportSelector#selectImports -ImportSelector的重要方法
	AutoConfigurationImportSelector#getAutoConfigurationEntry
		AutoConfigurationImportSelector#getCandidateConfigurations
			SpringFactoriesLoader#loadFactoryNames -扫描 META-INF/spring.factories

42) 条件装配底层

        条件装基础注解时@Conditional,其value() 必须是Condition类。Condition类的matches方法可对ConditionContext AnnotatedTypeMetadata 进行判断。

我们可以引入自定义的注解在类上标注@Conditional(自己实现的Condition)参考org.springframework.boot.autoconfigure.condition.ConditionalOnClass

    static class MyCondition implements Condition { // 存在 Druid 依赖
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
            String className = attributes.get("className").toString();
            boolean exists = (boolean) attributes.get("exists");
            boolean present = ClassUtils.isPresent(className, null);
            return exists == present;
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Conditional(MyCondition.class)
    @interface ConditionalOnClass {
        boolean exists(); // true 判断存在 false 判断不存在

        String className(); // 要判断的类名
    }

    @Configuration // 第三方的配置类
    @ConditionalOnClass(className = "com.alibaba.druid.pool.DruidDataSource", exists = false)
    static class AutoConfiguration1 {
        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }
    }

43) FactoryBean

44) @Indexed 原理

45) 代理进一步理解

收获💡

  1. spring 代理的设计特点

    • 依赖注入和初始化影响的是原始对象

      • 因此 cglib 不能用 MethodProxy.invokeSuper()

    • 代理与目标是两个对象,二者成员变量并不共用数据,代理立对象不能直接访问属性,代理的方法内原始对象 属性信息是完整的

  2. static 方法、final 方法、private 方法均无法增强JDK、Cglib

    • 进一步理解代理增强基于方法重写

@Component
public class Bean1 {

    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    protected Bean2 bean2;

    protected boolean initialized;

    @Autowired
    public void setBean2(Bean2 bean2) {
        log.debug("setBean2(Bean2 bean2)");
        this.bean2 = bean2;
    }

    @PostConstruct
    public void init() {
        log.debug("init");
        initialized = true;
    }

    public Bean2 getBean2() {
        log.debug("getBean2()");
        return bean2;
    }

    public boolean isInitialized() {
        log.debug("isInitialized()");
        return initialized;
    }

    public void m1() {
        System.out.println("m1() 成员方法");
    }

    final public void m2() {
        System.out.println("m2() final 方法");
    }

    static public void m3() {
        System.out.println("m3() static 方法");
    }

    private void m4() {
        System.out.println("m4() private 方法");
    }

}


@SpringBootApplication
public class A45 {
    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = SpringApplication.run(A45.class, args);
        displayAllBeans(context);
        Bean1 proxy = context.getBean(Bean1.class);



        /*
            1.演示 spring 代理的设计特点
                依赖注入和初始化影响的是原始对象
                代理与目标是两个对象,二者成员变量并不共用数据
         */
        showProxyAndTarget(proxy);

        System.out.println(">>>>>>>>>>>>>>>>>>>");
        System.out.println(proxy.getBean2());
        System.out.println(proxy.isInitialized());

        /*
            2.演示 static 方法、final 方法、private 方法均无法增强
         */

        proxy.m1();
        proxy.m2();
        Bean1.m3();
        Method m4 = Bean1.class.getDeclaredMethod("m1");
        m4.setAccessible(true);
        m4.invoke(proxy);

        context.close();
    }


    public static void showProxyAndTarget(Bean1 proxy) throws Exception {
        System.out.println(">>>>> 代理中的成员变量");
        System.out.println("\tinitialized=" + proxy.initialized);
        System.out.println("\tbean2=" + proxy.bean2);

        if (proxy instanceof Advised) {
            System.out.println(">>>>> 目标中的成员变量");
            Bean1 target = (Bean1) ((Advised) proxy).getTargetSource().getTarget();
            System.out.println("\tinitialized=" + target.initialized);
            System.out.println("\tbean2=" + target.bean2);
        }
    }

}

46) @Value 装配底层

  1. 如果需要的值是字符串,先解析 ${ },再解析 #{ }

  2. 不是字符串,需要用 TypeConverter 转换

ContextAnnotationAutowireCandidateResolver 获取标注了@Value的内容(org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#getSuggestedValue),先通过Environment 解析#{}占位符(org.springframework.core.env.PropertyResolver#resolvePlaceholders),再通过BeanExpressionResolver计算(org.springframework.beans.factory.config.BeanExpressionResolver#evaluate)。
    public class Bean2 {
        @Value("#{@bean3}") // SpringEL       #{SpEL}
        private Bean3 bean3;
    }

    @Component("bean3")
    public class Bean3 {
    }

    static class Bean4 {
        @Value("#{'hello, ' + '${JAVA_HOME}'}")
        private String value;
    }

/
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A46.class);
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();

        ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();
        resolver.setBeanFactory(beanFactory);

        test1(context, resolver, Bean1.class.getDeclaredField("home"));
        test2(context, resolver, Bean1.class.getDeclaredField("age"));
        test3(context, resolver, Bean2.class.getDeclaredField("bean3"));
        test3(context, resolver, Bean4.class.getDeclaredField("value"));
    }

    private static void test3(AnnotationConfigApplicationContext context, ContextAnnotationAutowireCandidateResolver resolver, Field field) {
        DependencyDescriptor dd1 = new DependencyDescriptor(field, false);
        // 获取 @Value 的内容
        String value = resolver.getSuggestedValue(dd1).toString();
        System.out.println(value);

        // 解析 ${}
        value = context.getEnvironment().resolvePlaceholders(value);
        System.out.println(value);
        System.out.println(value.getClass());

        // 解析 #{} @bean3
        Object bean3 = context.getBeanFactory().getBeanExpressionResolver().evaluate(value, new BeanExpressionContext(context.getBeanFactory(), null));

        // 类型转换
        Object result = context.getBeanFactory().getTypeConverter().convertIfNecessary(bean3, dd1.getDependencyType());
        System.out.println(result);
    }

47) @Autowired 装配底层

1. @Autowired 本质上是根据成员变量或方法参数的类型进行装配
2. 如果待装配类型是 Optional,需要根据 Optional 泛型找到 bean,再封装为 Optional 对象装配
3. 如果待装配的类型是 ObjectFactory,需要根据 ObjectFactory 泛型创建 ObjectFactory 对象装配
   * 此方法可以延迟 bean 的获取
4. 如果待装配的成员变量或方法参数上用 @Lazy 标注,会创建代理对象装配
   * 此方法可以延迟真实 bean 的获取
   * 被装配的代理不作为 bean
5. 如果待装配类型是数组,需要获取数组元素类型,根据此类型找到多个 bean 进行装配
6. 如果待装配类型是 Collection 或其子接口,需要获取 Collection 泛型,根据此类型找到多个 bean
7. 如果待装配类型是 ApplicationContext 等特殊类型
   * 会在 BeanFactory 的 resolvableDependencies 成员按类型查找装配
   * resolvableDependencies 是 map 集合,key 是特殊类型,value 是其对应对象
   * 不能直接根据 key 进行查找,而是用 isAssignableFrom 逐一尝试右边类型是否可以被赋值给左边的 key 类型
8. 如果待装配类型有泛型参数
   * 需要利用 ContextAnnotationAutowireCandidateResolver 按泛型参数类型筛选
9. 如果待装配类型有 @Qualifier
   * 需要利用 ContextAnnotationAutowireCandidateResolver 按注解提供的 bean 名称筛选
10. 有 @Primary 标注的 @Component 或 @Bean 的处理
11. 与成员变量名或方法参数名同名 bean 的处理

 1-4

@Configuration
public class A47_1 {
    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A47_1.class);
        // 1. 根据成员变量的类型注入
        DependencyDescriptor dd1 = new DependencyDescriptor(Bean1.class.getDeclaredField("bean2"), false);
        beanFactory.doResolveDependency(dd1, "bean1", null, null);
        // 2. 根据参数的类型注入
        Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
        DependencyDescriptor dd2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), false);
        beanFactory.doResolveDependency(dd2, "bean1", null, null);

        // 3. 结果包装为 Optional<Bean2>
        DependencyDescriptor dd3 = new DependencyDescriptor(Bean1.class.getDeclaredField("bean3"), false);
        if (dd3.getDependencyType() == Optional.class) {
            dd3.increaseNestingLevel();
            Object result = beanFactory.doResolveDependency(dd3, "bean1", null, null);
            Optional.ofNullable(result);
        }

        // 4. 结果包装为 ObjectProvider,ObjectFactory
        DependencyDescriptor dd4 = new DependencyDescriptor(Bean1.class.getDeclaredField("bean4"), false);
        if (dd4.getDependencyType() == ObjectFactory.class) {
            dd4.increaseNestingLevel();
            ObjectFactory objectFactory = new ObjectFactory() {
                @Override
                public Object getObject() throws BeansException {
                    return beanFactory.doResolveDependency(dd4, "bean1", null, null);
                }
            };
            // 需要的时候才去加载
            objectFactory.getObject();
        }

        // 5. 对 @Lazy 的处理
        DependencyDescriptor dd5 = new DependencyDescriptor(Bean1.class.getDeclaredField("bean2"), false);
        ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();
        resolver.setBeanFactory(beanFactory);
        Object proxy = resolver.getLazyResolutionProxyIfNecessary(dd5, "bean1");
        // 返回的是代理对象
        System.out.println(proxy);
        System.out.println(proxy.getClass());

 通过org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency 可获取依赖注入的Bean,

 对@Autowired 、@Qualifier、@Value的解析

5-9

mport org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@SuppressWarnings("all")
@Configuration
public class A47_2 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A47_2.class);
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 1. 数组类型");
        testArray(beanFactory);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 2. List 类型");
        testList(beanFactory);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 3. applicationContext");
        testApplicationContext(beanFactory);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 4. 泛型");
        testGeneric(beanFactory);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 5. @Qualifier");
        testQualifier(beanFactory);
        /*
            学到了什么
                1. 如何获取数组元素类型
                2. Spring 如何获取泛型中的类型
                3. 特殊对象的处理, 如 ApplicationContext, 并注意 Map 取值时的类型匹配问题 (另见  TestMap)
                4. 谁来进行泛型匹配 (另见 TestGeneric)
                5. 谁来处理 @Qualifier
                6. 刚开始都只是按名字处理, 等候选者确定了, 才会创建实例
         */
    }

    private static void testQualifier(DefaultListableBeanFactory beanFactory) throws NoSuchFieldException {
        DependencyDescriptor dd5 = new DependencyDescriptor(Target.class.getDeclaredField("service"), true);
        Class<?> type = dd5.getDependencyType();
        ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();
        resolver.setBeanFactory(beanFactory);
        for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) {
            BeanDefinition bd = beanFactory.getMergedBeanDefinition(name);
            //                                                             @Qualifier("service2")
            if (resolver.isAutowireCandidate(new BeanDefinitionHolder(bd,name), dd5)) {
                System.out.println(name);
                System.out.println(dd5.resolveCandidate(name, type, beanFactory));
            }
        }
    }
    private static void testGeneric(DefaultListableBeanFactory beanFactory) throws NoSuchFieldException {
        DependencyDescriptor dd4 = new DependencyDescriptor(Target.class.getDeclaredField("dao"), true);
        Class<?> type = dd4.getDependencyType();
        ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();
        resolver.setBeanFactory(beanFactory);
        for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) {
            BeanDefinition bd = beanFactory.getMergedBeanDefinition(name);
            // 对比 BeanDefinition 与 DependencyDescriptor 的泛型是否匹配
            if (resolver.isAutowireCandidate(new BeanDefinitionHolder(bd,name), dd4)) {
                System.out.println(name);
                System.out.println(dd4.resolveCandidate(name, type, beanFactory));
            }
        }
    }
    private static void testApplicationContext(DefaultListableBeanFactory beanFactory) throws NoSuchFieldException, IllegalAccessException {
        DependencyDescriptor dd3 = new DependencyDescriptor(Target.class.getDeclaredField("applicationContext"), true);

        Field resolvableDependencies = DefaultListableBeanFactory.class.getDeclaredField("resolvableDependencies");
        resolvableDependencies.setAccessible(true);
        Map<Class<?>, Object> dependencies = (Map<Class<?>, Object>) resolvableDependencies.get(beanFactory);
//        dependencies.forEach((k, v) -> {
//            System.out.println("key:" + k + " value: " + v);
//        });
        for (Map.Entry<Class<?>, Object> entry : dependencies.entrySet()) {
            // 左边类型                      右边类型
            if (entry.getKey().isAssignableFrom(dd3.getDependencyType())) {
                System.out.println(entry.getValue());
                break;
            }
        }
    }
    private static void testList(DefaultListableBeanFactory beanFactory) throws NoSuchFieldException {
        DependencyDescriptor dd2 = new DependencyDescriptor(Target.class.getDeclaredField("serviceList"), true);
        if (dd2.getDependencyType() == List.class) {
            Class<?> resolve = dd2.getResolvableType().getGeneric().resolve();
            System.out.println(resolve);
            List<Object> list = new ArrayList<>();
            // 在指定工厂
            String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, resolve);
            for (String name : names) {
                // 通过Bean的名称获取Bean 等同于 beanFactory.getBean();
                Object bean = dd2.resolveCandidate(name, resolve, beanFactory);
                list.add(bean);
            }
            System.out.println(list);
        }
    }
    private static void testArray(DefaultListableBeanFactory beanFactory) throws NoSuchFieldException {
        DependencyDescriptor dd1 = new DependencyDescriptor(Target.class.getDeclaredField("serviceArray"), true);
        if (dd1.getDependencyType().isArray()) {
            Class<?> componentType = dd1.getDependencyType().getComponentType();
            System.out.println(componentType);
            String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, componentType);
            List<Object> beans = new ArrayList<>();
            for (String name : names) {
                System.out.println(name);
                Object bean = dd1.resolveCandidate(name, componentType, beanFactory);
                beans.add(bean);
            }
            // array 与List 仅差一步类型转换 TypeConverter(高级转换接口)
            Object array = beanFactory.getTypeConverter().convertIfNecessary(beans, dd1.getDependencyType());
            System.out.println(array);
        }
    }
    static class Target {
        @Autowired private Service[] serviceArray;
        @Autowired private List<Service> serviceList;
        @Autowired private ConfigurableApplicationContext applicationContext;
        @Autowired private Dao<Teacher> dao;
        @Autowired @Qualifier("service2") private Service service;
    }
    interface Dao<T> {

    }
    @Component("dao1") static class Dao1 implements Dao<Student> {
    }
    @Component("dao2") static class Dao2 implements Dao<Teacher> {
    }

    static class Student {

    }

    static class Teacher {

    }

    interface Service {

    }

    @Component("service1")
    static class Service1 implements Service {

    }

    @Component("service2")
    static class Service2 implements Service {

    }

    @Component("service3")
    static class Service3 implements Service {

    }
}

@Autowired解析 数组类型、List类型需要通过DependencyDescriptor 进一步确定集合中的类型再从BeanFactory中查找该类型的Bean,并放在对应的数据结构中。

 解析 ConfigurableApplicationContext 类型的依赖,该接口下属的实现常用类为 DefaultListableBeanFactory 其属性 

resolvableDependencies 在org.springframework.context.support.AbstractApplicationContext#refresh 的org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory中添加了ApplicationContext。

我们通过反射获取DefaultListableBeanFactory 的resolvableDependencies 遍历确定具有相同接口即可

对于标注有 @Qualifier、或者是具有泛型的接口,可通过org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#isAutowireCandidate 判断。

参数一 BeanDefinitionHolder 需要一个BeanDefinition和beanName beanName可以通过 org.springframework.beans.factory.BeanFactoryUtils#beanNamesForTypeIncludingAncestors(org.springframework.beans.factory.ListableBeanFactory, java.lang.Class<?>) 传入的 dd.getDependencyType() 确定后返回  beanName。

@Autowired private Dao<Teacher> dao;
@Autowired @Qualifier("service2") private Service service;

@Qualifier 检查过后会再检查 @Primary,如果获取的仍不满足 就会以注入的name进行查找。

beanFactory.getMergedBeanDefinition(name).isPrimary() name.equals(dd.getDependencyName())
    private static void testPrimary(DefaultListableBeanFactory beanFactory) throws NoSuchFieldException {
        DependencyDescriptor dd = new DependencyDescriptor(Target1.class.getDeclaredField("service"), false);
        Class<?> type = dd.getDependencyType();
        for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) {
            if (beanFactory.getMergedBeanDefinition(name).isPrimary()) {
                System.out.println(name);
            }
            if(name.equals(dd.getDependencyName())) {
                System.out.println(name);
            }
        }
    }

48) 事件监听器 49) 事件发布器

重要接口 org.springframework.context.ApplicationListener  org.springframework.context.ApplicationEventPublisher | ApplicationEventPublisher的主要实现 AbstractApplicationContext

 http://t.csdn.cn/8CzkOhttp://t.csdn.cn/8CzkO

自定义一个能够将自定义的注解方法注册为ApplicationListener。

org.springframework.beans.factory.SmartInitializingSingleton在BeanFactory引导期间,在单例初始化阶段结束时触发的回调接口。

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
           org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated AbstractApplicationContext.finishBeanFactoryInitialization(ConfigurableListableBeanFactory)  (org.springframework.context.support)
    AbstractApplicationContext.refresh()  (org.springframework.context.support)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

@Configuration
public class A48_3 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A48_3.class);
        context.getBean(MyService.class).doBusiness();
        context.close();
    }

    /**
     *
     * @param context SmartInitializingSingleton 需要一个beanFactory 同时需要支持addApplicationListener 方法
     * @return
     */
    @Bean
    public SmartInitializingSingleton smartInitializingSingleton(ConfigurableApplicationContext context) {
        return () -> {
            for (String name : context.getBeanDefinitionNames()) {
                Object bean = context.getBean(name);
                for (Method method : bean.getClass().getMethods()) {
                    // 判断方法上是否标注的有自定义的注解 @MyListener
                    if (method.isAnnotationPresent(MyListener.class)) {
                        context.addApplicationListener((event) -> {
                            System.out.println(event);
                            Class<?> eventType = method.getParameterTypes()[0];// 监听器方法需要的事件类型
                            if (eventType.isAssignableFrom(event.getClass())) {
                                try {
                                    // 此处通过反射执行方式时候的形参并不是方法上的参数 method.getParameters()[0]
                                    method.invoke(bean, event);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                    }
                }
            }
        };
    }

    @Component
    static class MyService {
        private static final Logger log = LoggerFactory.getLogger(MyService.class);
        @Autowired
        private ApplicationEventPublisher publisher; // applicationContext

        public void doBusiness() {
            log.debug("主线业务");
            // 主线业务完成后需要做一些支线业务,下面是问题代码
            publisher.publishEvent(new MyEvent("MyService.doBusiness()"));
        }
    }

    @Component
    static class SmsService {
        private static final Logger log = LoggerFactory.getLogger(SmsService.class);

        @MyListener
        public void listener(MyEvent myEvent) {
            log.debug("发送短信");
        }
    }

    @Component
    static class EmailService {
        private static final Logger log = LoggerFactory.getLogger(EmailService.class);

        @MyListener
        public void listener(MyEvent myEvent) {
            log.debug("发送邮件");
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @interface MyListener {
    }

    static class MyEvent extends ApplicationEvent {
        public MyEvent(Object source) {
            super(source);
        }
    }
}

双亲委派模式 - 简书 (jianshu.com)icon-default.png?t=M85Bhttps://www.jianshu.com/p/5e0441cd2d4c 

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

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

相关文章

南京溧水农民丰收节 国稻种芯·中国水稻节:江苏味稻文化

南京溧水农民丰收节 国稻种芯中国水稻节&#xff1a;江苏味稻文化 &#xff08;融媒体记者 诸婧雯&#xff09;新闻中国采编网 中国新闻采编网 谋定研究中国智库网 国稻种芯中国水稻节 中国三农智库网-功能性农业农业大健康大会报道&#xff1a;由溧水区政府、市农业农村局主办…

MCE | Hippo 途径与靶向策略

在 PubMed 输入了“Hippo pathway or YAP/TAZ”&#xff0c;小编发现近十年来与 Hippo 通路沾点边的研究势头猛烈&#xff0c;且发的文章不少都“非富即贵”&#xff0c;如发表在 Nature Cell Biology 上的两篇关于 YAP (TAZ) 相变的文章 (两篇结论相反的文章&#xff0c;还能双…

红黑树C++实现

目录 一、红黑树的概念 二、红黑树的性质 三、红黑树节点的定义 四、红黑树的插入 4.1 插入节点 4.2 插入节点的颜色 4.3 调整情况1 4.4 调整情况2 4.5 调整情况3 4.6 调整情况总结 五、调整的实现 5.1 调整的步骤分析 5.2 代码实现 六、树的平衡判断 七、源代码…

微信小程序制作要多少钱?【制作小程序】

关于微信小程序制作要多少钱的问题&#xff0c;是很多企业商家在制作小程序之前需要了解的事项&#xff0c;因为总是听说制作小程序的费用有高有低&#xff0c;而他们又对这方面不太了解&#xff0c;所以也还是需要了解微信小程序制作要多少钱的。那么微信小程序制作要多少钱呢…

RocketMQ中生产者发消息前为啥一定要调用start()方法?

前言 我们在使用RocketMQ发送消息时&#xff0c;一般都会使用DefaultMQProducer&#xff0c;类型的代码如下&#xff1a; DefaultMQProducer producer new DefaultMQProducer("producer_group"); producer.setNamesrvAddr("42.192.50.8:9876"); try {pr…

Chrome 103支持使用本地字体,纯前端导出PDF优化

在前端导出PDF&#xff0c;解决中文乱码一直是一个头疼的问题。要解决这个问题&#xff0c;需要将ttf等字体文件内容注册到页面PDF生成器中。但是之前网页是没有权限直接获取客户机器字体文件&#xff0c;这时就需要从服务器下载字体文件或者提示用户选择字体文件上传到页面。对…

链接杂谈 CASPP

构建大型程序 构建大型程序&#xff0c;不可避免的一个问题是链接问题&#xff1a; - 链接器提示&#xff1a;缺少某个模块 缺少某个库 不兼容的库版本 理解全局变量的链接 你的代码可能有多个全局变量&#xff0c;有些是强变量&#xff0c;有些是弱定义&#xff0c;执行…

清除浮动的常用方法

关于浮动 我们为什么需要浮动&#xff1f; 我们想把多个块级元素放到同一行上。 打破标准流的限制。 浮动原来做图文混排效果&#xff0c;现在主要用来做网页布局的。 浮动语法 只有左浮动和右浮动。 float: left; float: right;浮动特点 1.浮动元素会脱离标准流&#x…

Win10禁止应用独占麦克风

痛点需求&#xff1a; qq和微信同时发起语音通话&#xff0c;发现只有一个qq说话对方能听到&#xff0c;但是微信却不能&#xff0c;这是典型的应用程序独占了麦克风&#xff0c;导致其他应用无法使用。 有没有办法让qq和微信同时使用麦克风呢&#xff1f; 答案是&#xff1a;有…

图的拓扑序列

拓扑序列&#xff1a; 拓扑序是按照点的先后顺序排列的。拓扑序列满足以下两点&#xff1a; 1.每个顶点在序列中出现且只出现一次。 2.若存在一条从顶点 A 到顶点 B 的路径&#xff0c;那么在序列中顶点 A 出现在顶点 B 的前面。 拓扑序列只存在于有向无环图中。可以理解成…

MCE | 肝炎病毒是如何诱发肝癌的

肝炎病毒分类 肝炎病毒是世界上最常见的肝炎病因&#xff0c;其它原因包括酗酒、某些药物、毒素、其他感染、自身免疫性疾病和非酒精性脂肪性肝炎 (NASH)。肝炎病毒共有五种主要的肝炎病毒株&#xff0c;分别为 A、B、C、D 和 E 型。目前&#xff0c;全世界大约有 3.25 亿人患…

2023中国绿色铝业国际峰会

会议背景 铝行业属于能源高度密集型行业&#xff0c;主要包括铝矿石开采、氧化铝生产、电解铝生产和铝材加工等环节。我国原铝产量自2001年以来一直占据世界首位&#xff0c;连续7年产量占比超过全球50%。然而与国际先进铝生产企业相比&#xff0c;我国铝生产企业单位原铝碳…

C# 自定义事件

一 自定义事件 例如&#xff0c;利用自定义绘制的技术&#xff0c;画出一个圆角按钮。 现在来看&#xff0c;怎么样给它添加自定义的事件。 二 要点与细节 1 Control 类本身就有继承的鼠标和键盘事件&#xff0c;这里只是一个引子&#xff0c;用于引出更复杂的自定义事件。 …

web测试——业务测试2

1.历史数据 前端&#xff1a; 组件相关  组件内部是否动过&#xff1b;  展示的数据是否受影响&#xff1b;  失焦后的校验(爆红) 页面样式相关  坐标位置、  按钮位置是否动过&#xff0c;  新版本上线对历史配置的影响 交互提示相关  新手引导的展示位置、关闭后的展…

pycharm2022.2 远程连接服务器调试代码

目的&#xff1a; 同步本地和服务器的全部或者部分文件本地debug&#xff0c;服务器跑实验 需要条件&#xff1a; 服务器上已经创建好虚拟环境你本地已经安装好pycharm 1.1 File → Settings → Project:XXX →Python Interpreter 打开之后再右边这添加解释器。选On SSH 1.2把…

简单的网页制作期末作业——电影泰坦尼克号(4页)

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

小学生python游戏编程arcade----excel调用

小学生python游戏编程arcade----excel调用前言小学生python游戏编程arcade----excel调用1、excel文件1.1 excel表头1.2 excel文件1.3 文件读取函数1.4 打开excel文件读取数据,每行一个字典&#xff0c;再总存为序列1.5 打开excel文件读取数据&#xff0c;取两列存为字典1.6 游戏…

[漏洞复现] jenkins 远程代码执行 (CVE-2019-100300)

文章目录一、简介二、影响版本三、复现四、修复一、简介 拥有Overall/Read 权限的用户可以绕过沙盒保护&#xff0c;在jenkins可以执行任意代码。此漏洞需要一个账号密码和一个存在的job。 Jenkins的pipeline主要是通过一个配置文件或者job里面的pipeline脚本配置来设定每个j…

锐捷MPLS跨域方案C2实验配置

目录 配置ASBR之间的EBGP邻居 配置PE之间的Vpnv4邻居 此时配置PE与CE设备对接命令 手工配置为PE地址分配标签 MPLS隧道——跨域解决方案C1、C2讲解_静下心来敲木鱼的博客-CSDN博客https://blog.csdn.net/m0_49864110/article/details/127634890?ops_request_misc%257B%252…

数字孪生|交通运输可视化系统

交通是城市经济发展的动脉&#xff0c;与我们的日常生活息息相关。 传统交通信息管理中&#xff0c;只是做了粗略的信息发布以及简单的交通流量监测&#xff0c;早已经不能满足现代智慧交通的需求。现代的智慧交通则提供基于实时交通数据的交通信息服务&#xff0c;融入了物联网…