深入解析:手写MyBatis第111弹:Spring Boot自定义注解@MybatisMapperScan注解深度解析:从注解定义到接口代理的完整实现

news/2025/11/13 19:09:19/文章来源:https://www.cnblogs.com/yangykaifa/p/19219288

自定义@MybatisMapperScan注解深度解析:从注解定义到接口代理的完整实现

「Spring Boot自定义注解黑魔法:手写@MybatisMapperScan+MapperScannerRegistrar原理+接口代理衔接全揭秘」

自定义注解:Spring生态的扩展艺术

在Spring Boot生态中,自定义注解是框架扩展的重要手段。通过模仿官方@MapperScan注解的实现机制,我们可以创建功能更专注、语义更明确的@MybatisMapperScan注解。这种深度定制不仅体现了对框架原理的理解,更是企业级开发中的常见需求。

目录

自定义@MybatisMapperScan注解深度解析:从注解定义到接口代理的完整实现

自定义注解:Spring生态的扩展艺术

@MybatisMapperScan注解设计

注解的元数据定义

重复注解支持

MybatisMapperScannerRegistrar实现原理

ImportBeanDefinitionRegistrar的作用机制

自定义扫描器MybatisClassPathMapperScanner

扫描器的核心实现

自定义MapperFactoryBean实现

增强的MapperFactoryBean

Spring扫描器与MyBatis接口代理的衔接

代理创建的完整链路

类型匹配与接口识别

Bean定义的后处理

使用示例与配置

基础使用方式

高级配置示例

测试与验证

单元测试

集成测试

总结


  (❁´◡`❁)您的点赞➕评论➕收藏⭐是作者创作的最大动力

支持我:点赞+收藏⭐️+留言欢迎留言讨论

(源码 + 调试运行 + 问题答疑)

 有兴趣可以联系我。文末有免费源码

免费获取源码。

更多内容敬请期待。如有需要可以联系作者免费送

更多源码定制,项目修改,项目二开可以联系作者
点击可以进行搜索(每人免费送一套代码):千套源码目录(点我)

2025元旦源码免费送(点我)

我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。

@MybatisMapperScan注解设计

注解的元数据定义

自定义注解需要精心设计元数据,既要满足功能需求,又要保持与Spring生态的一致性:

 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 @Documented
 @Import(MybatisMapperScannerRegistrar.class)
 @Repeatable(MybatisMapperScans.class)
 public @interface MybatisMapperScan {
     /**
      * 要扫描的包路径(别名)
      */
     String[] value() default {};
     /**
      * 要扫描的基础包名
      */
     String[] basePackages() default {};
     /**
      * 指定基础包类
      */
     Class[] basePackageClasses() default {};
     /**
      * Bean名称生成器
      */
     Class nameGenerator() default BeanNameGenerator.class;
     /**
      * 注解过滤条件
      */
     Class annotationClass() default Annotation.class;
     /**
      * 标记接口过滤条件
      */
     Class markerInterface() default Class.class;
     /**
      * SQL会话工厂Bean名称
      */
     String sqlSessionFactoryRef() default "";
     /**
      * SQL会话模板Bean名称
      */
     String sqlSessionTemplateRef() default "";
     /**
      * 自定义Mapper工厂Bean
      */
     Class factoryBean() default MapperFactoryBean.class;
     /**
      * 自定义属性:是否启用缓存
      */
     boolean enableCache() default true;
     /**
      * 自定义属性:执行器类型
      */
     ExecutorType executorType() default ExecutorType.SIMPLE;
     /**
      * 自定义属性:Mapper接口命名模式
      */
     String namePattern() default "*Mapper";
 }
重复注解支持

为了支持在同一元素上多次使用注解,需要定义容器注解:

 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 @Documented
 public @interface MybatisMapperScans {
     MybatisMapperScan[] value();
 }

MybatisMapperScannerRegistrar实现原理

ImportBeanDefinitionRegistrar的作用机制

MybatisMapperScannerRegistrar是实现自定义注解扫描的核心,它通过Spring的ImportBeanDefinitionRegistrar接口在配置类处理阶段介入:

 public class MybatisMapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
     private ResourceLoader resourceLoader;
     @Override
     public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                       BeanDefinitionRegistry registry) {
         // 获取@MybatisMapperScan注解属性
         AnnotationAttributes mapperScanAttrs = AnnotationAttributes
             .fromMap(importingClassMetadata.getAnnotationAttributes(MybatisMapperScan.class.getName()));
         if (mapperScanAttrs != null) {
             registerBeanDefinitions(mapperScanAttrs, registry);
         }
         // 处理重复注解
         AnnotationAttributes mapperScansAttrs = AnnotationAttributes
             .fromMap(importingClassMetadata.getAnnotationAttributes(MybatisMapperScans.class.getName()));
         if (mapperScansAttrs != null) {
             AnnotationAttributes[] annotations = mapperScansAttrs.getAnnotationArray("value");
             for (AnnotationAttributes annotation : annotations) {
                 registerBeanDefinitions(annotation, registry);
             }
         }
     }
     private void registerBeanDefinitions(AnnotationAttributes annoAttrs,
                                        BeanDefinitionRegistry registry) {
         // 创建自定义扫描器
         MybatisClassPathMapperScanner scanner = new MybatisClassPathMapperScanner(registry);
         // 设置资源加载器
         if (this.resourceLoader != null) {
             scanner.setResourceLoader(this.resourceLoader);
         }
         // 配置扫描器参数
         Class 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);
         }
         // 设置自定义属性
         scanner.setEnableCache(annoAttrs.getBoolean("enableCache"));
         scanner.setExecutorType((ExecutorType) annoAttrs.get("executorType"));
         scanner.setNamePattern(annoAttrs.getString("namePattern"));
         // 设置SQL会话相关引用
         String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
         if (StringUtils.hasText(sqlSessionFactoryRef)) {
             scanner.setSqlSessionFactoryBeanName(sqlSessionFactoryRef);
         }
         String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
         if (StringUtils.hasText(sqlSessionTemplateRef)) {
             scanner.setSqlSessionTemplateBeanName(sqlSessionTemplateRef);
         }
         // 设置Mapper工厂Bean
         Class mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
         if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
             scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);
         }
         // 注册类型过滤器
         scanner.registerFilters();
         // 执行扫描
         List basePackages = new ArrayList<>();
         // 处理value属性
         basePackages.addAll(Arrays.asList(annoAttrs.getStringArray("value")));
         // 处理basePackages属性
         basePackages.addAll(Arrays.asList(annoAttrs.getStringArray("basePackages")));
         // 处理basePackageClasses属性
         for (Class clazz : annoAttrs.getClassArray("basePackageClasses")) {
             basePackages.add(ClassUtils.getPackageName(clazz));
         }
         // 如果没有指定包路径,使用配置类所在包
         if (basePackages.isEmpty()) {
             String className = importingClassMetadata.getClassName();
             basePackages.add(ClassUtils.getPackageName(className));
         }
         // 执行扫描
         scanner.scan(basePackages.toArray(new String[0]));
     }
     @Override
     public void setResourceLoader(ResourceLoader resourceLoader) {
         this.resourceLoader = resourceLoader;
     }
 }

自定义扫描器MybatisClassPathMapperScanner

扫描器的核心实现

MybatisClassPathMapperScanner继承自Spring的ClassPathBeanDefinitionScanner,重写了扫描逻辑以适应MyBatis的特殊需求:

 public class MybatisClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
     private Class mapperFactoryBeanClass = MybatisMapperFactoryBean.class;
     private String sqlSessionFactoryBeanName;
     private String sqlSessionTemplateBeanName;
     private boolean enableCache = true;
     private ExecutorType executorType = ExecutorType.SIMPLE;
     private String namePattern = "*Mapper";
     public MybatisClassPathMapperScanner(BeanDefinitionRegistry registry) {
         super(registry, false);
     }
     @Override
     public Set doScan(String... basePackages) {
         // 调用父类扫描方法获取候选Bean定义
         Set beanDefinitions = super.doScan(basePackages);
         if (beanDefinitions.isEmpty()) {
             logger.warn("No MyBatis mapper was found in '" +
                        Arrays.toString(basePackages) + "' package. " +
                        "Please check your configuration and name pattern: " + namePattern);
         } else {
             // 处理扫描到的Bean定义
             processBeanDefinitions(beanDefinitions);
             if (logger.isInfoEnabled()) {
                 logger.info("Found " + beanDefinitions.size() + " MyBatis mappers: " +
                            beanDefinitions.stream()
                                .map(BeanDefinitionHolder::getBeanName)
                                .collect(Collectors.joining(", ")));
             }
         }
         return beanDefinitions;
     }
     /**
      * 重写候选组件检查逻辑,支持自定义命名模式
      */
     @Override
     protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
         // 调用父类检查
         boolean isCandidate = super.isCandidateComponent(beanDefinition);
         if (isCandidate) {
             // 应用自定义命名模式过滤
             String beanClassName = beanDefinition.getBeanClassName();
             String simpleName = ClassUtils.getShortName(beanClassName);
             // 使用Ant风格路径匹配
             AntPathMatcher pathMatcher = new AntPathMatcher();
             if (!pathMatcher.match(namePattern, simpleName)) {
                 if (logger.isDebugEnabled()) {
                     logger.debug("Skipping " + beanClassName + " as it doesn't match name pattern: " + namePattern);
                 }
                 return false;
             }
         }
         return isCandidate;
     }
     /**
      * 处理Bean定义,设置MapperFactoryBean等属性
      */
     private void processBeanDefinitions(Set beanDefinitions) {
         for (BeanDefinitionHolder holder : beanDefinitions) {
             GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
             // 设置Mapper接口类作为构造参数
             definition.getConstructorArgumentValues()
                 .addGenericArgumentValue(definition.getBeanClassName());
             // 设置自定义MapperFactoryBean
             definition.setBeanClass(this.mapperFactoryBeanClass);
             // 设置自定义属性
             definition.getPropertyValues().add("enableCache", this.enableCache);
             definition.getPropertyValues().add("executorType", this.executorType);
             // 设置SQL会话工厂引用
             if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
                 definition.getPropertyValues().add("sqlSessionFactory",
                     new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
             }
             // 设置SQL会话模板引用
             if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
                 definition.getPropertyValues().add("sqlSessionTemplate",
                     new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
             }
         }
     }
     // 注册类型过滤器
     public void registerFilters() {
         boolean acceptAllInterfaces = true;
         // 如果指定了注解类,添加包含过滤器
         if (this.annotationClass != null) {
             addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
             acceptAllInterfaces = false;
         }
         // 如果指定了标记接口,添加包含过滤器
         if (this.markerInterface != null) {
             addIncludeFilter(new AssignableTypeFilter(this.markerInterface));
             acceptAllInterfaces = false;
         }
         // 如果没有指定过滤条件,接受所有接口
         if (acceptAllInterfaces) {
             addIncludeFilter(new TypeFilter() {
                 @Override
                 public boolean match(MetadataReader metadataReader,
                                    MetadataReaderFactory metadataReaderFactory) throws IOException {
                     return true;
                 }
             });
         }
         // 排除package-info.java
         addExcludeFilter(new TypeFilter() {
             @Override
             public boolean match(MetadataReader metadataReader,
                                MetadataReaderFactory metadataReaderFactory) throws IOException {
                 String className = metadataReader.getClassMetadata().getClassName();
                 return className.endsWith("package-info");
             }
         });
     }
     // Getter和Setter方法
     public void setEnableCache(boolean enableCache) {
         this.enableCache = enableCache;
     }
     public void setExecutorType(ExecutorType executorType) {
         this.executorType = executorType;
     }
     public void setNamePattern(String namePattern) {
         this.namePattern = namePattern;
     }
 }

自定义MapperFactoryBean实现

增强的MapperFactoryBean

通过自定义MybatisMapperFactoryBean,我们可以为Mapper接口提供额外的配置选项:

 public class MybatisMapperFactoryBean extends MapperFactoryBean {
     private boolean enableCache = true;
     private ExecutorType executorType = ExecutorType.SIMPLE;
     public MybatisMapperFactoryBean() {
         super();
     }
     public MybatisMapperFactoryBean(Class mapperInterface) {
         super(mapperInterface);
     }
     @Override
     protected void checkDaoConfig() {
         super.checkDaoConfig();
         // 应用自定义配置
         applyCustomConfiguration();
     }
     private void applyCustomConfiguration() {
         SqlSessionTemplate sqlSessionTemplate = (SqlSessionTemplate) getSqlSession();
         Configuration configuration = sqlSessionTemplate.getConfiguration();
         // 应用缓存配置
         MappedStatement mappedStatement = getMappedStatement();
         if (mappedStatement != null && !enableCache) {
             mappedStatement.getCache(); // 触发缓存初始化
             // 禁用缓存的具体逻辑
             disableCacheForMappedStatement(mappedStatement);
         }
         // 应用执行器类型配置
         if (executorType != ExecutorType.SIMPLE) {
             // 这里可以设置执行器类型,但需要注意线程安全问题
             logger.debug("Custom executor type configured: " + executorType);
         }
     }
     private MappedStatement getMappedStatement() {
         try {
             String statementName = getMapperInterface().getName() + ".selectAll";
             return getSqlSession().getConfiguration().getMappedStatement(statementName);
         } catch (Exception e) {
             return null;
         }
     }
     private void disableCacheForMappedStatement(MappedStatement mappedStatement) {
         // 禁用缓存的实现逻辑
         // 注意:这需要访问MyBatis内部API,实际使用时需要谨慎
         try {
             Field cacheField = MappedStatement.class.getDeclaredField("cache");
             cacheField.setAccessible(true);
             cacheField.set(mappedStatement, null);
         } catch (Exception e) {
             logger.warn("Failed to disable cache for mapper: " + getMapperInterface().getName(), e);
         }
     }
     // Getter和Setter方法
     public void setEnableCache(boolean enableCache) {
         this.enableCache = enableCache;
     }
     public void setExecutorType(ExecutorType executorType) {
         this.executorType = executorType;
     }
 }

Spring扫描器与MyBatis接口代理的衔接

代理创建的完整链路

理解Spring扫描器如何与MyBatis接口代理衔接是掌握自定义注解的关键:

 // 1. Spring容器启动阶段
 @ComponentScan → 发现@MybatisMapperScan → MybatisMapperScannerRegistrar.registerBeanDefinitions()
 ​
 // 2. Bean定义注册阶段
 MybatisClassPathMapperScanner.doScan() → 发现Mapper接口 → 创建BeanDefinition → 设置MybatisMapperFactoryBean
 ​
 // 3. Bean实例化阶段
 Spring容器getBean() → MybatisMapperFactoryBean.getObject() → SqlSession.getMapper() → MapperProxyFactory.newInstance()
 ​
 // 4. 代理创建阶段
 MapperProxyFactory.newInstance() → JDK动态代理 → MapperProxy拦截器 → 返回代理对象
类型匹配与接口识别
Spring扫描器通过类型过滤器识别Mapper接口:
 // 在MybatisClassPathMapperScanner中
 @Override
 protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
     return beanDefinition.getMetadata().isInterface() &&
            beanDefinition.getMetadata().isIndependent() &&
            super.isCandidateComponent(beanDefinition);
 }
Bean定义的后处理

在Bean定义注册后,Spring会进行后处理以确保配置正确:

 public class MybatisMapperPostProcessor implements BeanDefinitionRegistryPostProcessor {
     @Override
     public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
         // 验证所有Mapper Bean定义的正确性
         String[] beanNames = registry.getBeanDefinitionNames();
         for (String beanName : beanNames) {
             BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
             if (isMapperBeanDefinition(beanDefinition)) {
                 validateMapperBeanDefinition(beanName, beanDefinition);
             }
         }
     }
     private boolean isMapperBeanDefinition(BeanDefinition beanDefinition) {
         return beanDefinition.getBeanClassName() != null &&
                beanDefinition.getBeanClassName().contains("MybatisMapperFactoryBean");
     }
     private void validateMapperBeanDefinition(String beanName, BeanDefinition beanDefinition) {
         // 验证Bean定义的完整性
         if (!beanDefinition.hasConstructorArgumentValues()) {
             throw new BeanDefinitionStoreException(
                 "MyBatis mapper bean definition must have constructor arguments: " + beanName);
         }
     }
 }

使用示例与配置

基础使用方式
@SpringBootApplication
@MybatisMapperScan(basePackages = "com.example.mapper",namePattern = "*Mapper",enableCache = true,executorType = ExecutorType.REUSE
)
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
高级配置示例
@Configuration
@MybatisMapperScan(value = "com.example.dao",basePackageClasses = {UserMapper.class, OrderMapper.class},sqlSessionFactoryRef = "sqlSessionFactory",annotationClass = Repository.class,enableCache = false,executorType = ExecutorType.BATCH,namePattern = "*DAO"
)
public class MybatisConfig {@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {// 自定义SqlSessionFactory配置SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource);return sessionFactory.getObject();}
}

测试与验证

单元测试
@SpringBootTest
class MybatisMapperScanTest {@Autowiredprivate ApplicationContext applicationContext;@Testvoid testMapperBeansRegistered() {// 验证Mapper Bean是否正确注册String[] beanNames = applicationContext.getBeanNamesForType(UserMapper.class);assertThat(beanNames).hasSize(1);UserMapper userMapper = applicationContext.getBean(UserMapper.class);assertThat(userMapper).isNotNull();assertThat(AopUtils.isJdkDynamicProxy(userMapper)).isTrue();}@Testvoid testCustomPropertiesApplied() {// 验证自定义属性是否生效MybatisMapperFactoryBean factoryBean = applicationContext.getBean(MybatisMapperFactoryBean.class);assertThat(factoryBean).isNotNull();}
}
集成测试
 @DataJdbcTest
 @MybatisMapperScan(basePackages = "com.example.mapper")
 class MybatisMapperScanIntegrationTest {
     @Autowired
     private UserMapper userMapper;
     @Test
     void testMapperFunctionality() {
         // 测试Mapper的实际功能
         User user = userMapper.findById(1L);
         assertThat(user).isNotNull();
     }
 }

总结

通过实现自定义@MybatisMapperScan注解,我们深入掌握了Spring注解扩展机制:

  1. 注解设计:合理设计注解元数据,保持与Spring生态的一致性

  2. 注册机制:通过ImportBeanDefinitionRegistrar实现配置类处理

  3. 扫描逻辑:自定义扫描器实现精确的接口识别和过滤

  4. 代理衔接:理解Spring Bean创建与MyBatis代理的完整链路

这种深度定制能力让我们能够根据具体业务需求创建更专注、更强大的开发工具,是框架高级使用的标志性技能。


(❁´◡`❁)您的点赞➕评论➕收藏⭐是作者创作的最大动力

支持我:点赞+收藏⭐️+留言欢迎留言讨论

(源码 + 调试运行 + 问题答疑)

 有兴趣可以联系我。文末有免费源码

学习知识需费心,
整理归纳更费神。
源码免费人人喜,
码农福利等你领!

常来我家多看看,
网址:扣棣编程
感谢支持常陪伴,
点赞关注别忘记!

山高路远坑又深,
大军纵横任驰奔,
谁敢横刀立马行?
唯有点赞+关注成!

往期文章推荐:

基于Springboot + vue实现的学生宿舍信息管理系统
免费获取宠物商城源码--SpringBoot+Vue宠物商城网站系统
【2025小年源码免费送】

⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇点击此处获取源码⬇⬇⬇⬇⬇⬇⬇⬇⬇

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

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

相关文章

Imbalance

Background Special for beginners, _ Description 上周 DB(Dream Bear) 做了均衡区间,但是它没过。 于是 DB 急了,就打算出一道 《非均衡区间》,但是求非均衡区间只要 \(n^2 − ans\) (均衡区间数) 即可。 出了…

2025 年 11 月展厅设计公司权威推荐榜:企业展厅、校史馆、博物馆、多媒体数字及VR线上虚拟展厅设计厂家精选

2025 年 11 月展厅设计公司权威推荐榜:企业展厅、校史馆、博物馆、多媒体数字及VR线上虚拟展厅设计厂家精选 随着数字化浪潮的深入推进,展厅设计行业正经历着前所未有的变革。从传统的实体展馆到如今的多媒体数字展厅…

点赞!开幕式背后的云力量!

粤港澳大湾区华光璀璨 “同心圆”水舞台如梦似幻 三地健儿齐聚 激情与梦想在此交汇 11月9日,广州奥体中心 第十五届全国运动会 盛大开幕这是一场展现 “科技、绿色、融合、人文”的 体育盛会 中国电信天翼云 作为十五…

#20232329 2025-2026-1 《网络与系统攻防技术》 实验六实验报告

#20232329 2025-2026-1 《网络与系统攻防技术》 实验六实验报告Metasploit攻击渗透实践 1.实验内容 下载官方靶机Metasploitable2,完成下面实验内容。 (1)前期渗透 (2)Vsftpd源码包后门漏洞(21端口) (3)SambaM…

易路AI人才罗盘:点亮组织内部的人才“星空”,让每一次人才决策都精准有据

引言:HR的世纪难题——“我们的人才究竟在哪里?” 当业务部门火急火燎地提出需求:“我们需要一位情商高、能团结队伍、激发团队斗志的HRBP负责人”,或者当公司战略决定“明年在德国建立营销中心,谁适合做第一任一…

11.13 比赛总结

比赛情况 今天又双叒叕犯了低级失误,\(A\) 题有个地方忘记取模爆int了,\(C\) 题数组开小了。

壅土(拼音:yōng tǔ)

壅土(拼音:yōng tǔ)是一个多义词,主要包含以下三层含义: 1. 堆积的泥土指长期积聚形成的泥土堆。例句:唐柳宗元《兴州江运记》:"于是决去壅土,疏导江涛。" 2. 农业培土技术(最常见用法)在农作物…

P14463 【MX-S10-T4】『FeOI-4』呼吸之野

P14463 【MX-S10-T4】『FeOI-4』呼吸之野 P14463 【MX-S10-T4】『FeOI-4』呼吸之野 - 洛谷 (luogu.com.cn) Solution 大战此题 6h。 判定中位数 \(\ge x\) 有经典套路:把 \(\ge x\) 的位置看作 \(1\),\(<x\) 的位…

业务用例的四个核心要素 - f

一、四个核心要素完整拆解 1. 系统边界(已明确)定义:划分“采购系统”与外部环境的范围,明确哪些功能属于系统内、哪些是外部交互对象。 示例:采购系统内部包含库存检查、订单生成、库存更新功能;外部是供应商、…

20232322 2025-2026-1 《网络与系统攻防技术》实验五实验报告

一.实验内容DNS与IP信息搜集分析; 从微信中获取好友IP地址与地理位置; 通过nmap和Nessus对靶机环境扫描探测、漏洞评估与攻击路径分析; 搜索个人网上足迹与高级搜索技能实践。二.实验目的 学习一系列安全工具和技术…

网易梦幻事业部游戏测试开发外包面经(一面)

写在前面 同一天下午连续面试两场,问题有可能与乐易一面记混 时间顺序上,本次面试为当前招聘季第四家公司,共第6次面试。 面试平台在网易自己的会议中,通过面试链接在浏览器进行,界面类似腾讯会议,代码环节是内置…

win7 如何运行cherry studio

win7 如何运行cherry studio安装vxkex扩展内核:版本:KexSetup_Release_1_1_3_1545.exe

《密码系统设计》第十一周预习

20231313 张景云《密码系统设计》第十一周预习AI对内容的总结Headfirst C 一、核心概念网络通信基础:网络程序由服务器和客户端组成,通过“协议”(结构化对话规则)实现数据交互,如自定义的IKKP协议、通用的HTTP协…

深入解析:Flink 状态和 CheckPoint 的区别和联系(附源码)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

抚州0.5mm镜面铝板无压痕模厂家优选,品质稳定采购无忧

近年来,国内模切材料市场持续波动,尤其在高端精密刀模领域,受原材料价格、加工工艺及区域供需关系等多重因素影响,终端产品价格呈现出明显的周期性变化。以0.5mm镜面铝板无压痕模为例,2024年第四季度至2025年第三…

松原西林瓶灌装加塞机推荐,适配冻干机半加塞功能

近年来,随着生物医药产业在东北地区的加速布局,松原市及周边区域对高精度、高洁净度灌装设备的需求持续增长。尤其在冻干制剂领域,适配冻干机的西林瓶灌装加塞机成为众多药企技术升级的关键设备。该类设备需具备半加…