常见知识点
# 在spring中怎么定义一个CustomScannerRegister?
在 Spring 中,您可以通过实现 org.springframework.context.annotation.ImportBeanDefinitionRegistrar
接口来定义一个自定义的扫描器注册器,从而向容器注册 bean。以下是一个简单的例子,展示如何实现这个接口来创建一个自定义的扫描器注册器:
javaCopy code
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class CustomScannerRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 在此处可以编写扫描逻辑,并使用 registry 注册 bean。
}
}
2
3
4
5
6
7
8
9
10
11
在这个示例中,我们实现了 ImportBeanDefinitionRegistrar
接口,并重写了 registerBeanDefinitions()
方法,该方法接收两个参数:AnnotationMetadata
和 BeanDefinitionRegistry
。在这个方法中,我们可以编写扫描逻辑,并使用 BeanDefinitionRegistry
实例注册 bean。例如,您可以使用 ClassPathBeanDefinitionScanner
扫描特定的包,并将扫描到的 bean 注册到容器中:
javaCopy code
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class CustomScannerRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
scanner.scan("com.example.custompackage");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
这个示例中,我们使用了 ClassPathBeanDefinitionScanner
扫描了包路径为 com.example.custompackage
的所有类,并将扫描到的 bean 注册到容器中。请注意,在您的项目中使用时,您需要修改包路径和扫描逻辑来适应您的项目。
# mybatis举例
这个接口的使用和@import注解一起使用。我们可以看看mybatis整合springboot中是如何实现这个ImportBeanDefinitionRegistrar
在使用@MapperScann() 意思是扫描指定的dao接口路径,我们来看看这个注解是如何使用的
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
}
2
3
4
5
6
7
我们可以看到这里使用@Import注解,里面有一个类叫做MapperScannerRegistrar.class
- 首先看看@Import注解,这个注解的作用如下·所示
@Import注解的作用是给容器中导入组件,回顾下我们给容器中注入组件的方式,可以通过Spring的xml配置方式,也可以通过注解,如@Component
等,也可以通过java配置类的方式给容器中导入注解,今天来介绍另一个注解,其作用也是给容器中导入组件。
也即是这个注解将我们MapperScannerRegistrar注入到我们的容器中。
- 我们再来看看这个MapperScannerRegistrar对象
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
/**
* {@inheritDoc}
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取了MapperScan注解上的属性
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry);
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
// ClassPathMapperScanner继承了spring的扫描器ClassPathBeanDefinitionScanner
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
// 给扫描器增加扫描路径
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("basePackages"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(
Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
.map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
scanner.registerFilters();
// 开始扫描
scanner.doScan(StringUtils.toStringArray(basePackages));
}
/**
* A {@link MapperScannerRegistrar} for {@link MapperScans}.
* @since 2.0.0
*/
static class RepeatingRegistrar extends MapperScannerRegistrar {
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScansAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
if (mapperScansAttrs != null) {
Arrays.stream(mapperScansAttrs.getAnnotationArray("value"))
.forEach(mapperScanAttrs -> registerBeanDefinitions(mapperScanAttrs, registry));
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
同时我们会发现上面的类经常会和以下的类结合在一起ClassPathBeanDefinitionScanner
:
# ClassPathBeanDefinitionScanner有什么用
ClassPathBeanDefinitionScanner是Spring框架中的一个组件,主要用于扫描指定路径下的类文件,自动将符合条件的类注册为Spring容器中的BeanDefinition。
具体来说,ClassPathBeanDefinitionScanner通常用于以下情况:
- 扫描指定包路径下的类文件,将其转换为BeanDefinition注册到Spring容器中,以便在需要时使用。
- 扫描类文件中的注解,将带有特定注解的类自动注册为BeanDefinition,简化了手动配置的过程。
- 扫描指定路径下的XML文件,将其中定义的Bean自动注册到Spring容器中,简化了手动配置的过程。
使用ClassPathBeanDefinitionScanner可以大大减少手动配置Bean的工作量,提高代码的开发效率和可维护性。同时,它也使得开发人员可以更加专注于业务逻辑的实现,而不是过多关注框架相关的细节。
# ClassPathBeanDefinitionScanner案例
在spring中我自定义了一个注解,让我想让spring能够自动扫描对应的包,然后将包下面使用了自定义注解的类加载到spring ioc中。
要使用ClassPathBeanDefinitionScanner将使用自定义注解的类注册到Spring容器中,可以按照以下步骤进行:
- 定义自定义注解
可以使用Java的元注解(@Target和@Retention)来定义一个注解,并添加相应的属性,例如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "";
}
2
3
4
5
- 创建扫描器并设置过滤器(重点)
创建一个ClassPathBeanDefinitionScanner实例,并设置TypeFilter,以便仅扫描带有自定义注解的类,例如:
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(applicationContext);
TypeFilter myAnnotationFilter = new AnnotationTypeFilter(MyAnnotation.class);
scanner.addIncludeFilter(myAnnotationFilter);
2
3
- 扫描指定的包路径
调用scanner的scan()方法,指定要扫描的包路径,例如:
scanner.scan("com.example");
在这个例子中,将扫描com.example包下的所有类文件,自动将带有@MyAnnotation注解的类注册为Spring的BeanDefinition。
- 在Spring容器中使用Bean
完成上述步骤后,使用带有@MyAnnotation注解的类的实例就可以在Spring容器中被自动注入或获取。
总的来说,通过定义自定义注解、创建扫描器并设置过滤器、扫描指定的包路径等步骤,可以使用ClassPathBeanDefinitionScanner将使用自定义注解的类注册到Spring容器中。这样可以大大提高开发效率和可维护性。
如果在spring中配置了
<context:component-scan base-package="com.example.demo" />
那么在ioc容器的初始化阶段就会调用beanFactoryPostProcessor阶段,就会采用classPathBeanDefinitionScanner进行包扫描,并将符合过滤条件的类注册到IOC容器内。就类似于mybatis的mapper注册器同样是继承ClassPastBeanDefinitionScanner,来进行对应路径下的包的扫描和definition的注册。
那么我们会什么还会使用ImportBeanDefinitionRegistrar呢?
原因如下:
如果你只需要自动将使用自定义注解的类加载到Spring容器中,而不需要对这些类进行其他的处理,那么就不需要使用ImportBeanDefinitionRegistrar。
ImportBeanDefinitionRegistrar接口是用于动态注册BeanDefinition的高级扩展点,它允许在BeanDefinition注册之前对BeanDefinition进行一些特殊处理,例如:修改BeanDefinition的属性、增加额外的元数据等等。它的主要作用是在@Configuration类中,通过实现ImportBeanDefinitionRegistrar接口,动态地向容器中注册BeanDefinition。
在使用自定义注解注册Bean时,如果只需要简单地将使用自定义注解的类加载到Spring容器中,而不需要对这些类进行特殊处理,那么就不需要使用ImportBeanDefinitionRegistrar接口。使用ClassPathBeanDefinitionScanner就足够了。
当然,如果你需要对自定义注解的类进行特殊处理,例如:根据注解的属性修改BeanDefinition的属性,那么可以考虑使用ImportBeanDefinitionRegistrar接口来完成这些操作。需要注意的是,这种方式比较复杂,需要实现一些接口,编写一些代码,因此只有在必要的情况下才需要使用。
那么实现这个接口有什么用呢?
通过@Import({ImportBeanDefinitionRegistrar.class}),可以让spring启动过程创建beanDefinition时调用registerBeanDefinitions方法,传入BeanDefinitionRegistry注册器,那么我们就可以自己设置规则,来往spring里面注册beanDefinition,从而做到往ioc容器中注册bean的过程
简单的说ImportBeanDefinitionRegistrar,可以让我们自己注册bean到spring ioc容器中
# 什么是AnnotationAttributes和AnnotationMetadata
AnnotationAttributes是Spring框架提供的一个工具类,用于方便地获取和操作注解中的属性。它提供了一些常用的方法,如获取字符串、布尔、整数等类型的属性值,也可以通过getAttribute方法获取Object类型的属性值,还可以判断注解中是否存在某个属性等。
AnnotationAttributes通常用于通过反射获取注解的属性及其值。在Spring中,通过AnnotationMetadata接口获取注解的属性及其值,它的实现类StandardAnnotationMetadata中提供了一个getAnnotationAttributes方法,用于获取指定注解的AnnotationAttributes对象。
AnnotationAttributes使得使用注解时更加方便,可以通过AnnotationAttributes对象获取注解中的属性值,并可以直接用于构建BeanDefinition等操作。同时,AnnotationAttributes提供了类型安全的访问方法,能够防止类型转换错误导致的异常。
两者有什么区别呢?
AnnotationMetadata和AnnotationAttributes是Spring框架中用于处理注解的两个重要接口,它们的区别如下:
- AnnotationMetadata是一个接口,它提供了访问类或方法上的注解及其属性的方法,可以获取注解的名称、属性及其值,还可以判断是否存在某个注解。AnnotationAttributes是AnnotationMetadata中的一个子接口,它提供了获取和操作注解属性的方法,可以获取注解属性的值、判断是否存在某个属性等。
- AnnotationMetadata通常用于获取注解的信息,例如:获取注解的名称、类型、属性值等。AnnotationAttributes则用于获取和操作注解的属性,例如:获取属性的值、判断属性是否存在、修改属性的值等。
- AnnotationMetadata提供了获取注解类型的方法,可以获取指定名称的注解类型,以及类或方法上所有注解的类型。AnnotationAttributes则没有获取注解类型的方法,它只能获取指定注解中的属性。
综上所述,AnnotationMetadata和AnnotationAttributes在处理注解时具有不同的功能和用途,AnnotationMetadata用于获取注解信息,而AnnotationAttributes用于获取和操作注解的属性。在Spring框架中,通常先使用AnnotationMetadata获取指定类或方法上的注解,再使用AnnotationAttributes获取注解的属性。
# maven中scope标签的含义
在 Maven 的依赖管理中,scope
标签用于指定依赖项在特定阶段的可见性和可用性。scope
可以设置为以下几个值:
compile
:默认值。依赖项在编译、测试和运行时均可用。provided
:依赖项在编译和测试时可用,但在运行时由运行环境提供。例如,Servlet API 就是一个provided
依赖项,因为它由 Web 容器提供。runtime
:依赖项在测试和运行时可用,但在编译时不可用。例如,JDBC 驱动程序就是一个runtime
依赖项,因为它只需要在运行时才需要。test
:依赖项仅在测试时可用,不会在编译或运行时包含在项目中。system
:依赖项类似于provided
,但需要手动提供jar
文件的路径。import
:仅用于<dependencyManagement>
部分,用于导入其他 Maven 项目的依赖管理信息。
通过使用 scope
标签,可以使 Maven 知道哪些依赖项是必需的,哪些是可选的,以及在不同的构建阶段哪些依赖项是需要的。这有助于管理项目的依赖项,确保只有必需的依赖项被包含在构建中,从而减少项目的大小和复杂性。
# 一些常用的工具类
RuntimeUtil
public class RuntimeUtil {
/**
* 获取当前cpu的数量
* @return
*/
public static int cpus(){
return Runtime.getRuntime().availableProcessors();
}
}
2
3
4
5
6
7
8
9
10
# bean的生命周期
在 Spring 中,Bean 的生命周期可以分为以下阶段:
- 实例化(Instantiation):Spring 根据配置文件中的信息,使用 Bean 工厂创建一个 Bean 的实例。
- 属性赋值(Populating Properties):Spring 将配置文件中的属性值和 Bean 的属性值进行绑定,也就是依赖注入。
- 初始化(Initialization):在 Bean 实例化完成并完成属性赋值之后,Spring 会调用 Bean 的初始化方法。Bean 可以通过实现 InitializingBean 接口或者在配置文件中指定 init-method 来定义初始化方法。
- 使用(In Use):Bean 可以被使用了。
- 销毁(Destruction):当 Bean 不再需要时,Spring 会调用 Bean 的销毁方法。Bean 可以通过实现 DisposableBean 接口或者在配置文件中指定 destroy-method 来定义销毁方法。
需要注意的是,只有在通过 Spring 容器获取 Bean 时,Bean 才会经过完整的生命周期,如果通过 new 关键字或其他方式创建 Bean,Spring 就无法控制 Bean 的生命周期。
此外,Spring 还提供了许多扩展 Bean 生命周期的方式,例如 BeanPostProcessor 接口和 BeanFactoryPostProcessor 接口等,它们可以在 Bean 的实例化和初始化过程中进行一些自定义的操作。
# 那么ImportBeanDefinitionRegistrar是处于哪一个阶段呢
ImportBeanDefinitionRegistrar 属于 Spring Bean 的注册阶段,而不是 Bean 的生命周期阶段。
在 Spring 中,Bean 的注册阶段是在 Bean 的实例化、属性赋值和初始化之前的一个阶段,通过 BeanDefinitionRegistry 接口向 Spring 容器中注册 Bean 定义,包括 Bean 的名称、类型、作用域、依赖关系等信息。这个阶段可以通过实现 BeanDefinitionRegistryPostProcessor 接口来进行自定义操作,例如修改或添加 Bean 的定义。
ImportBeanDefinitionRegistrar 接口是 BeanDefinitionRegistryPostProcessor 接口的子接口,它可以在注册 Bean 的同时,动态地注册其他的 Bean 定义。通常情况下,ImportBeanDefinitionRegistrar 接口是用来实现条件化注册 Bean 的场景,例如根据配置文件中的条件动态地注册一些 Bean。
需要注意的是,ImportBeanDefinitionRegistrar 接口的实现类会在 Bean 的注册阶段被调用,而不是 Bean 的生命周期阶段。在实现 ImportBeanDefinitionRegistrar 接口时,不能对已经存在的 Bean 进行修改或添加,否则会抛出异常。
在spring的启动过程中ImportBeanDefinitionRegistrar的registerBeanDefinitions方法只会调用一次,annotationMetadata可以获取注解的元信息
# 实现BeanPostProcessor有什么用?
BeanPostProcessor 接口是 Spring 框架中用于对 Bean 进行后置处理的一个扩展点,它可以在 Bean 的初始化前后对 Bean 进行一些自定义操作。BeanPostProcessor 接口属于 Bean 的初始化阶段,也就是 Bean 生命周期中的第三个阶段。
在 Bean 的初始化阶段,Spring 容器会调用实现了 BeanPostProcessor 接口的类的 postProcessBeforeInitialization() 方法,在 Bean 初始化之前对 Bean 进行自定义的操作。然后,Spring 容器会调用 Bean 的初始化方法(例如通过 @PostConstruct 注解指定的方法),完成 Bean 的初始化。最后,Spring 容器会调用实现了 BeanPostProcessor 接口的类的 postProcessAfterInitialization() 方法,在 Bean 初始化之后对 Bean 进行自定义的操作。
需要注意的是,BeanPostProcessor 接口中定义的方法会被所有的 Bean 实例共享,也就是说,如果一个 Bean 实现了 BeanPostProcessor 接口,那么它的 postProcessBeforeInitialization() 和 postProcessAfterInitialization() 方法会被所有的 Bean 实例调用,包括它自己。因此,在实现 BeanPostProcessor 接口时,需要判断当前处理的 Bean 是否是需要进行处理的目标 Bean。例如,可以通过 Bean 的名称、类型、注解等来判断当前 Bean 是否需要进行后置处理。
在 BeanPostProcessor 接口中的 postProcessAfterInitialization() 方法中,参数含义如下:
- Object bean:当前正在进行后置处理的 Bean 实例。
- String beanName:当前正在进行后置处理的 Bean 的名称。
这两个参数的作用分别是获取正在进行后置处理的 Bean 实例和 Bean 的名称,开发者可以在这个方法中对 Bean 进行自定义的后置处理,例如对 Bean 进行动态代理、AOP 切面等操作。
实现 BeanPostProcessor 接口的方式如下:
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 在 Bean 初始化之前进行自定义操作
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 在 Bean 初始化之后进行自定义操作
return bean;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
在这个实现中,我们可以在 postProcessAfterInitialization() 方法中对 Bean 进行一些自定义的操作。例如,我们可以对某个 Bean 进行动态代理,以实现 AOP 切面的功能:
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof UserService) {
// 对 UserService 进行动态代理,以实现 AOP 切面的功能
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(bean);
proxyFactory.addAdvice(new MyAdvice());
return proxyFactory.getProxy();
}
return bean;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
在这个实现中,我们判断当前的 Bean 是否是 UserService 类型,如果是,则对该 Bean 进行动态代理,并将代理后的 Bean 实例返回,以实现 AOP 切面的功能。其他类型的 Bean 则不进行处理,直接返回原始的 Bean 实例。
# 那么aop是怎么和beanPostProcessor结合起来的呢?
Spring 中的面向切面(AOP)功能是通过 BeanPostProcessor 接口实现的,但它并不仅仅是通过 BeanPostProcessor 接口实现的。
在 Spring 中,实现 AOP 功能有多种方式,其中最常见的方式是使用 AspectJ 注解和 Spring AOP 配置,这两种方式都可以实现 AOP 功能,但它们的实现原理是不同的。
使用 AspectJ 注解时,需要使用 AspectJ 提供的注解,例如 @Aspect、@Before、@After 等,Spring 容器会扫描带有这些注解的类,并自动生成代理类,将代理类应用到目标 Bean 上,从而实现 AOP 功能。
使用 Spring AOP 配置时,需要在 Spring 配置文件中定义切面、切入点、通知等元素,Spring 容器会根据这些元素生成代理类,并将代理类应用到目标 Bean 上,从而实现 AOP 功能。
在实现 AOP 功能时,BeanPostProcessor 接口主要用于对目标 Bean 进行增强操作,例如对目标 Bean 进行动态代理,以实现 AOP 切面的功能。但 BeanPostProcessor 接口并不是实现 AOP 的必需品,开发者可以使用其他方式来实现 AOP 功能,例如使用 AspectJ 注解或 Spring AOP 配置等。
# java中的随机数
在 Java 中,Random 类是用于生成随机数的类,可以使用它来生成伪随机数。
要使用 Random 类生成随机数,可以按照以下步骤进行操作:
- 创建 Random 对象。可以使用无参数构造函数来创建 Random 对象,也可以使用带有种子参数的构造函数来创建 Random 对象。如果不指定种子参数,则默认使用当前时间作为种子。
Random random = new Random(); // 创建 Random 对象
- 调用 Random 对象的方法来生成随机数。Random 类提供了多种方法来生成不同类型的随机数,例如生成整数、浮点数、布尔值等。
int randomInt = random.nextInt(100); // 生成 [0, 100) 之间的随机整数
double randomDouble = random.nextDouble(); // 生成 [0.0, 1.0) 之间的随机浮点数
boolean randomBoolean = random.nextBoolean(); // 生成随机布尔值
2
3
- 根据需要重复步骤 2 来生成多个随机数。
int[] randomInts = new int[10];
for (int i = 0; i < 10; i++) {
randomInts[i] = random.nextInt(100);
}
2
3
4
需要注意的是,Random 类生成的随机数是伪随机数,它们是根据某个算法计算出来的,而不是真正的随机数。如果需要更高质量的随机数,可以使用 java.security.SecureRandom 类来生成随机数。此外,还可以使用第三方库,例如 Apache Commons Math 库或 Google Guava 库,来生成更高质量的随机数。
# 为什么是伪随机数
伪随机数(Pseudo-Random Number)是由计算机程序生成的数值序列,看起来像是随机产生的数值序列,但实际上它们是由一个固定的算法计算出来的,因此不是真正的随机数。
在计算机科学中,真正的随机数是很难实现的,因为计算机是一台确定性的机器,它必须按照给定的算法执行指令。因此,计算机程序生成的数值序列通常是伪随机数,即看起来像是随机产生的数值序列,但实际上是由一个固定的算法计算出来的。这个算法称为随机数生成器(Random Number Generator,简称 RNG)。
由于伪随机数是由一个固定的算法计算出来的,因此它们具有以下特点:
- 可重复性:由于伪随机数是由一个固定的算法计算出来的,因此如果使用相同的算法和种子,就会生成相同的数值序列。
- 周期性:伪随机数的数值序列是有限的,当计算机执行的次数达到一定数量级时,数值序列就会重复。
- 随机性:虽然伪随机数不是真正的随机数,但是它们具有很高的随机性,能够满足绝大多数应用场景的要求。
在实际应用中,伪随机数广泛用于模拟和加密等领域。如果需要更高质量的随机数,可以使用真正的随机数源,例如物理随机数源,但是这通常需要特殊的硬件设备和算法支持。
# 计网
# IP
ip的分类,每个类别ip的范围,主机数量的范围,两种特殊的ip
广播的分类
单播、广播与多播的区别
什么是网段
关于多播(组播)
从 224.0.0.0 ~ 239.255.255.255 都是多播的可用范围,其划分为以下三类:
224.0.0.0 ~ 224.0.0.255 为预留的组播地址,只能在局域网中,路由器是不会进行转发的。
224.0.1.0 ~ 238.255.255.255 为用户可用的组播地址,可以用于 Internet 上。
239.0.0.0 ~ 239.255.255.255 为本地管理组播地址,可供内部网在内部使用,仅在特定的本地范围内有效。
- ip地址的分类有什么优点?
有什么缺点。由CIDR解决
CIDR的两种表现形式
(1)10.100.122.2/24
(2) 地址+子网掩码
为什么要区分网络号和主机号
子网掩码用于划分网络号和主机号,还可以用来划分子网
划分子网其实就是 ip分类+CIDR
划分后就成为:网络地址+子网网络地址+子网主机地址
举例:
假设对 C 类地址进行子网划分,网络地址 192.168.1.0,使用子网掩码 255.255.255.192 对其进行子网划分。
ip分类中的公有地址和私有地址
ip地址当中网络地址与路由控制关系
路由控制原理-》最长匹配原则
- 网络地址在路由器中的转发流程
- 主机 A 要发送一个 IP 包,其源地址是
10.1.1.30
和目标地址是10.1.2.10
,由于没有在主机 A 的路由表找到与目标地址10.1.2.10
相同的网络地址,于是包被转发到默认路由(路由器1
) - 路由器
1
收到 IP 包后,也在路由器1
的路由表匹配与目标地址相同的网络地址记录,发现匹配到了,于是就把 IP 数据包转发到了10.1.0.2
这台路由器2
- 路由器
2
收到后,同样对比自身的路由表,发现匹配到了,于是把 IP 包从路由器2
的10.1.2.1
这个接口出去,最终经过交换机把 IP 数据包转发到了目标主机
- 什么是环回地址
环回地址是不会流向网络的
环回地址是在同一台计算机上的程序之间进行网络通信时所使用的一个默认地址。
计算机使用一个特殊的 IP 地址 127.0.0.1 作为环回地址。与该地址具有相同意义的是一个叫做 localhost
的主机名。使用这个 IP 或主机名时,数据包不会流向网络。
# ip的分片与重组
# IP分片和重组
在计算机网络中,IP分片和重组是两个重要的概念。IP分片是指将一个IP数据报分成多个较小的IP分片进行传输,而IP重组则是指在接收端重新组合这些分散在不同IP分片中的原始IP数据报的过程。本文将详细介绍IP分片和重组的过程,以及它们的影响和应用。
# IP分片
当一个IP数据报在发送端超过了MTU(Maximum Transmission Unit,最大传输单元)的限制,会被分成多个IP分片进行传输。MTU是指链路层所能传输的最大数据帧的大小。在传输过程中,这些分片可能会经过不同的网络路径,并可能以不同的顺序到达接收端。因此,接收端需要将这些分片重新组合成原始的IP数据报。
IP分片是在网络层进行的,具体步骤如下:
- 发送端将要传输的IP数据报分成多个IP分片,每个分片的大小不超过MTU。
- 发送端在每个IP分片的IP头中添加标识符和片偏移字段,以便接收端能够将它们组合成原始的IP数据报。
- 发送端将每个IP分片单独传输,这些分片可能会经过不同的网络路径,并可能以不同的顺序到达接收端。
- 接收端接收到一个IP分片时,会先检查IP头中的标识符和片偏移字段,以确定这个分片属于哪个IP数据报,以及这个分片在原始IP数据报中的位置。
- 接收端将每个分片的有效负载(即不包括IP头)存储在缓冲区中,同时记录它们的偏移量和长度。
- 接收端检查缓冲区中是否已经接收到了原始IP数据报的所有分片。如果没有,接收端会等待其余的分片到达,并继续存储它们的有效负载。
- 一旦所有分片都到达,接收端会按照原始IP数据报中的片偏移字段,将它们组合成原始IP数据报。在组合过程中,接收端需要检查片偏移字段,以确保分片的顺序正确,并且没有丢失或重复的分片。
# IP重组
IP重组是在接收端重新组合分散在不同IP分片中的原始IP数据报的过程。当一个IP数据报在发送端超过了MTU的限制,会被分成多个IP分片进行传输。在传输过程中,这些分散的IP分片可能会以不同的顺序到达接收端,因此接收端需要将这些分片重新组合成原始的IP数据报。IP重组是在网络层进行的,具体步骤如下:
- 接收端接收到一个IP分片时,会先检查IP头中的标识符和片偏移字段,以确定这个分片属于哪个IP数据报,以及这个分片在原始IP数据报中的位置。
- 接收端将每个分片的有效负载(即不包括IP头)存储在缓冲区中,同时记录它们的偏移量和长度。
- 接收端检查缓冲区中是否已经接收到了原始IP数据报的所有分片。如果没有,接收端会等待其余的分片到达,并继续存储它们的有效负载。
- 一旦所有分片都到达,接收端会按照原始IP数据报中的片偏移字段,将它们组合成原始IP数据报。在组合过程中,接收端需要检查片偏移字段,以确保分片的顺序正确,并且没有丢失或重复的分片。
- 组合完成后,接收端将重组后的IP数据报交给上层协议进行处理,例如TCP或UDP协议。
# IP分片和重组的影响和应用
IP分片和重组会对网络性能和应用程序产生影响。IP分片可以在一定程度上提高网络的灵活性和可靠性,因为它可以处理各种MTU大小的网络,使得数据包可以经过不同的网络路径进行传输。但是,IP分片也会增加网络负担和延迟,因为它需要处理和组合分散的分片,并且如果分片丢失或重复,会导致原始IP数据报的丢失或损坏。
为了解决这个问题,TCP引入了MSS(Maximum Segment Size,最大报文段长度)来控制每个TCP报文段的大小,以确保它们不会被IP分片。这样,TCP可以确保传输的可靠性,并且在分片时不会丢失或重复分片。对于UDP协议,我们需要注意发送的数据包大小,尽量不要发送一个大于MTU的数据报文。
IP重组通常由操作系统内核处理,但是它也可能会由网络设备,如路由器和防火墙进行处理。在某些情况下,这些网络设备可能会丢弃IP分片,因为它们增加了网络负载并且可能导致性能下降。因此,应用程序和网络管理员应该尽可能避免使用IP分片和重组,以提高网络性能和可靠性。
# 总结
IP分片和重组是在网络层进行的,它们可以使得数据包可以经过不同的网络路径进行传输。IP分片可以在一定程度上提高网络的灵活性和可靠性,但是它也会增加网络负担和延迟,因为它需要处理和组合分散的分片,并且如果分片丢失或重复,会导致原始IP数据报的丢失或损坏。TCP引入了MSS来控制每个TCP报文段的大小,以确保它们不会被IP分片,以提高传输的可靠性,并且在分片时不会丢失或重复分片。UDP协议应该尽量避免发送一个大于MTU的数据报文,以避免IP分片和重组的影响。
总之,了解IP分片和重组的工作原理和影响,对于设计网络应用程序和网络架构是非常重要的。在实际应用中,应该尽量避免使用IP分片和重组,以提高网络性能和可靠性。
# IPV6
结构
优点
如何进行分类的。
# DNS
DNS(Domain Name System,域名系统)是互联网中用于将域名解析为IP地址的分布式数据库系统,也是互联网中最重要的基础设施之一。在本文中,我们将介绍DNS的基本概念、工作原理和常见问题,以及在面试中可能会被问到的相关问题。
# 1. DNS基础概念
DNS是一种分布式数据库系统,它将域名解析为IP地址,从而允许用户通过易于记忆的名称来访问互联网上的资源。每个域名都可以看作是一个树形结构,根域名在顶部,其下有多个子域名,最终到达域名的末尾,也称为主机名。
在DNS中,每个域名都映射到一个或多个IP地址,这些IP地址可以是IPv4地址或IPv6地址。域名的映射关系存储在分布式数据库中,由多个DNS服务器共同维护。为了提高性能和可靠性,DNS服务器通常会采用多级缓存机制,以避免频繁地查询其他DNS服务器。
# 2. DNS工作原理
DNS的工作原理可以分为以下几个步骤:
- 应用程序向本地DNS服务器发送域名查询请求。
- 如果本地DNS服务器缓存了该域名的映射关系,则直接返回该映射关系,否则将查询请求转发到根DNS服务器。
- 根DNS服务器根据查询请求的顶级域名(如.com、.cn等)返回对应的顶级域名服务器地址。
- 本地DNS服务器向顶级域名服务器发送查询请求。
- 顶级域名服务器返回下一级域名服务器地址,本地DNS服务器再向下一级域名服务器发送查询请求,如此往复,直到查询到域名的映射关系。
- 本地DNS服务器将查询结果返回给应用程序,并将查询结果缓存起来,以便下次查询时直接返回。
# 3. 常见DNS问题
在面试中,可能会被问到一些与DNS相关的问题,以下是一些常见的问题及其答案:
- DNS服务器是如何实现负载均衡的?
DNS服务器可以采用多种负载均衡算法,如轮询、随机、最少连接数等。其中,轮询算法是最常用的一种算法,它将查询请求平均分配到每个DNS服务器上。
- 为什么会有负载均衡
使用负载均衡是为了提高服务的可用性和可靠性,减轻单个服务器的负载压力,同时提高服务的性能和吞吐量。
对于DNS服务器,由于其工作方式和负载均衡的目标相似,因此通常都是以集群的方式部署,即将多台服务器组成一个集群来共同处理查询请求。负载均衡器可以将查询请求分发到不同的DNS服务器上,从而使得整个DNS服务的负载得到均衡,并提高DNS服务的可用性和可靠性。
在集群中,各个DNS服务器之间的数据同步也是非常重要的,确保各个服务器之间的数据保持一致。常见的同步方式包括:主从同步、多主同步、任意节点同步等。
因此,对于DNS服务器而言,使用负载均衡是非常常见的做法,通过集群的方式来提高服务的可用性和可靠性,同时提高服务的性能和吞吐量。
- DNS缓存是如何工作的?
DNS缓存可以分为两种类型:本地DNS服务器缓存和客户端缓存。本地DNS服务器缓存用于缓存查询结果,以避免频繁地查询其他DNS服务器,客户端缓存则用于缓存应用程序查询结果,以便快速访问相同的域名。当DNS服务器查询一个域名时,它会首先在本地DNS服务器缓存中查找,如果没有找到,则会向其他DNS服务器查询。查询结果会被缓存一段时间,以避免重复查询。
- DNS有哪些记录类型?
DNS记录类型包括:A记录、AAAA记录、CNAME记录、MX记录、TXT记录等。其中,A记录用于将域名解析为IPv4地址,AAAA记录用于将域名解析为IPv6地址,CNAME记录用于将一个域名映射到另一个域名,MX记录用于指定域名的邮件服务器,TXT记录用于存储任意文本信息。
- DNS解析过程中可能出现哪些问题?
DNS解析过程中可能出现的问题包括:DNS服务器故障、DNS缓存过期、域名不存在等。当DNS服务器故障时,查询请求无法得到响应,需要等待DNS服务器恢复正常;当DNS缓存过期时,查询结果可能已经失效,需要重新查询;当域名不存在时,查询请求会返回错误信息,应用程序需要做相应处理。
# 4. 总结
DNS是互联网中最重要的基础设施之一,它通过将域名解析为IP地址,允许用户通过易于记忆的名称来访问互联网上的资源。DNS的工作原理包括:应用程序向本地DNS服务器发送查询请求,本地DNS服务器根据查询请求向其他DNS服务器查询域名的映射关系,并将查询结果缓存起来。在面试中,可能会被问到与DNS相关的问题,需要掌握DNS的基本概念、工作原理和常见问题,以便应对面试考验。
# ARP
# RARP
# DHCP
- DHCP discover
client 68 server 67
- DHCP offer
配置信息、IP地址、子网掩码、地址租期、默认网关、DNS服务器
# ping
涉及协议:ICMP
主要功能:确认 IP 包是否成功送达目标地址、报告发送过程中 IP 包被废弃的原因和改善网络设置等。
# tranceroute
traceroute 的第一个作用就是故意设置特殊的 TTL,来追踪去往目的地时沿途经过的路由器。
traceroute 的参数指向某个目的 IP 地址:
traceroute 192.168.1.100
可以知道沿途的所有机器的ip地址
源主机会发送ICMP请求报文,要求TTL从1逐渐增大,目标端口设置为一个不可能存在的值。
如果源主机收到了ICMP超时消息,那么就表示是ttl过期,我们可以将ttl增大1,继续发送icmp请求报文
如果返回的是端口不可达,那就表示我们成功找到了目标主机,程序停止。
第二个作用:
traceroute 还有一个作用是故意设置不分片,从而确定路径的 MTU。
# NAT
# NATP
# IGMP
# ICMP
在ip包发送的过程中,如果ip没有成功到达目的地值,那么ICMP就负责将ip包没有送达的原因进行通过
数据包的格式大概如图所示
ICMP包实际上是IP包,工作在网络层。
ICMP通常有两种类型:
- 查询报文类型
8 回送请求 0回送应答
一般通信作用在主机和路由器,判断所发送的数据包是否已经成功到达对端的一种消息,ping命令就是利用了这个查询报文类型。
相比于传统的格式,这里还多了两个字段,标识符(用于表示是主机哪个)、序号(每发送一次,就会给序列号加一,这样就可以约人网络包是否有丢失)
在数据选项中,ping还会存放发送请求的时间值,来计算往返时间。
- 差错报文类型
3 目标不可达
当一个路由器发现其所发送的数据包无法找到对应的目标时,就会向源主机发送一条目标不可到达的信息,其中会在代码字段包含具体不可达的原因:
- 网络不可达代码为 0
IP 地址是分为网络号和主机号的,所以当路由器中的路由器表匹配不到接收方 IP 的网络号,就通过 ICMP 协议以网络不可达(Network Unreachable)的原因告知主机。
自从不再有网络分类以后,网络不可达也渐渐不再使用了。
- 主机不可达代码为 1
当路由表中没有该主机的信息,或者该主机没有连接到网络,那么会通过 ICMP 协议以主机不可达(Host Unreachable)的原因告知主机。
- 协议不可达代码为 2
当主机使用 TCP 协议访问对端主机时,能找到对端的主机了,可是对端主机的防火墙已经禁止 TCP 协议访问,那么会通过 ICMP 协议以协议不可达的原因告知主机。
#
- 端口不可达代码为 3
当主机访问对端主机 8080 端口时,这次能找到对端主机了,防火墙也没有限制,可是发现对端主机没有进程监听 8080 端口,那么会通过 ICMP 协议以端口不可达的原因告知主机。
- 需要进行分片但设置了不分片位代码为 4
发送端主机发送 IP 数据报时,将 IP 首部的分片禁止标志位设置为1。根据这个标志位,途中的路由器遇到超过 MTU 大小的数据包时,不会进行分片,而是直接抛弃。
随后,通过一个 ICMP 的不可达消息类型,代码为 4 的报文,告知发送端主机。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
4 原点抑制
在使用低速广域线路的情况下,连接 WAN 的路由器可能会遇到网络拥堵的问题。
ICMP
原点抑制消息的目的就是为了缓和这种拥堵情况。
当路由器向低速线路发送数据时,其发送队列的缓存变为零而无法发送出去时,可以向 IP 包的源地址发送一个 ICMP 原点抑制消息。
收到这个消息的主机借此了解在整个线路的某一处发生了拥堵的情况,从而增大 IP 包的传输间隔,减少网络拥堵的情况。
然而,由于这种 ICMP 可能会引起不公平的网络通信,一般不被使用。
5 重定向
如果路由器发现发送端主机使用了「不是最优」的路径发送数据,那么它会返回一个 ICMP 重定向消息给这个主机。
在这个消息中包含了最合适的路由信息和源数据。这主要发生在路由器持有更好的路由信息的情况下。路由器会通过这样的 ICMP 消息告知发送端,让它下次发给另外一个路由器。
好比,小林本可以过条马路就能到的地方,但小林不知道,所以绕了一圈才到,后面小林知道后,下次小林就不会那么傻再绕一圈了。
11 超时消息
IP 包中有一个字段叫做 TTL
(Time To Live
,生存周期),它的值随着每经过一次路由器就会减 1,直到减到 0 时该 IP 包会被丢弃。
此时,路由器将会发送一个 ICMP 超时消息给发送端主机,并通知该包已被丢弃。
设置 IP 包生存周期的主要目的,是为了在路由控制遇到问题发生循环状况时,避免 IP 包无休止地在网络上被转发。