Spring源码-3.Bean的后置处理器

news2024/11/16 17:57:59

Bean后置处理器与注解的关系

首先以一个没有添加额外的后置处理器来说明:️GenericApplicationContext

GenericApplicationContext context = new GenericApplicationContext();

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

    private Bean2 bean2;

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

    @Autowired
    private Bean3 bean3;

    @Resource
    public void setBean3(Bean3 bean3) {
        log.debug("@Resource 生效: {}", bean3);
        this.bean3 = bean3;
    }

    private String home;

    @Autowired
    public void setHome(@Value("${JAVA_HOME}") String home) {
        log.debug("@Value 生效: {}", home);
        this.home = home;
    }

    @PostConstruct
    public void init() {
        log.debug("@PostConstruct 生效");
    }

    @PreDestroy
    public void destroy() {
        log.debug("@PreDestroy 生效");
    }

    @Override
    public String toString() {
        return "Bean1{" +
               "bean2=" + bean2 +
               ", bean3=" + bean3 +
               ", home='" + home + '\'' +
               '}';
    }
}

public class Bean2 {
}

public class Bean3 {
}

// ⬇️用原始方法注册三个 bean
context.registerBean("bean1", Bean1.class);
context.registerBean("bean2", Bean2.class);
context.registerBean("bean3", Bean3.class);

context.refresh(); // 执行beanFactory后处理器, 添加bean后处理器, 初始化所有单例

运行发现,bean1 bean2 bean3 并没有被注册成功!

通过分析原因是因为 bean1 中得 @Autowired 和 @ value并没有生效,那么接下来启用它

// 自动装配候选者的解析器
        context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class); // @Autowired @Value

此时 bean 中的 @Autowired 和 @Value 就被注入了

[DEBUG] 21:07:55.642 [main] com.itheima.a04.Bean1               - @Value 生效: C:\Program Files\Java\jdk1.8.0_231 
[DEBUG] 21:07:55.647 [main] com.itheima.a04.Bean1               - @Autowired 生效: com.itheima.a04.Bean2@120f102b 

而如果想要@Resource @PostConstruct @PreDestroy 也被注入,需要 启动这个

context.registerBean(CommonAnnotationBeanPostProcessor.class); // @Resource @PostConstruct @PreDestroy

效果如下:

[DEBUG] 21:12:03.082 [main] com.itheima.a04.Bean1               - @Resource 生效: com.itheima.a04.Bean3@37858383 
[DEBUG] 21:12:03.093 [main] com.itheima.a04.Bean1               - @Autowired 生效: com.itheima.a04.Bean2@59af0466 
[DEBUG] 21:12:03.102 [main] com.itheima.a04.Bean1               - @Value 生效: C:\Program Files\Java\jdk1.8.0_231 
[DEBUG] 21:12:03.102 [main] com.itheima.a04.Bean1               - @PostConstruct 生效 
[DEBUG] 21:12:03.186 [main] com.itheima.a04.Bean1               - @PreDestroy 生效 

通过上述其实可以看到, @Autowired 等注解的解析 属于 bean 生命周期阶段 (依赖注入,初始化)的扩展功能,而这些扩展功能由bean的后处理器完成。

并且仔细观察输出的结果可以发现,先 @Resource 后 @Autowired,这是其源码的执行顺序有关,其@Resource逻辑判断在@Autowired之前。

以上是加载一些 普通类,但是对于加载配置类 其实还是不行的

@ConfigurationProperties(prefix = "java")
public class Bean4 {

    private String home;

    private String version;

    public String getHome() {
        return home;
    }

    public void setHome(String home) {
        this.home = home;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    @Override
    public String toString() {
        return "Bean4{" +
               "home='" + home + '\'' +
               ", version='" + version + '\'' +
               '}';
    }
}

context.registerBean("bean4", Bean4.class);
System.out.println(context.getBean(Bean4.class));

发现其配置类中并没有成功加载上对应的home 和 version

Bean4{home='null', version='null'}

其实在这里还是需要添加一个新的后置处理器:

ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());

其实到这里,本质就是说明了常用注解 与 后置处理器之间的关系,需要加入对应的后置处理器,才会让这些注解生效。

详解Bean后置处理器生效的过程

还是以上面的代码举例:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        beanFactory.registerSingleton("bean2", new Bean2()); // 创建过程,依赖注入,初始化
        beanFactory.registerSingleton("bean3", new Bean3());
        beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); // @Value

实际上刚才让 @Autowired 和 @Value 起作用的是AutowiredAnnotationBeanPostProcessor.class

那么接下来就进入内部来看看具体的实现流程吧。

// 查找哪些属性、方法加了 @Autowired, 这称之为 InjectionMetadata
AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
        processor.setBeanFactory(beanFactory);

        // 自己创建的,依赖注入等都不会生效
        Bean1 bean1 = new Bean1();
        System.out.println(bean1);
        processor.postProcessProperties(null, bean1, "bean1"); // 执行依赖注入 @Autowired @Value
        System.out.println(bean1);

输出:

Bean1{bean2=null, bean3=null, home='null'}
[DEBUG] 21:32:35.864 [main] com.itheima.a04.Bean1               - @Value 生效: ${JAVA_HOME} 
[DEBUG] 21:32:35.873 [main] com.itheima.a04.Bean1               - @Autowired 生效: com.itheima.a04.Bean2@4562e04d 
Bean1{bean2=com.itheima.a04.Bean2@4562e04d, bean3=com.itheima.a04.Bean3@2a65fe7c, home='${JAVA_HOME}'}

其实可以发现,当processor.postProcessProperties(null, bean1, “bean1”); 之后,实际上bean1已经被注入了

而后进入到postProcessProperties方法查看

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
        InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass(), pvs);

        try {
            metadata.inject(bean, beanName, pvs);
            return pvs;
        } catch (BeanCreationException var6) {
            throw var6;
        } catch (Throwable var7) {
            throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", var7);
        }
    }

可以看到,发现发现findAutowiringMetadata 其实也就是那些属性,那些方法参数上由Autowired注解,找到以后,封装到InjectionMetadata中,然后Inject进去。

进入findAutowiringMetadata里面查看发现:

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
        String cacheKey = StringUtils.hasLength(beanName) ? beanName : clazz.getName();
        InjectionMetadata metadata = (InjectionMetadata)this.injectionMetadataCache.get(cacheKey);
        if (InjectionMetadata.needsRefresh(metadata, clazz)) {
            synchronized(this.injectionMetadataCache) {
                metadata = (InjectionMetadata)this.injectionMetadataCache.get(cacheKey);
                if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                    if (metadata != null) {
                        metadata.clear(pvs);
                    }

                    metadata = this.buildAutowiringMetadata(clazz);
                    this.injectionMetadataCache.put(cacheKey, metadata);
                }
            }
        }

        return metadata;
    }

其是private,那么我们首先用反射的方式获取到这个方法:

// 拿到私有方法
        Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
        findAutowiringMetadata.setAccessible(true);
// 这行代码其实就是去执行了findAutowiringMetadata,分析bean1这个类中,有那个有autowire修饰 解析器收集起来
        InjectionMetadata metadata = (InjectionMetadata) findAutowiringMetadata.invoke(processor, "bean1", Bean1.class, null);// 获取 Bean1 上加了 @Value @Autowired 的成员变量,方法参数信息
// 由于有些方法并没有重写toString,所以需要加 断点来测试
        System.out.println(metadata);

断点如图所示:

在这里插入图片描述

此时其实就是找到了所有的@Autowired

此时调用 InjectionMetadata 来进行依赖注入, 注入时按类型查找值

metadata.inject(bean1, "bean1", null);
System.out.println(bean1);

而inject做了什么呢?首先通过反射获取到成员变量或者方法

Field bean3 = Bean1.class.getDeclaredField("bean3");
DependencyDescriptor dd1 = new DependencyDescriptor(bean3, false);
// 根据成员变量的信息得到类型
Object o = beanFactory.doResolveDependency(dd1, null, null, null);
System.out.println(o);

这段代码其实也就是inject的本质了,首先通过反射API来获取Bean1类中名为“bean3”的成员变量的Field对象,然后Spring内部封装了一个DependencyDescriptor 对象来描述bean3成员变量的依赖,最后,会调用doResolveDependency()方法来解析 bean3的依赖,这意味着Spring会尝试查找并注入名为“bean3”的成员变量所需的Bean,然后打印返回值,这主要是 “bean3”的依赖,也就是所需要的 Bean的实例。

而@Autowired的方法也大同小异:

Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
DependencyDescriptor dd2 =
                new DependencyDescriptor(new MethodParameter(setBean2, 0), true);
Object o1 = beanFactory.doResolveDependency(dd2, null, null, null);
System.out.println(o1);

Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class);
DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome, 0), true);
Object o2 = beanFactory.doResolveDependency(dd3, null, null, null);
System.out.println(o2);

BeanFactory后置处理器与注解关系

还是以一个例子来讲解吧

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);

@Configuration
@ComponentScan("com.itheima.a05.component")
public class Config {
    @Bean
    public Bean1 bean1() {
        return new Bean1();
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean(initMethod = "init")
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }
}

public class Bean1 {

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

    public Bean1() {
        log.debug("我被 Spring 管理啦");
    }
}

context.refresh();

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

实际上输出只会打印出来config,并没有其他的,结合Bean后置处理器与注解的关系,我们可以轻易的想到,一定是缺少了什么后置处理器,所以导致其他的输出不出来。

context.registerBean(ConfigurationClassPostProcessor.class); // @ComponentScan @Bean @Import @ImportResource

发现输出结果和我们预期的是一致的:

[DEBUG] 22:07:04.915 [main] com.itheima.a05.component.Bean2     - 我被 Spring 管理啦 
[DEBUG] 22:07:04.919 [main] com.itheima.a05.component.Bean3     - 我被 Spring 管理啦 
[DEBUG] 22:07:04.928 [main] com.itheima.a05.Bean1               - 我被 Spring 管理啦 
[INFO ] 22:07:05.039 [main] c.a.druid.pool.DruidDataSource      - {dataSource-1} inited 
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
bean2
bean3
bean1
sqlSessionFactoryBean
dataSource
[INFO ] 22:07:05.107 [main] c.a.druid.pool.DruidDataSource      - {dataSource-1} closing ... 
[INFO ] 22:07:05.108 [main] c.a.druid.pool.DruidDataSource      - {dataSource-1} closed 

以上其实就将我们的beanFactory 配置好了,config里面的配置都被注册成了bean,用过Mybatis的都知道,最终我们使用的是Mapper接口,如果我们配置了Mapper接口,那么是否会被注册进来呢?

@Mapper
public interface Mapper1 {
}

@Mapper
public interface Mapper2 {
}

测试发现并没有,所以还需要配置另一种后置处理器

context.registerBean(MapperScannerConfigurer.class, bd -> { // @MapperScanner  自动配置间接的用到了这个
            bd.getPropertyValues().add("basePackage", "com.itheima.a05.mapper");
        });

此时在结果中可以看到,mapper1 mapper2都被注册进来了。

通过上文可以看到@ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能,这些扩展功能由不同的 BeanFactory 后处理器来完成, 其实主要就是补充了一些 bean 定义。

详解BeanFactory后置处理器生效的过程

@ComponentScan

public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override // context.refresh 这个方法在所有的bean定义都已经被加载并且bean实例化之前调用。它允许你在实例化之前对bean工厂进行后置处理操作
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
    // 模拟解析
    @Override // 这个方法在所有的bean定义被加载,但是还未实例化之前调用。它允许你对注册的bean定义进行修改、添加或删除。
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        try {
            // 扫描Config类里面的ComponentScan注解
            ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
            if (componentScan != null) {
                // 便利包结构
                for (String p : componentScan.basePackages()) {
                    System.out.println(p);
                    // com.itheima.a05.component -> classpath*:com/itheima/a05/component/**/*.class
                    String path = "classpath*:" + p.replace(".", "/") + "/**/*.class";
                    System.out.println(path);
                    // 读取类的原信息
                    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                    // 根据指定的路径来获取所有符合条件的资源文件
                    Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
                    // 用于生成Bean名称的策略类
                    AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                    for (Resource resource : resources) {
                        // System.out.println(resource);
                        // 读取每一个class
                        MetadataReader reader = factory.getMetadataReader(resource);
                        // System.out.println("类名:" + reader.getClassMetadata().getClassName());
                        // 得到类上的注解信息
                        AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
                        // System.out.println("是否加了 @Component:" + annotationMetadata.hasAnnotation(Component.class.getName()));
                        // System.out.println("是否加了 @Component 派生:" + annotationMetadata.hasMetaAnnotation(Component.class.getName()));
                        if (annotationMetadata.hasAnnotation(Component.class.getName())
                            || annotationMetadata.hasMetaAnnotation(Component.class.getName())) {

                            /**
                             创建一个通用的Bean定义实例,该实例基于提供的类名(reader.getClassMetadata().getClassName())创建。
                             具体来说,它会为给定的类名创建一个RootBeanDefinition实例,该实例包含了类的元数据信息。
                             */

                            AbstractBeanDefinition bd = BeanDefinitionBuilder
                                    .genericBeanDefinition(reader.getClassMetadata().getClassName())
                                    .getBeanDefinition();

                            /**
                             然后,使用generateBeanName()方法生成一个唯一的Bean名称。这个方法会根据给定的Bean定义
                             和Bean工厂的规则来生成一个合适的名称。生成的名称通常会基于类名,并在名称冲突时添加序号以保证唯一性。
                             */

                            String name = generator.generateBeanName(bd, beanFactory);

                            /**
                             最后,通过beanFactory.registerBeanDefinition()方法将生成的Bean定义注册到Bean工厂中,
                             使得该Bean能够被Spring容器管理和使用。
                             */

                            beanFactory.registerBeanDefinition(name, bd);
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

context.registerBean(ComponentScanPostProcessor.class); // 解析 @ComponentScan

其实本质上就是读取@ComponentScan 注解里面的包结构,然后遍历每一个包路径里面所有的资源,如果对应的资源有@Component 或者器 派生类的话,就将其注入进去即可。

输出:

com.itheima.a05.component
classpath*:com/itheima/a05/component/**/*.class
[DEBUG] 22:18:26.118 [main] com.itheima.a05.component.Bean2     - 我被 Spring 管理啦 
[DEBUG] 22:18:26.121 [main] com.itheima.a05.component.Bean3     - 我被 Spring 管理啦 
config
com.itheima.a05.ComponentScanPostProcessor
bean2
bean3

@Bean

上面的输出其实并没有把config里面的三个bean也注入进去,所以还需要完善bean的注入:

public class AtBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    /**

     优化点: 路径 和 配置类写死了
     理论上先找到所有的配置类,然后对配置类在进行操作

     */

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        try {
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/itheima/a05/Config.class"));
            Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
            for (MethodMetadata method : methods) {
                System.out.println(method);

                // 解析都是类似的
                String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();

                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
                // 定义config里面的工厂方法
                builder.setFactoryMethodOnBean(method.getMethodName(), "config");
                // 自动装配,因为sqlSessionFactoryBean 有参数,需要将dataSource 装配进来
                // 构造方法的参数,工厂方法的参数,自动装配,选择装配模式AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR
                builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);

                if (initMethod.length() > 0) {

                    /**
                     builder.setInitMethodName(initMethod) 方法的作用是将初始化方法名称设置到 BeanDefinitionBuilder 中,
                     让 Spring 容器在创建 bean 实例之后,自动调用这个指定的初始化方法。

                     具体而言,当使用 BeanDefinitionBuilder 构建一个 BeanDefinition 对象时,可以通过该方法来设置创建的 bean
                     需要执行的初始化方法名称,从而在创建 bean 实例时自动调用这个方法。
                     */

                    builder.setInitMethodName(initMethod);
                }

                AbstractBeanDefinition bd = builder.getBeanDefinition();
                beanFactory.registerBeanDefinition(method.getMethodName(), bd);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

context.registerBean(AtBeanPostProcessor.class); // 解析 @Bean

本质上的解析其实也都是类似的,不用的在于,没有遍历包路径这一层,只需要遍历指定 config.class里面的方法即可。

此时其实打印输出就可以看到里面的bean也被注入了。

@Mapper

mapper接口其实和前两个还不太一样,比如将其注册在BeanFactory中:

	@Bean
    public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
        MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
        factory.setSqlSessionFactory(sqlSessionFactory);
        return factory;
    }

    @Bean
    public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {
        MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
        factory.setSqlSessionFactory(sqlSessionFactory);
        return factory;
    }

这样mapper其实也是能被注入的,但是如果手动的将 mapper包下的所有接口都这样注入,岂不是要麻烦死,而且也不符合自动注入的思想对吧?那么可以这样:

public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        try {
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resolver.getResources("classpath:com/itheima/a05/mapper/**/*.class");
            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            for (Resource resource : resources) {
                MetadataReader reader = factory.getMetadataReader(resource);
                ClassMetadata classMetadata = reader.getClassMetadata();
                // 判断是接口 还是 实现类
                if (classMetadata.isInterface()) {

                    /**
                     @Bean
                     public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {
                        MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
                        factory.setSqlSessionFactory(sqlSessionFactory);
                        return factory;
                     }
                     */


                    AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
                            .addConstructorArgValue(classMetadata.getClassName())
                            .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                            .getBeanDefinition();

                    AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();

                    /**
                     如果直接使用bd 来生成名字,由于 MapperFactoryBean.class 所以 两个mapper 都是 叫 MapperFactoryBean
                     所以参考spring源码 以classMetadata.getClassName() 作为名字生成bean
                     */

                    String name = generator.generateBeanName(bd2, beanFactory);
                    beanFactory.registerBeanDefinition(name, bd);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

其实本质上还是和前两种是一样的,但是在这里有一个坑,就是命名上,如果直接使用bd 来生成名字,由于 MapperFactoryBean.class是唯一的 所以 两个mapper 都是 叫 MapperFactoryBean,所以在这里,参考Spring源码,用第二个AbstractBeanDefinition 以classMetadata.getClassName() 作为名字生成bean。这样就不会出现重名的问题了。

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

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

相关文章

工作流审批平台,可迁移,可快速开发审批单(源码)

前言 activiti工作流引擎项目&#xff0c;企业erp、oa、hr、crm等企事业办公系统轻松落地&#xff0c;请假审批demo从流程绘制到审批结束实例。 一、项目形式 springbootvueactiviti集成了activiti在线编辑器&#xff0c;流行的前后端分离部署开发模式&#xff0c;快速开发平…

vscode C++项目相对路径的问题

如图所示的项目目录结构 如果要在main.cpp里用相对路径保存一个txt文件 std::ofstream file("./tree_model/my_file.txt");if (file.is_open()) {file << "This is a sample text.\n";file.close();std::cout << "File saved in the mode…

stm32 Keil V5 开发pack突然丢失

1.好好的工程,前一天可以正常编译,第二天突然无法编译,显示缺少安装包文件,如是重新下载安装包进入导入。 2.显示丢失 1.做STM32开发时,经常发现下载的DEMO代码无法打开,ST自带的更新库软件根本连不上服务器,每次到此都非常恼火。 3. CMSIS变成可红色 2. 莫名其妙的丢…

通过servlet设计一个博客系统

博客系统 准备工作servlrt依赖mysql依赖jackson依赖 服务器和数据库的交互设计数据库/数据表封装DBUtil,实现建立连接和断开连接创建实体类bloguser 编写Dao类BlogDaoUserDao 前端和服务器的交互功能一:博客列表页约定格式后端代码前端代码 功能二:实现博客详情页约定格式后端代…

NCP1256ESN65T1G具有多种保护功能 一款低功率离线电流模式PWM控制器

NCP1256ESN65T1G 包括构建几瓦到几十瓦成本高效开关模式电源所需的一切功能。该零件采用微型 TSOP-6 封装&#xff0c;供电范围高达 30 V&#xff0c;具有带抖动的 65 kHz 或 100 kHz 开关电路&#xff0c;在峰值电流模式控制下运行。当辅助侧功率开始降低时&#xff0c;该控制…

ARP欺骗攻击

ARP协议的概念 地址解析协议&#xff0c;即ARP&#xff08;Address Resolution Protocol&#xff09;&#xff0c;是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机&#xff0c;并接收返回消息&#xff0c;以此…

邂逅React及React初体验

文章目录 一、React是什么二、React数据对比三、React特点3.1 技术特点3.2 声明式编程3.3 组件化开发3.4 多平台适配 四、React开发依赖4.1 React的开发依赖4.2 Babel和React的关系4.3 React的依赖引入 五、React初体验5.1 Hello World5.2 增加按钮&#xff0c;点击修改文本5.3…

多元数据库时代,如何选择数据库基础设施?

一 多元数据库时代趋势特点 在数据库技术发展、数字化转型应用和信创三大驱动力共同作用下&#xff0c;数据库从单一架构支持多类应用演变为多类架构支持多类应用。这些架构并非替代关系&#xff0c;而是相互共存、共同发展的关系&#xff0c;数据库的多样性成为必然&#xff0…

Oculus开发入门

老是访问官网搭建unity环境太麻烦了&#xff0c;自己记录一下&#xff0c;在国内看。 官网教程连接 Getting Started with Interaction SDK | Oculus Developers Adjust Camera Rig Once you’ve completed the tutorial listed above, you can find Interaction SDK in Unit…

媒介易发稿教程,在人民网投稿的指南与技巧

在当今信息快速传播的时代&#xff0c;网络媒体成为了人们获取信息、表达观点的重要平台。而在这个庞大信息海洋中&#xff0c;人民网作为中国最具影响力的新闻网站之一&#xff0c;扮演着引领舆论、传播价值观念的重要角色。无论是个人经历、社会观察&#xff0c;还是学术研究…

日本汽车弯道超车,意图反超中国汽车和特斯拉,中国汽车另辟蹊径

日本汽车领军者丰田再次宣称固态电池是商用正在加速&#xff0c;已在电池材料技术上取得突破&#xff0c;充电10分钟可行驶1200公里&#xff0c;将在2027年装载于雷克萨斯上&#xff0c;再次引领汽车行业变革&#xff0c;为它在新能源汽车行业的落后鼓气。 丰田与大众可谓汽车行…

使用 IBM HeapAnalyzer 分析堆转储快照文件的

1.打开IBM HeapAnalyzer工具所在文件夹&#xff0c;执行cmd进入命令窗口 java -jar -Xmx1G .\ha456.jarha456.jar 为IBM HeapAnalyzer工具文件名 2、打开需要分析的日志文件

测试人员的设计思维

我们在工作、生活中经常听到别人说设计思维。那么&#xff0c;设计思维是什么&#xff1f;对测试人员来说有什么帮助&#xff1f; 在聊设计思维前&#xff0c;让我们先了解一下思维。思维是人类的重要组成部分&#xff0c;它帮助我们理解复杂的情况&#xff0c;形成深思熟虑的…

Class文件简单解析

一、Class文件 Test.java public class Test{ private int count; public int inc(){ return count;} } vim -b Test.class 输入 :%!xxd 图一 java反编译 javap -v -l -c Test.class 图二 二、Class文件解析 1、Class文件结构 1、魔数 魔数magic是Class文件的标记&…

(免费领源码)java#Springboot#mysql英语自主学习平台的设计与实现35901-计算机毕业设计项目选题推荐

摘 要 随着互联网趋势的到来&#xff0c;各行各业都在考虑利用互联网将自己推广出去&#xff0c;最好方式就是建立自己的互联网系统&#xff0c;并对其进行维护和管理。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xff0c;采用Java技术建设英语自主学习平台。 …

【Proteus仿真】【STM32单片机】水箱液位监控系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用LCD1602液晶、按键、蜂鸣器、液位传感器、PCF8591 ADC转换器、水泵等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示当前水位、上…

安全和便捷:如何将运营商二要素API应用于实名制管理中

引言 随着互联网的快速发展&#xff0c;数字化身份验证和实名制管理变得越来越重要。在金融、电子商务、社交媒体等领域&#xff0c;确保用户身份的安全和准确性至关重要。运营商二要素核验API成为了实名制管理的有力工具&#xff0c;它不仅能够提供高水平的安全性&#xff0c…

M-LVDS收发器MS2111,可替代SN65MLVD206

MS2111 是多点低压差分 (M-LVDS) 线路驱动器和接收器。经过 优化&#xff0c;可运行在高达 200Mbps 的信号速率下。所有部件均符合 M LVDS 标准 TIA / EIA-899 。该驱动器的输出支持负载低至 30Ω 的多 点总线。 MS2111 的接收器属于 Type-2 &#xff0c; 可在 -1…

特殊类设计[上]

文章目录 1.只能在堆上创建对象的类1.1析构函数私有化1.2析构函数 delete1.3构造函数私有定义拷贝构造私有只声明1.4构造函数私有定义拷贝构造 delete 2.不能被拷贝的类2.1 私有声明不定义拷贝构造函数2. 2拷贝构造函数 delete 3.只能在栈和静态区创建对象的类4.不能被继承的…