Spring底层架构核心概念

news2025/1/11 15:04:45

文章目录

  • Spring底层架构核心概念
    • BeanDefinition
    • BeanDefinitionReader
      • AnnotatedBeanDefinitionReader
      • XmlBeanDefinitionReader
      • ClassPathBeanDefinitionScanner
    • BeanFactory
    • ApplicationContext
      • 国际化
      • 资源加载
      • 获取运行时环境
      • 事件发布
    • 类型转换
      • PropertyEditor
      • ConversionService
      • TypeConverter
    • OrderComparator
    • BeanPostProcess
    • BeanFactoryPostProcessor
    • FactoryBean
    • ExcludeFilter和IncludeFilter
    • MetadataReader、ClassMetadata、AnnotationMetadata

Spring底层架构核心概念

BeanDefinition

Bean定义,存在很多属性来描述一个Bean的特点。BeanDefinition是一个接口

  • beanClass:表示Bean的Class类型
  • scope:表示Bean作用域,单例或原型等等
  • lazyInit:是否为懒加载
  • initMethodName:初始化时要执行的方法
  • destroyMethodName:销毁时要执行的方法
  • … …

声明式定义Bean:@Bean @Component <Bean/>

编程式定义Bean:

public static void main(String[] args) {
    // 定义Spring容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

    // 定义BeanDefinition
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(UserService.class);
    beanDefinition.setScope("singleton");
    // 注册进容器中
    applicationContext.registerBeanDefinition("userService", beanDefinition);
}

误区:

  • 在java配置类中并不是加了@Bean注解的方法就立刻会执行,而是会先生成一个BeanDefinition对象,再之后遍历找出非懒加载的单例Bean再去执行方法。

BeanDefinition是一个接口

AbstractBeanDefinition抽象类实现了BeanDefinition接口

GenericBeanDefinition类继承了AbstractBeanDefinition抽象类,比较常见的类

RootBeanDefinition类继承了AbstractBeanDefinition抽象类,它跟合并BeanDefinition有关

ScannedGenericBeanDefinition类继承了GenericBeanDefinition类,ClassPathBeanDefinitionScanner的scan()方法创建的是该类

AnnotatedGenericBeanDefinition类继承了GenericBeanDefinition类,AnnotatedBeanDefinitionReader的register()方法创建的是该类



BeanDefinitionReader

BeanDefinition的读取器

我们有多种方式定义Bean,XML或者是注解,那么也就需要不同的类去读取解析这些内容并生成BeanDefinition,再存入Spring容器中,所以就定义了一个规范BeanDefinitionReader接口

该接口有三个实现

public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader
    
public class GroovyBeanDefinitionReader extends AbstractBeanDefinitionReader
    public class AnnotationConfigApplicationContext extends GenericApplicationContext

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader


AnnotatedBeanDefinitionReader

能直接将某个类转换为BeanDefinition,并解析类上的注解

它能解析的注解是:@Conditional、@Scope、@Lazy、@Primary、@DependsOn、@Role、@Description

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

AnnotatedBeanDefinitionReader annotatedBeanDefinitionReader = new AnnotatedBeanDefinitionReader(applicationContext);
annotatedBeanDefinitionReader.registerBean(User.class);

System.out.println(applicationContext.getBean("user"));

我们也可以直接使用applicationContext.register()方法注册,它底层实际上就是调用的AnnotatedBeanDefinitionReader.register()方法



XmlBeanDefinitionReader

可以解析<bean/>标签

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(context);
int i = xmlBeanDefinitionReader.loadBeanDefinitions("spring.xml");

System.out.println(context.getBean("user"));


ClassPathBeanDefinitionScanner

扫描器,作用和BeanDefinitionReader类似,可以扫描某个包路径下的类。

比如扫描到的类上如果存在@Component这一类注解,那么就会把这个类解析为一个BeanDefinition

// 构造方法中没有指定配置类,也就没有包扫描路径
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();

ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.scan("com.hs");

System.out.println(context.getBean("userService"));

我们也可以直接使用applicationContext.scan()方法执行包扫描路径,它底层实际上就是调用的ClassPathBeanDefinitionScanner.scan()方法



BeanFactory

Bean工厂,复制创建Bean,并提供获取Bean的API方法。

而ApplicationContext也是BeanFactory的一种,他们都是接口,ApplicationContext继承了BeanFactory。但ApplicationContext还继承了很多其他的接口,它的功能更强大。比如MessageSource表示国际化,ApplicationEventPublisher表示事件发布,EnvironmentCapable表示获取环境变量,等等


当我们new一个ApplicationContext,其底层会new一个BeanFactory出来,当我们调用getBean()方法时实际上底层也是调用的BeanFactory的getBean()方法。

BeanFactory接口有一个很重要的实现类:DefaultListableBeanFactory

// 直接使用DefaultListableBeanFactory,而不是ApplicationContext的某个实现类
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

// 创建一个beanDefinition
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(User.class);

// 注册进BeanFactory中
beanFactory.registerBeanDefinition("user", beanDefinition);

System.out.println(beanFactory.getBean("user"));

DefaultListableBeanFactory它实现了很多接口,增加了很多功能

在这里插入图片描述



ApplicationContext

它也是一个BeanFactory,它还继承了一些其他的接口 有了一些其他的功能:

  • EnvironmentCapable,可以获取运行时环境,但没有设置的功能
  • ListableBeanFactory,拥有获取beanNames的功能
  • HierarchicalBeanFactory,分层,拥有获取父BeanFactory的功能
  • MessageSource,国际化功能
  • ApplicationEventPublisher,拥有广播事件的功能,ApplicationContext没有添加事件监听器的功能
  • ResourcePatternResolver,资源加载器,可以一次性获取多个资源(文件资源等等)

其中ApplicationContext有两个重要的实现类:AnnotationConfigApplicationContext和ClassPathXmlApplicationContext

现在比较常用的是AnnotationConfigApplicationContext,这两个实现类就不过多分析了。



国际化

我们首先创建一个国际化文件

在这里插入图片描述

指定文件名和语言

在这里插入图片描述

然后就有了这样的三个文件,文件名为errorMessage,语言有三种 en/sc/tc

在这里插入图片描述


定义一个Bean

@Bean
public MessageSource messageSource() {
   ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    // 指定文件名
   messageSource.setBasename("errorMessage");
   return messageSource;
}

有了这个Bean我们就可以在任意要进行国际化的地方使用该MessageSource,调用messageSource.getMessage()方法

@Autowired
private MessageSource messageSource;

public void test(){
    System.out.println(messageSource.getMessage("SYS001", null, new Locale("en")));

}

因为ApplicationContext也有国际化功能,我们也可以在类中实现ApplicationContextAware 得到ApplicationContext对象,再这样使用

System.out.println(applicationContext.getMessage("SYS001", null, new Locale("en")));


资源加载

很多东西资源,比如文件资源,网络资源,比如可以通过ApplicationContext获取某个文件的内容,或者是网络资源

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

Resource resource = context.getResource("file://D:\\project\\tuling\\src\\main\\java\\com\\zhouyu\\aspect\\ZhouyuAspect.java");
System.out.println(resource.contentLength());
System.out.println(resource.getFilename());

Resource resource1 = context.getResource("https://www.baidu.com");
System.out.println(resource1.contentLength());
System.out.println(resource1.getURL());

Resource resource2 = context.getResource("classpath:spring.xml");
System.out.println(resource2.contentLength());
System.out.println(resource2.getURL());

也还可以一次性获取多个

Resource[] resources = context.getResources("classpath:com/zhouyu/*.class");
for (Resource resource : resources) {
	System.out.println(resource.contentLength());
	System.out.println(resource.getFilename());
}

在Spring底层源码中,就可以通过该功能获取xml配置文件中的内容,还有包扫描。



获取运行时环境

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

// 操作系统层面的环境变量
Map<String, Object> systemEnvironment = context.getEnvironment().getSystemEnvironment();
System.out.println(systemEnvironment);

System.out.println("=======");

// 操作系统层面的配置
Map<String, Object> systemProperties = context.getEnvironment().getSystemProperties();
System.out.println(systemProperties);

System.out.println("=======");

// 这个是比较全的,包含了上面两种以及properties配置文件中的内容
MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
System.out.println(propertySources);

System.out.println("=======");

// 上面打印的内容其实都是获取的Environment对象中内容,我们也可以直接获取特定的配置
System.out.println(context.getEnvironment().getProperty("JAVA_HOME"));
System.out.println(context.getEnvironment().getProperty("sun.jnu.encoding"));
// 在配置类上面使用 @PropertySource("classpath:spring.properties") 加载某个properties文件
System.out.println(context.getEnvironment().getProperty("这里就可以获取properties文件中的配置项"));

输出结果如下图所示

在这里插入图片描述



事件发布

在Spring框架中,有三个关键类接口

分别是

  • 事件抽象类:ApplicationEvent
  • 事件发布者接口:ApplicationEventPublisher
  • 事件监听接口:ApplicationListenr

我们先定义一个事件,创建一个类,继承ApplicationEvent,然后重写构造方法

public class EmailEvent extends ApplicationEvent {

	public EmailEvent(Object source) {
		super(source);
	}

}

再定义一个事件监听器

@Bean
public ApplicationListener applicationListener() {
    return new ApplicationListener() {
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            
            if (event instanceof EmailEvent){
                // 自定义逻辑
                System.out.println("接收到了一个Email事件:" + event.getSource());
            } else {
                System.out.println("接收到了一个事件:" + event.getSource());
            }
            
        }
    };
}

然后发布一个事件

context.publishEvent("hushang");

除了使用ApplicationContext对象发布时间之外,在平时的工作中,比较常见的是使用依赖注入的方式发布事件

@Component
public class UserService {

	@Autowired
	private OrderService orderService;

	@Autowired
	private ApplicationEventPublisher applicationEventPublisher;

	public void test(){
		System.out.println("test()...");

		EmailEvent emailEvent = new EmailEvent("hs");
		applicationEventPublisher.publishEvent(emailEvent);
	}
}

此时会输出两遍,这是因为Spring在启动时就会发布一个事件。

接收到了一个事件:org.springframework.context.annotation.AnnotationConfigApplicationContext@7a8119...
test()...
接收到了一个Email事件:hs


类型转换

在Spring的源码中,关于类型转换的场景会很常见,比如就很有可能需要把String转换为其他类型。

比如我现在有一个User类

public class User {

   private String name;

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }
}

我现在先把String转换为User

@Component
public class UserService {

    // 这里能够直接赋值到User对象中的那么name属性中
   @Value("hushang")
   private User user;


   public void test(){
      System.out.println("test()...");
   }
}

这是会报错的,因为不能把String转换为User,我们需要进行类型转换相关的操作



PropertyEditor

这其实是JDK中提供的类型转化工具类

创建一个类

public class StringToUserPropertyEditor extends PropertyEditorSupport implements PropertyEditor {

   @Override
   public void setAsText(String text) throws IllegalArgumentException {
      User user = new User();
      user.setName(text);
      this.setValue(user);
   }
}

基本使用

StringToUserPropertyEditor propertyEditor = new StringToUserPropertyEditor();
propertyEditor.setAsText("1");
User value = (User) propertyEditor.getValue();
System.out.println(value);

向Spring中注册PropertyEditor

@Bean
public CustomEditorConfigurer customEditorConfigurer() {
   CustomEditorConfigurer customEditorConfigurer = new CustomEditorConfigurer();
    
   Map<Class<?>, Class<? extends PropertyEditor>> propertyEditorMap = new HashMap<>();
   // 表示StringToUserPropertyEditor可以将String转化成User类型
   // 在Spring源码中,如果发现当前对象是String,而需要的类型是User,就会使用该PropertyEditor来做类型转化
   propertyEditorMap.put(User.class, StringToUserPropertyEditor.class);
    
   customEditorConfigurer.setCustomEditors(propertyEditorMap);
   return customEditorConfigurer;
}

现在就能正常注入了

@Value("hushang")
private User user;


ConversionService

Spring中提供的类型转化服务,它比PropertyEditor更强大

创建一个类

public class StringToUserConverter implements ConditionalGenericConverter {

	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
        // 判断两个类型是否为String和User
		return sourceType.getType().equals(String.class) && targetType.getType().equals(User.class);
	}

	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
        // 可转换的类型
		return Collections.singleton(new ConvertiblePair(String.class, User.class));
	}

	@Override
	public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        // 自定义转换逻辑
		User user = new User();
		user.setName((String)source);
		return user;
	}
}

基本使用

DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new StringToUserConverter());
User value = conversionService.convert("1", User.class);
System.out.println(value);

向Spring中注册PropertyEditor

@Bean
public ConversionServiceFactoryBean conversionService() {
    ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
    conversionServiceFactoryBean.setConverters(Collections.singleton(new StringToUserConverter()));

    return conversionServiceFactoryBean;
}


TypeConverter

整合了PropertyEditor和ConversionService的功能,Spring底层源码中使用的是TypeConverter,因为它前两种转换都支持,Spring也不确定我们程序员会使用前两种的哪一种类型转换器,所以Spring就直接使用TypeConverter,将我们定义的类型转换器收集起来,然后直接调用convertIfNecessary()方法转换即可


整合PropertyEditor方式

public static void main(String[] args) {
    SimpleTypeConverter typeConverter = new SimpleTypeConverter();

    // 自定义的StringToUserPropertyEditor类
    typeConverter.registerCustomEditor(User.class, new StringToUserPropertyEditor());

    User value = typeConverter.convertIfNecessary("1", User.class);
    System.out.println(value);
}

整合了ConversionService方式

public static void main(String[] args) {
    SimpleTypeConverter typeConverter = new SimpleTypeConverter();

    // 自定义的StringToUserConverter类
    DefaultConversionService conversionService = new DefaultConversionService();
    conversionService.addConverter(new StringToUserConverter());
    typeConverter.setConversionService(conversionService);

    User value = typeConverter.convertIfNecessary("1", User.class);
    System.out.println(value);
}


OrderComparator

OrderComparator是Spring所提供的一种比较器,可以用来根据@Order注解或实现Ordered接口来执行值进行比较,从而可以进行排序。


实现Ordered接口的方式

// 先定义两个类 都实现Ordered接口,在getOrder()方法中指定要进行排序的数值
public class A implements Ordered {

	@Override
	public int getOrder() {
		return 3;
	}

	@Override
	public String toString() {
		return this.getClass().getSimpleName();
	}
}



public class B implements Ordered {

	@Override
	public int getOrder() {
		return 2;
	}

	@Override
	public String toString() {
		return this.getClass().getSimpleName();
	}
}

接下来进行排序比较

public class Main {

	public static void main(String[] args) {
		A a = new A(); // order=3
		B b = new B(); // order=2

		OrderComparator comparator = new OrderComparator();
		System.out.println(comparator.compare(a, b));  // 1

		List list = new ArrayList<>();
		list.add(a);
		list.add(b);

		// 按order值升序排序
		list.sort(comparator);

		System.out.println(list);  // B,A
	}
}

根据@Order注解的方式

Spring中还提供了一个OrderComparator的子类:AnnotationAwareOrderComparator,它支持用@Order来指定order值。比如:

@Order(3)
public class A {

	@Override
	public String toString() {
		return this.getClass().getSimpleName();
	}

}




@Order(2)
public class B {

	@Override
	public String toString() {
		return this.getClass().getSimpleName();
	}

}

进行排序比较

public class Main {

	public static void main(String[] args) {
		A a = new A(); // order=3
		B b = new B(); // order=2

		AnnotationAwareOrderComparator comparator = new AnnotationAwareOrderComparator();
		System.out.println(comparator.compare(a, b)); // 1

		List list = new ArrayList<>();
		list.add(a);
		list.add(b);

		// 按order值升序排序
		list.sort(comparator);

		System.out.println(list); // B,A
	}
}


BeanPostProcess

Bean的后置处理器,我们可以定义多个BeanPostProcessor,在创建Bean时,每个Bean都会执行这其中的前置和后置方法,我们也可以加if来判断给特定某个Bean执行某些特定的方法。

我们可以自定义一个类,实现BeanPostProcessor接口,重写接口的抽象方法,然后把该类注册进Spring容器中


我们可以通过定义BeanPostProcessor来干涉Spring创建Bean的过程。

Spring框架的扩展性主要就是体现在PostProcessor中。



BeanFactoryPostProcessor

Bean工厂的后置处理器,和BeanPostProcessor类似,只不过他们针对的对象不一样,一个是Bean,一个的BeanFactory。

BeanFactoryPostProcessor是干涉BeanFactory的创建过程。比如,我们可以这样定义一个BeanFactoryPostProcessor:

@Component
public class MyFactoryPostProcessor implements BeanFactoryPostProcessor {

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("加工beanFactory");
	}
}

典型的引用就是从配置文件中读取出来的${jdbc.username}配置项,刚开始生成BeanDefinition时值还是这个字符串,经过BeanFactoryPostProcessor之后才会替换为具体的配置项



FactoryBean

Spring中一个Bean的创建过程有很多的步骤,如果我们想要一个Bean完全由我们自己来创建就可以使用FactoryBean机制。

// 刚开始下面会创建一个BeanName为myFactoryBean的bean对象,对应的value是MyFactoryBean类型的对象
// 当getBean()方法调用的时候就会判断是否实现了FactoryBean接口,然后返回的是getObject()方法返回的Bean对象
@Component
public class MyFactoryBean implements FactoryBean {

	@Override
	public Object getObject() throws Exception {
		UserService userService = new UserService();

		return userService;
	}

	@Override
	public Class<?> getObjectType() {
		return UserService.class;
	}
}

我们自己创建了一个UserService的Bean对象,这种方式创建的bean对象只会经过初始化后的步骤,依赖注入和初始化哪些就不会有了。

而普通@Bean注解方式创建的Bean对象就会走完成的创建流程。虽然这两种方式都是在方法中直接new一个对象并返回。



ExcludeFilter和IncludeFilter

Spring在包扫描过程中使用的,ExcludeFilter表示排除过滤器,IncludeFilter表示包含过滤器。


案例

扫描com.hs包下面所有类,但是排除UserService类,也就是就算它上面有@Component注解也不会成为Bean。

@ComponentScan(value = "com.hs",
		excludeFilters = {@ComponentScan.Filter(
            	type = FilterType.ASSIGNABLE_TYPE, 
            	classes = UserService.class)})
public class AppConfig {
}

扫描com.hs包下面所有类,UserService类就算它上面没有@Component注解也会成为Bean。

@ComponentScan(value = "com.zhouyu",
		includeFilters = {@ComponentScan.Filter(
            	type = FilterType.ASSIGNABLE_TYPE, 
            	classes = UserService.class)})
public class AppConfig {
}

FilterType分为:

  1. ANNOTATION:表示是否包含某个注解
  2. ASSIGNABLE_TYPE:表示是否是某个类
  3. ASPECTJ:表示否是符合某个Aspectj表达式
  4. REGEX:表示是否符合某个正则表达式
  5. CUSTOM:自定义

在Spring的扫描逻辑中,默认会添加一个AnnotationTypeFilterincludeFilters,表示默认情况下Spring扫描过程中会认为类上有@Component注解的就是Bean。



MetadataReader、ClassMetadata、AnnotationMetadata

Spring中需要去解析类的元数据信息,比如类名、方法名、类上注解等。所以Spring对类的元数据做了抽象并提供了一些工具类

MetadataReader表示类的元数据读取器,默认实现类为SimpleMetadataReader。比如:

public class Test {

	public static void main(String[] args) throws IOException {
		SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory();
		
        // 构造一个MetadataReader
        MetadataReader metadataReader = simpleMetadataReaderFactory.getMetadataReader("com.hs.service.UserService");
		
        // 得到一个ClassMetadata,并获取了类名
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        System.out.println(classMetadata.getClassName());
        
        // 获取一个AnnotationMetadata,并获取类上的注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        // 判断类上面是否存在@Component注解
        annotationMetadata.hasMetaAnnotation(Component.class.getName())
		for (String annotationType : annotationMetadata.getAnnotationTypes()) {
			System.out.println(annotationType);
		}
	}
}

SimpleMetadataReader去解析类时,使用的ASM技术。


为什么要使用ASM技术?

我们可以把一个字节码先通过类加载器加载之后,得到Class对象,再去解析,但这种方式不太好。

Spring启动的时候需要去扫描,如果指定的包路径比较宽泛,那么扫描的类是非常多的,那如果在Spring启动时就把这些类全部加载进JVM了,这样不太好,因为JVM的类加载是在类要使用时才会加载,而现在都没有使用就都加载了,所以使用了ASM技术。

ASM技术就不需要进行类加载,直接去解析字节码文件

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

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

相关文章

20行Python代码获取 心碎榜单文件保存本地,准备开始emo......

人生苦短 我用python&#xff08;emo版&#xff09; (语气充满悲伤…) 今天咱们试试只用20行代码来实现 批量获取 某某云 文件保存本地&#xff0c;炒鸡简单&#xff01; 悄悄的告诉你&#xff0c;其实不到20行代码~ 事前准备 软件环境 环境Python3.8编辑器是pycharm 模块…

轻松掌握k8s的kubectl使用命令行操作01知识点

程序员使用的kubectl&#xff0c;只能在主节点使用kubectl命令 1、查看集群所有节点 kubectl get nodes 2、根据配置文件&#xff0c;给集群创建资源 kubectl apply -f xxxx.yaml 3、查看集群部署了哪些应用 kubectl get pods -A 4、指定查看命名空间部署了哪些应用 不指…

[DSCoding2] 反转链表——迭代法

题目描述 核心思路 观察上图可以发现&#xff0c;将链表反转后&#xff0c;原有的结点间的引用关系发生了改变&#xff0c;比如反转前1指向2&#xff0c;反转后2指向1&#xff0c; 所以我们可以从修改节点间的引用关系下手。 在遍历链表时&#xff0c;将当前节点的next指针指向…

ReentrantLock原理

实现了Lock接口 内部也维护了一个同步器Sync继承自AQS&#xff0c;Sync是抽象的&#xff0c;两个实现NonFairSync和FairSync public ReentrantLock() {sync new NonfairSync(); } public ReentrantLock(boolean fair) {sync fair ? new FairSync() : new NonfairSync(); }非…

算法训练Day30:332.重新安排行程 51. N皇后 37. 解数独

文章目录 重新安排行程题解 [N 皇后](https://leetcode.cn/problems/n-queens/description/)题解 解数独题解 重新安排行程 CategoryDifficultyLikesDislikesContestSlugProblemIndexScorealgorithmsHard (47.57%)7650--0 Tags Companies 给你一份航线列表 tickets &#xf…

微服务学习——微服务

认识微服务 单体架构 将业务的所有功能集中在一个项目中开发&#xff0c;打成一个包部署。 优点: 架构简单部署成本低 缺点: 耦合度高 分布式架构 根据业务功能对系统进行拆分&#xff0c;每个业务模块作为独立项目开发&#xff0c;称为一个服务。 优点: 降低服务耦合有利…

【LeetCode】剑指 Offer 58. 反转字符串 p284 -- Java Version

1. 题目介绍&#xff08;58. 反转字符串&#xff09; 面试题58&#xff1a;翻转字符串&#xff0c; 一共分为两小题&#xff1a; 题目一&#xff1a;翻转单词顺序题目二&#xff1a;左旋转字符串 2. 题目1&#xff1a;翻转单词顺序 题目链接&#xff1a;https://leetcode.cn/p…

使用 ip2region 获取用户的 IP 归属地

目录 1. ip2region 简介2. 使用步骤2.1 下载资源2.2 引入依赖2.3 编写工具类2.3.1 获取 IP 地址2.3.2 根据 IP 地址获取 IP 归属地2.3.3 完整代码 2.4 结果测试 1. ip2region 简介 ip2region 是一个离线IP地址定位库和IP定位数据管理框架&#xff0c;10微秒级别的查询效率&…

部署zabbix代理服务器和snmp监控

目录 zabbix代理服务器 分布式监控的作用 部署zabbix代理服务器 在 Web 页面配置 agent 代理 snmp监控 SNMP简介 部署zabbix-snmp 服务端安装snmp监控程序 在 Web 页面配置 snmp 方式监控 zabbix代理服务器 分布式监控的作用 分担 server 的集中式压力 解决多机房之…

HTTP | 强缓存与协商缓存

缓存&#xff0c;开发绕不开的环节。 web缓存分为很多种&#xff0c;比如数据库缓存、代理服务器缓存、CDN缓存&#xff0c;以及浏览器缓存&#xff08;localStorage, sessionstorage, cookie&#xff09;。 一个web应用&#xff0c;需要各式各样的资源&#xff08;html/css/…

【C++】C++11 右值引用和移动语义

文章目录 一、左值与左值引用二、右值与右值引用三、左值引用和右值引用的比较四、右值引用的使用场景和意义1、左值引用的短板2、移动构造和移动赋值3、STL 容器的变化 五、万能引用与完美转发1、万能引用2、完美转发 六、新增默认成员函数七、成员变量的缺省值八、default 和…

【Blender建模】newbird从零开始学+新手常见问题处理

目标 第一阶段&#xff1a;在跟着教程下&#xff0c;熟悉如何使用blender 教程地址&#xff1a;https://www.youtube.com/watch?vnIoXOplUvAw 一、移动、旋转、扩展各视角下的物体&#xff0c;熟悉各个窗口 鼠标中键&#xff08;Shift&#xff09;控制视角的方向 ~键快速选择…

Redis --- 入门、数据类型

一、前言 1.1、什么是Redis Redis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件&#xff0c;它是「Remote Dictionary Service」的首字母缩写&#xff0c;也就是「远程字典服务」。 基于内存存储&#xff0c;读写性能高适合存储热点…

Pytorch基础 - 5. torch.cat() 和 torch.stack()

目录 1. torch.cat(tensors, dim) 2. torch.stack(tensors, dim) 3. 两者不同 torch.cat() 和 torch.stack()常用来进行张量的拼接&#xff0c;在神经网络里经常用到。且前段时间有一个面试官也问到了这个知识点&#xff0c;虽然内容很小很细&#xff0c;但需要了解。 1. t…

Spring(10. 面试问题简析)学习笔记

上一篇&#xff1a;9. Spring 底层原理 文章目录 1. 对Spring的IOC机制的理解2. 对spring的AOP机制的理解3. 了解过cglib动态代理吗&#xff1f;他跟jdk动态代理的区别是什么&#xff1f;4. 能说说Spring中的Bean是线程安全的吗&#xff1f;5. Spring的事务实现原理是什么&…

Leetcode-二叉树

1.中序-后序构建二叉树 106. 从中序与后序遍历序列构造二叉树 - 力扣&#xff08;LeetCode&#xff09; 1. 首先根据后序&#xff08;左右中&#xff09;确定顶点元素&#xff1b; 2. 根据顶点元素划分中序序列&#xff1b; 3. 根据划分中序序列中-左子树的长度&#xff0c;进…

半小时学会HTML5

一、了解几个概念 1、HTML定义 HTML是&#xff08;Hyper Text Markup Language&#xff09;超文本标记语言&#xff0c;超文本包含&#xff1a;文字、图片、音频、视频、动画等。 2、W3C 是什么&#xff1f; W3C 即&#xff08;World Wide Web Consortium&#xff09; 万维…

【性能测试】常见适用场景以及策略

面对日益复杂的业务场景和不同的系统架构&#xff0c;前期的需求分析和准备工作&#xff0c;需要耗费很多的时间。而不同的测试策略&#xff0c;也对我们的测试结果是否符合预期目标至关重要。 这篇博客&#xff0c;聊聊我个人对常见的性能测试策略的理解&#xff0c;以及它们…

RK3399 Android 10 Camera2保存录像时缩略图获取为空

RK3399 Android 10相机录像保存时无法获取缩略预览图 先找到录像点击按钮 //点击快门按钮时可以通过log打印看到停止录像的流程onShutterButtonClick() //这里主要看停止的流程即stop true时会进入onStopVideoRecording方法 public void onShutterButtonClick() {Log.d(TAG…

【HAL库】BMP180气压传感器+STM32,hal库移植

BMP180气压传感器STM32 1 导入.c.h文件&#xff08;不再赘述&#xff0c;详细见LED部分&#xff09;2 Cubemx配置3 修改 .h 文件4 测试 将BMP180从标准库移植到HAL库。模拟IIC。 极简工程代码如下&#xff1a; https://github.com/wyfroom/HAL_BMP180 该份代码硬件配置&#x…