第一讲:BeanFactory和ApplicationContext

BeanFactory和ApplicationContext

什么是BeanFactory

  • 它是ApplicationContext的父接口
  • 它才是Spring的核心容器,主要的ApplicationContext实现都组合了它的功能

image-20231205165356277

BeanFactory能做什么?

表面上看BeanFactory的主要方法只有getBean(),实际上控制反转、基本的依赖注入、Bean的生命周期的各种功能,都由他的实现类提供

例如:通过反射查看它的成员变量singletonObjectssingletonObjects内部包含了所有单例bean

实现这个例子前,引出一个类DefaultListableBeanFactory,主要功能是作为一个可配置的、可列表化的 Bean 工厂,用于管理和维护应用程序中的 Bean 定义和实例

DefaultListableBeanFactory继承了DefaultSingletonBeanRegistry类,而DefaultSingletonBeanRegistry类就是专门用来管理单例Bean的

image-20231205165954183

DefaultSingletonBeanRegistry其中有个成员变量也就是singletonObjects

image-20231205170156677

这样我们就找到了单例Bean存储的地方,通过反射调用即可

SpringBoot启动会有很多单例Bean注入,map中包含了所有的单例Bean,我自定义了Component1类注入到其中

@SpringBootApplication
public class Application {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        //返回容器对象
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);

        //反射获取singletonObjects属性
        Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
        //开启可操作singletonObjects,否则无法对他进行操作,因为他是private final的
        singletonObjects.setAccessible(true);
        //BeanFactory是ConfigurableListableBeanFactory实现类的成员变量
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //Field.get(Object obj):获取指定对象obj上此field表示的字段的值
        Map<String,Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);
        map.entrySet().stream().filter(e -> e.getKey().startsWith("component")).forEach(e -> {
            System.out.println(e.getKey() + "=" + e.getValue());
        });
    }
}

上面代码最难理解的是singletonObjects.get(beanFactory),刚开始没有理解到反射,重新翻了反射的代码发现他的作用是填充ConfigurableListableBeanFactory中的singletonObjects属性

Java源码中并没有这个属性,反复查找后发现DefaultListableBeanFactoryConfigurableListableBeanFactory的实现类,而DefaultListableBeanFactory继承了DefaultSingletonBeanRegistry,所以自然有singletonObjects属性

ApplicationContext能做什么

前面提到过,ApplicationContext是在BeanFactory基础上做了扩展

  • MessageSource:拓展国际化功能
  • ApplicationEventPublisher:事件发布与监听,实现组件之间的解耦
  • ResourcePatternResolver:通过通配符方式获取一组Resource资源
  • EnvironmentCapable:整合 Environment 环境(能通过它获取各种来源的配置信息)

image-20231205165356277

国际化

所谓国际化就是可以通过准备好的配置文件,将语言翻译成各种国家的语言

#准备以下2个配置文件
#messages_en.properties  翻译成英文
hi=hello

#messages_zh.properties  翻译成中文  \u4F60\u597D对应着你好
hi=\u4F60\u597D

通过getMessage()转化

@SpringBootApplication
public class Application {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        //国际化
        System.out.println(context.getMessage("hi", null, Locale.US)); //hello
        System.out.println(context.getMessage("hi", null, Locale.CHINA)); //你好
    }
}

获取Resource资源

@SpringBootApplication
public class Application {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        //通配符方式获取一组 Resource 资源
        Resource[] resources = context.getResources("classpath:application.properties");
        for (Resource resource : resources) {
            System.out.println(resource); //class path resource [application.properties]
        }
    }
}

整合 Environment 环境

可以读取系统环境变量,也可以读取SpringBoot配置文件

@SpringBootApplication
public class Application {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        //整合 Environment 环境
        System.out.println(context.getEnvironment().getProperty("JAVA_HOME"));
        System.out.println(context.getEnvironment().getProperty("server.port"));
    }
}

事件发布与监听

事件发布与监听指的是,一条线程发布特定事件,监听该事件的方法收到消息后,处理该方法,这也是异步的一种思想

创建事件

事件需要继承ApplicationEvent

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

监听者

@EventListener 可以在 Spring 应用中监听和响应特定类型的事件。当事件发生时,标记有 @EventListener 注解的方法将被自动调用。

@Component
public class Component1 {
    private static final Logger logger = LoggerFactory.getLogger(Component1.class);

    @EventListener
    public void aaa(UserRegisteredEvent event) {
        logger.info("{}",event);
    }
}

事件发布

@SpringBootApplication
public class Application {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        //发布事件
        context.publishEvent(new UserRegisteredEvent(context));
    }
}

BeanFactory实现

代码准备

我们最终想要实现的是,像Spring一样,向自定义的BeanFactory注册Config对象

@Configuration
static class Config{
    @Bean
    public Bean1 bean1(){
        return new Bean1();
    }

    @Bean
    public Bean2 bean2(){
        return new Bean2();
    }
}

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

    @Autowired
    private Bean2 bean2;

    public Bean1() {
        logger.info("构造 Bean1()");
    }

    public Bean2 getBean2(){
        return bean2;
    }
}

static class Bean2{
    private static final Logger logger = LoggerFactory.getLogger(Bean2.class);

    public Bean2() {
        logger.info("构造 Bean2()");
    }
}

具体实现

BeanFactory可以通过registerBeanDefinition注册一个BeanDefinition 对象

public static void main(String[] args) {
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    //定义一个bean (class,scope,初始化,销毁) scope:单例还是多例
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class)
        .setScope("singleton").getBeanDefinition();
    beanFactory.registerBeanDefinition("config",beanDefinition);

    //查看BeanFactory中的bean
    for (String name : beanFactory.getBeanDefinitionNames()) {
        System.out.println(name);
    }
}

执行以上代码,控制台输出config,证明Config已经注册到BeanFactory

但是发现Bean1Bean2并没有注册进去,因为BeanFactory并不会主动调用BeanFactory的后置处理器

现在我们需要为BeanFactory添加一些常用的后置处理器,注意这里仅仅是添加

AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

通过观察控制台输出,发现多了一些BeanFactory中多了些Bean

image-20231207113233914

简单介绍一下目前需要了解几个关键的处理器

  • internalConfigurationAnnotationProcessor:用于处理@Configuration注解,它会解析@Configuration注解并处理其中的@Bean注解,将被注解的方法返回的对象注册为Bean。
  • internalAutowiredAnnotationProcessor:用于处理@Autowired注解,它会解析@Autowired注解并自动装配依赖对象。它会在Bean初始化之前进行处理,确保依赖对象正确注入。
  • internalCommonAnnotationProcessor:用于处理通用注解,如@Resource@PostConstruct@PreDestroy等。它会在Bean初始化之前进行处理,执行相应的初始化和销毁方法。

接下来需要让BeanFactory处理器生效,其中internalConfigurationAnnotationProcessor属于BeanFactory后置处理器,internalAutowiredAnnotationProcessorinternalCommonAnnotationProcessor属于Bean的后置处理器,生效方式不一样

//BeanFactory后置处理器的主要功能,补充了一些bean的定义
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values()
    .forEach(beanFactoryPostProcessor -> {
    beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
});

//bean后置处理器,针对bean的生命周期的各个阶段提供扩展,例如@Autowird,@Resource
beanFactory.getBeansOfType(BeanPostProcessor.class).values()
    .forEach(beanPostProcessor -> {
    beanFactory.addBeanPostProcessor(beanPostProcessor);
});

观察控制台,发现Bean1Bean2也被出册到了BeanFactory中,并且可以实例化

image-20231207115043414

目前到这看似我们的目标已经完成了,实际上会发现Bean的构造方法是在用到的时候调用的,属于懒汉式,对于单例Bean来说,希望他在注册的时候就已经实例化

//在容器初始化完成后,提前实例化所有的单例Bean
beanFactory.preInstantiateSingletons();

观察控制台发现,构造方法在容器初始化的时候就已经执行了

image-20231207132430162

总结

  • BeanFactory不会主动调用BeanFactory后置处理器
  • BeanFactory不会主动调用Bean后置处理器
  • BeanFactory不会主动初始化单例

补充说明

Bean的后置处理器会有排序的逻辑

举个例子,当一个接口有两个实现类时,这个接口通过依赖注入的形式注入,同时标注了@Autowired以及@Resource,哪个注解会生效,取决于Bean的哪个后置处理器先添加到BeanFactory

ApplicationContext实现

这里我们主要了解几个常用的ApplicationContext实现

  • ClassPathXmlApplicationContext
  • FileSystemXmlApplicationContext
  • AnnotationConfigApplicationContext
  • AnnotationConfigServletWebServerApplicationContext

基于XML配置文件注册Bean

目前无论是Spring还是SpringBoot都很少用XML的方式,更推荐使用配置类来注册Bean,所以这里了解即可

前置代码

需要注册的Bean

static class Bean1{
}

static class Bean2{
    private Bean1 bean1;

    public Bean1 getBean1() {
        return bean1;
    }

    public void setBean1(Bean1 bean1) {
        this.bean1 = bean1;
    }
}

读取的XML配置文件

<?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.xsd">
    <bean id="bean1" class="com.yellowstar.spring.demo1.TestApplicationContext$Bean1"/>

    <bean id="bean2" class="com.yellowstar.spring.demo1.TestApplicationContext$Bean2">
        <property name="bean1" ref="bean1"/>
    </bean>
</beans>
ClassPathXmlApplicationContext

ClassPathXmlApplicationContext的作用是基于classpath下 xml 格式的配置文件来注册Bean

private static void testClassPathXmlApplicationContext(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("b01.xml");
    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }
    System.out.println(context.getBean(Bean2.class).getBean1());
}

image-20231207152154004

FileSystemXmlApplicationContext

FileSystemXmlApplicationContext的作用是基于磁盘路径下 xml 格式的配置文件来创建

    private static void testFileSystemXmlApplicationContext(){
        //可以选择绝对路径,也可以是相对路径
        FileSystemXmlApplicationContext context = 
            new FileSystemXmlApplicationContext("src/main/resources/b01.xml");
    }
实现ClassPathXmlApplicationContext

XmlBeanDefinitionReader用于从XML配置文件中读取和解析Bean的定义信息

   DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
   //设置配置文件读取到beanFactory
   XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
   xmlBeanDefinitionReader.loadBeanDefinitions(new ClassPathResource("b01.xml"));

基于Java配置类注册Bean

AnnotationConfigApplicationContext

AnnotationConfigApplicationContext的作用是基于Java配置类来注册Bean

配置类

@Configuration
static class Config{
    @Bean
    public Bean1 bean1(){
        return new Bean1();
    }

    @Bean
    public Bean2 bean2(Bean1 bean1){
        Bean2 bean2 = new Bean2();
        bean2.setBean1(bean1);
        return bean2;
    }
}

实现

private static void testAnnotationConfigApplicationContext(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }
    System.out.println(">==" + context.getBean(Bean2.class).getBean1());
}

这里可以注意到常用的一些后置处理器是自动帮我们加载到容器中了,而基于XML模式的方式还需要在XML另外定义才行

image-20231207153311158

AnnotationConfigServletWebServerApplicationContext

AnnotationConfigServletWebServerApplicationContext也是是基于Java配置类来注册Bean,区别不同的在于他适用于web环境

这块内容比较复杂,先贴代码在详细解释

    private static void testAnnotationConfigServletWebServerApplicationContext(){
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }

    @Configuration
    static class WebConfig{
        @Bean
        public ServletWebServerFactory servletWebServerFactory(){
            return new TomcatServletWebServerFactory();
        }

        @Bean
        public DispatcherServlet dispatcherServlet(){
            return new DispatcherServlet();
        }

        @Bean
        public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet){
            // "/" 代表匹配所有请求
            return new DispatcherServletRegistrationBean(dispatcherServlet,"/");
        }

        @Bean("/")
        public Controller controller1(){
            return (request,response) -> {
                response.getWriter().print("hello");
                return null;
            };
        }
    }

上述代码的功能就像是启动了Spring服务一样,可以通过访问localhost:8080来进入controller1

image-20231207160024704

其中最重要的是WebConfig必须拥有三大组件

  • ServletWebServer:用于支持 Servlet 容器的接口。它定义了与 Servlet 容器相关的通用操作和属性,允许 Spring 应用程序与不同的 Servlet 容器(如Tomcat、Jetty等)进行交互。
  • DispatcherServlet:用于处理web请求的关键组件之一,所有请求都会经过他
  • DispatcherServletRegistrationBean:用于注册和配置 DispatcherServlet 的 Bean。它允许开发者以编程方式配置 DispatcherServlet 的各种属性,以及将其与特定的 URL 映射关联起来。

Bean的生命周期

通常情况下,Bean的生命周期分为四个阶段

  1. 构造方法阶段:在这个阶段,Spring容器实例化Bean对象,调用其构造方法来创建Bean的实例
  2. 依赖注入阶段:在Bean对象被实例化完成后,Spring容器会对Bean的属性进行依赖注入,将依赖的其他 Bean或值注入到当前 Bean中。
  3. 初始化阶段:在依赖注入完成后,Spring容器会调用Bean的初始化方法,例如使用@PostConstruct注解
  4. 销毁阶段:当容器关闭或销毁时,Spring容器会调用Bean的销毁方法,例如使用@PreDestory注解

这样讲讲方法太枯燥了,我一开始也记不住,通过下面例子可以加深印象

@SpringBootApplication
public class A03Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A03Application.class, args);
        context.close();
    }
}

@Component
public class LifeCycleBean {
    private static final Logger logger = LoggerFactory.getLogger(LifeCycleBean.class);

    public LifeCycleBean() {
        logger.debug("构造");
    }

    @Autowired
    public void autowire(@Value("${JAVA_HOME}") String home){
        logger.debug("依赖注入:{}",home);
    }

    @PostConstruct
    public void init(){
        logger.debug("初始化");
    }

    @PreDestroy
    public void destory(){
        logger.debug("销毁");
    }
}

执行代码可以观察到顺序依次为构造->依赖注入->初始化->销毁

image-20231208113234518

Bean扩展

Spring的扩展程度是非常高的,可以在Bean的各个生命周期中实现自己想要的结果

@Component
public class MyBeanPostProcessor implements DestructionAwareBeanPostProcessor, InstantiationAwareBeanPostProcessor {
    private static final Logger log = LoggerFactory.getLogger(MyBeanPostProcessor.class);

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 实例化之前执行, 这里返回的对象会替换掉原本的 bean");
        }
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 实例化之后执行, 这里如果返回 false 会跳过依赖注入阶段");
        }
        return true;
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 依赖注入阶段执行, 如 @Autowired、@Value、@Resource");
        }
        return pvs;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 初始化之前执行, 这里返回的对象会替换掉原本的 bean, 如 @PostConstruct、@ConfigurationProperties");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 初始化之后执行, 这里返回的对象会替换掉原本的 bean, 如代理增强");
        }
        return bean;
    }

    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 销毁之前执行, 如 @PreDestroy");
        }
    }
}

模版方法

模板方法是一种常用的设计模式,在之前对BeanFactory添加后处理器时,比较好奇他这个处理器是如何加载进去的,现在使用模板方法可以试着自己动手实现

先来看一下基础代码,MyBeanFactory是自定义的一个BeanFactory,其中getBean()可以获取Bean

我们的想法是,在getBean()时,添加后处理器,来解析@Autowired或者@Resource

    public static void main(String[] args) {
        MyBeanFactory beanFactory = new MyBeanFactory();
        beanFactory.getBean();
    }

    static class MyBeanFactory{
        public Object getBean(){
            Object bean = new Object();
            //依赖注入解析
            return bean;
        }
    }

方案一

比较简单的方法就是直接在getBean()中实现,但是这样并不利于扩展,假如需要后续解析其他注解,那么就得修改getBean()方法

方案二

参照之前DefaultListableBeanFactory的做法,我们并没有修改内部方法,而是调用DefaultListableBeanFactory其他方法来添加后处理器

通过一个集合来加载所有后处理器,这样在需要添加后处理器的时候就可以在外部直接调用addBeanPostProcessor()方法,不需要动到MyBeanFactory这个最底层的类

    public static void main(String[] args) {
        MyBeanFactory beanFactory = new MyBeanFactory();
        beanFactory.addBeanPostProcessor(() -> System.out.println("解析 @Autowired"));
        beanFactory.addBeanPostProcessor(() -> System.out.println("解析 @Resource"));
        beanFactory.getBean();
    }

    static class MyBeanFactory{
        public Object getBean(){
            Object bean = new Object();
            //执行后置处理器
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                beanPostProcessor.inject();
            }
            return bean;
        }

        private static List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

        public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor){
            beanPostProcessorList.add(beanPostProcessor);
        }
    }

    interface BeanPostProcessor{
        /**
         * 依赖注入阶段扩展
         */
        void inject();
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring-5-切入点的高级使用

Spring提供了两个额外的Pointcut实现&#xff0c;分别是ComposablePointcut和ControlFlowPointcut,它们提供了所需的灵活性。 使用控制流切入点 由ControlFlowPointcut类实现的Spring控制流切入点类似于许多其他AOP实现中可用的cflow构造&#xff0c;尽管功能上没有那么强大。…

(self-supervised learning)Event Camera Data Pre-training

Publisher: ICCV 2023 MOTIVATION OF READING: 自监督学习、稀疏事件 NILM link: https://arxiv.org/pdf/2301.01928.pdf Code: GitHub - Yan98/Event-Camera-Data-Pre-training 1. Overview Contributions are summarized as follows: 1. A self-supervised framework f…

“产品经理必懂的关键术语“

产品经理是现代企业中非常重要的一个角色&#xff0c;他们负责制定产品策略、规划产品开发流程、管理产品质量和用户反馈等等。然而&#xff0c;对于产品经理来说&#xff0c;了解并掌握相关的专业术语是非常重要的。本篇文章会介绍一些产品经理需要掌握的专业术语&#xff0c;…

一篇文章掌握系统架构的演变和常见微服务框架

目录 前言 一、系统架构的演变 1、单体应用架构 优点&#xff1a; 缺点&#xff1a; 2、垂直应用架构 优点&#xff1a; 缺点&#xff1a; 3、分布式SOA架构 3.1 什么是SOA 3.2 SOA架构 优点&#xff1a; 缺点&#xff1a; 4、微服务架构 优点&#xff1a; 缺点…

eve环境虚拟机和电脑如何传送文件

一.桥接 &#xff08;实现电脑和虚拟机在同一网段&#xff09; 虚拟机上网盘设置 二.属性---文件共享设置 1打开属性&#xff0c;点击共享 2.添加共享人为全部人&#xff0c;并修改权限为读写模式 3.点击高级共享&#xff0c;选定此文件夹 4.点击网络和共享中心&#xff0c;划…

C++——智能指针和RAII

该文章代码均在gitee中开源 C智能指针hpphttps://gitee.com/Ehundred/cpp-knowledge-points/tree/master/%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88​​​​​​​ 智能指针 传统指针的问题 在C自定义类型中&#xff0c;我们为了避免内存泄漏&#xff0c;会采用析构函数的方法释…

2023年末,软件测试面试题总结与分享

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;得准备年后面试了&#xff0c;又不知道从何下手&#xff01;为了帮大家节约时间&#xff0c;特意准备了一份面试相关的资料&#xff0c;内容非常的全面&#xff0c;真的可以好好补一补&#xff0c;希望大家在都能拿到…

分享Python采集40个NET整站程序源码,总有一款适合您

分享Python采集40个NET整站程序源码&#xff0c;总有一款适合您 Python采集的40个NET整站程序源码下载链接&#xff1a;https://pan.baidu.com/s/1z54JHJkFYa4Kx2oBtPrn_w?pwd2ta4 提取码&#xff1a;2ta4 商品评论网站系统 小孔子内容管理系统XkCms V2.0 友间别墅整站程…

实现二叉树的基本操作与OJ练习

目录 1.二叉树的基本操作 1.1二叉树基本操作完整代码 1.2检测value值是否存在 1.3层序遍历 1.4判断一棵树是不是完全二叉树 2.OJ练习 2.1平衡二叉树 2.2对称二叉树 2.3二叉树遍历 1.二叉树的基本操作 1.1二叉树基本操作完整代码 public class BinaryTree {static…

【Unity】【FBX】如何将FBX模型导入Unity

【背景】 网上能够找到不少不错的FBX模型资源&#xff0c;大大加速游戏开发时间。如何将这些FBX导入Unity呢&#xff1f; 【步骤】 打开Unity项目文件&#xff0c;进入场景。 点击Projects面板&#xff0c;右键选择Import New Assets 选中FBX文件后导入。Assets文件夹中就会…

Python 内置高阶函数练习(Leetcode500.键盘行)

Python 内置高阶函数练习&#xff08;Leetcode500.键盘行&#xff09; 【一】试题 &#xff08;1&#xff09;地址&#xff1a; 500. 键盘行 - 力扣&#xff08;LeetCode&#xff09; &#xff08;2&#xff09;题目 给你一个字符串数组 words &#xff0c;只返回可以使用在…

东方甄选小作文事件最大的赢家是谁? 董宇辉还是俞敏洪

有人说东方甄选小作文事件没有赢家&#xff0c;小马识途营销顾问认为小作文事件最终也没有输家。就公司来讲&#xff0c;有机会培养更多优秀主播&#xff0c;未来发展更健康了&#xff1b;就俞老师来讲&#xff0c;是把宇辉的薪酬和职位提高了&#xff0c;这些也是宇辉本来就应…

3D视觉-结构光测量-线结构光测量

概述 线结构光测量中&#xff0c;由激光器射出的激光光束透过柱面透镜扩束&#xff0c;再经过准直&#xff0c;产生一束片状光。这片光束像刀刃一样横切在待测物体表面&#xff0c;因此线结构光法又被成为光切法。线结构光测量常采用二维面阵 CCD 作为接受器件&#xff0c;因此…

模型 安索夫矩阵

本系列文章 主要是 分享模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。产品市场战略。 1 安索夫矩阵的应用 1.1 江小白的多样化经营策略 使用安索夫矩阵来分析江小白市场战略。具体如下&#xff1a; 根据安索夫矩阵&#xff0c;江小白的现有产品是其白酒产品&…

【23.12.30期--Spring篇】Spring的AOP介绍(详解)

Spring的AOP介绍 ✔️简述✔️扩展知识✔️AOP是如何实现的 ✔️简述 AOP(Aspect-Oriented Programming)&#xff0c;即面向切面编程&#xff0c;用人话说就是把公共的逻辑抽出来&#xff0c;让开发者可以更专注于业务逻辑开发。 和IOC-样&#xff0c;AOP也指的是一种思想。AOP…

Android APK未签名提醒

最近新建了一个项目&#xff0c;在build.gradle中配置好了签名&#xff0c;在执行打包的时候打出的包显示已签名&#xff0c;但是在上传市场的时候提示未签名。于是排查了好久&#xff0c;发现在build.gradle中配置的minsdk 24&#xff0c;会导致不使用V1签名&#xff0c;于是我…

【洛谷学习自留】p7621 超市购物

2023/12/29 解题思路&#xff1a; 简单的计算&#xff0c;难度主要集中在格式化输出和四舍五入的问题上。 1.建立一个计数器&#xff0c;for循环遍历单价和数量的乘积&#xff0c;存入计数器。 2.计算计数器的最终值乘以0.85h后的结果&#xff0c;为了保证四舍五入正确&…

小程序入门-登录+首页

正常新建一个登录页面 创建首页和TatBar&#xff0c;实现登录后底部出现两个按钮 代码 "pages": ["pages/login/index","pages/index/index","pages/logs/logs" ],"tabBar": {"list": [{"pagePath"…

数模学习day05-插值算法

插值算法有什么作用呢&#xff1f; 答&#xff1a;数模比赛中&#xff0c;常常需要根据已知的函数点进行数据、模型的处理和分析&#xff0c;而有时候现有的数据是极少的&#xff0c;不足以支撑分析的进行&#xff0c;这时就需要使用一些数学的方法&#xff0c;“模拟产生”一些…

Linux文件fd剖析

学习之前&#xff0c;首先要认识什么是文件&#xff1f; 空文件也是要在内存中占据空间的&#xff0c;因为它还有属性数据。文件 属性 内容文件操作 对内容 对属性 或者对内容和属性的操作标定一个文件的时候&#xff0c;必须使用&#xff1a;路径文件名&#xff0c;文件具…