Maven + JUnit:Java单元测试的坚实组合

Maven + JUnit:Java单元测试的坚实组合

  • Maven + JUnit:Java单元测试的坚实组合
    • 一、什么是软件测试?
    • 二、测试的维度:阶段与方法
      • (一)测试的四大阶段
      • (二)测试的三大方法
    • 三、main方法测试与JUnit测试区别的对比
    • 四、使用IDEA进行单元测试
      • 1. ​​引入依赖​​
      • 2. ​​创建测试类与测试方法​​
    • 五、断言:单元测试的核心
      • (一)JUnit 断言方法参考表
        • 1. 基础断言方法
        • 2. 对象引用断言
        • 3. 异常断言
        • 4. 集合和数组断言
        • 5. 组合断言
        • 6. 超时断言
        • 7. 失败断言
    • 六、常见注解
    • 七、测试覆盖率
      • (一)什么是测试覆盖率?
      • (二)覆盖率数据总览
        • 1. `HelloWorld` 类分析
        • 2. `UserService` 类分析
    • 八、Maven依赖Scope标签
      • Scope的作用
      • 常用Scope类型对比
    • 九、总结

Maven + JUnit:Java单元测试的坚实组合

在软件开发的世界里,编写代码只是完成了上半场,而确保代码正确、稳定、高效地运行,则同样重要的下半场——这就是软件测试的舞台。无论你是技术小白还是资深开发者,理解测试都是通往高质量软件的必经之路。今天,我们就来系统性地解析软件测试的"纵横"之道。

一、什么是软件测试?

简单来说,软件测试是一个系统性的过程,旨在通过运行软件来评估并提升其质量。它的核心目标是鉴定软件的:

  • 正确性:是否做了它应该做的事?
  • 完整性:功能是否完整无缺?
  • 安全性:能否抵御外部威胁?
  • 质量:性能、易用性等是否良好?

可以说,测试是产品质量的"守门员",是交付用户信任之前的关键工序。

二、测试的维度:阶段与方法

测试活动是系统化的过程,通常可从两个维度进行理解:一是纵向的测试阶段,体现测试过程的层次性与先后顺序;二是横向的测试方法,反映对待测对象的不同视角与关注点。二者紧密结合,共同构成完整的测试体系。

(一)测试的四大阶段

测试流程与开发阶段环环相扣,遵循V模型从左至右、自下而上的顺序,可分为四个主要层次,层层递进,确保问题尽早发现,降低修复成本:

  1. 单元测试 (Unit Testing)

    • 介绍:针对软件最小的可测试单位(如函数、类)进行验证。
    • 目的:确保每个代码单元的质量符合预期。
    • 执行者:通常由开发人员完成。
    • 常用方法:以白盒测试为主,关注代码逻辑和结构。
  2. 集成测试 (Integration Testing)

    • 介绍:将已通过单元测试的模块组合,测试接口及交互。
    • 目的:验证模块能否正确协同工作,检查数据传递、接口调用等问题。
    • 重点:暴露接口错误和集成缺陷。
    • 常用方法:多采用灰盒测试,兼顾部分内部结构和外部功能。
  3. 系统测试 (System Testing)

    • 介绍:对完整软件系统进行整体测试。
    • 目的:验证系统是否满足需求规格,包括功能、性能、安全等非功能性属性。
    • 执行者:通常由专业测试工程师执行。
    • 常用方法:以黑盒测试为主,从用户视角检验系统行为。
  4. 验收测试 (Acceptance Testing)

    • 介绍:基于用户需求和业务场景进行的最终测试。
    • 目的:确认系统达到用户验收标准,可否正式交付使用。
    • 执行者客户、产品经理或终端用户。
    • 常用方法:属于黑盒测试,强调真实环境下的系统表现。

(二)测试的三大方法

根据对系统内部结构的了解程度,测试方法可分为三类:

  1. 白盒测试 (White-Box Testing)

    • 特点:如同具备“透视眼”,测试人员清楚代码内部逻辑与结构。
    • 关注点:代码覆盖、路径执行、内部逻辑正确性。
    • 典型应用:主要用于单元测试和部分集成测试。
  2. 黑盒测试 (Black-Box Testing)

    • 特点:从“用户视角”出发,不考虑程序内部实现,只关注输入与输出。
    • 关注点:功能符合性、需求实现、用户体验。
    • 典型应用:适用于系统测试验收测试
  3. 灰盒测试 (Gray-Box Testing)

    • 特点:“半透视”方式,既了解部分内部结构(如数据库、接口),也结合外部功能验证。
    • 关注点:接口规范、数据流、集成逻辑。
    • 典型应用:常见于集成测试、安全测试和性能测试。

三、main方法测试与JUnit测试区别的对比

特性维度Main方法测试JUnit测试
本质一种临时原始的测试手段,在main函数中编写测试代码。一个专业标准化的单元测试框架。
编写方式需要手动编写main方法,并在其中创建对象、调用方法、打印结果。使用注解(如@Test)标识测试方法,框架自动识别和执行。
执行方式手动运行整个main方法,或作为应用的入口点启动。JUnit测试运行器自动执行,可以单独运行一个测试方法、一个测试类或整个测试套件。
结果验证人工观察控制台输出,与预期结果进行肉眼比对使用断言(Assertions) API(如assertEquals, assertTrue)进行自动化验证。
可读性与组织差。多个测试用例混杂在一起,逻辑混乱,不易维护和管理。好。每个测试方法独立且聚焦,测试类结构清晰,易于组织和管理。
测试隔离差。测试用例通常按顺序执行,一个用例的失败或异常可能导致后续用例中断。好。JUnit为每个@Test方法创建一个新的测试实例,确保测试之间的独立性
测试生命周期无。需要手动完成 setup(准备)和 teardown(清理)操作。提供注解(如@BeforeEach, @AfterEach)来管理测试的前后置操作,生命周期清晰。
效率低。大量重复代码,验证效率低下,无法实现自动化回归测试。高。编写一次,可一键运行所有测试,是持续集成(CI) 和自动化测试的基石。
功能支持功能单一,仅支持最基本的验证。功能强大,支持参数化测试、重复测试、超时测试、测试套件等高级特性。
适用场景快速验证一小段代码的简单逻辑,临时性、一次性的检查。所有正式的单元测试场景,是软件开发中不可或缺的工程质量保障环节。

四、使用IDEA进行单元测试

1. ​​引入依赖​​

在 pom.xml 配置文件中,引入 JUnit 的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>Maventest1</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>21</maven.compiler.source><maven.compiler.target>21</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- 核心JUnit依赖 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version></dependency></dependencies>

2. ​​创建测试类与测试方法​​

在 src/test/java 目录下,创建对应的测试类,并编写测试方法。在每个测试方法上声明 @Test 注解。

创建对应的测试类UserService

import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;public class UserService {/*** 给定一个身份证号, 计算出该用户的年龄* @param idCard 身份证号*/public Integer getAge(String idCard){if (idCard == null || idCard.length() != 18) {throw new IllegalArgumentException("无效的身份证号码");}String birthday = idCard.substring(6, 14);LocalDate parse = LocalDate.parse(birthday, DateTimeFormatter.ofPattern("yyyyMMdd"));return Period.between(parse, LocalDate.now()).getYears();}/*** 给定一个身份证号, 计算出该用户的性别* @param idCard 身份证号*/public String getGender(String idCard){if (idCard == null || idCard.length() != 18) {throw new IllegalArgumentException("无效的身份证号码");}return Integer.parseInt(idCard.substring(16,17)) % 2 == 1 ? "男" : "女";}}

编写测试方法

import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;/*** 测试类*/
@DisplayName("用户信息测试类")
public class UserServiceTest {@Testpublic void testGetAge(){UserService userService = new UserService();Integer age = userService.getAge("100000200010011011");System.out.println(age);}}

以下是测试结果

在这里插入图片描述

命名规范要求:
测试类名采用:XxxxTest(规范)
测试方法格式:public void xxxx(){…}(规定)

五、断言:单元测试的核心

在Java单元测试中,断言(Assertion)是验证代码行为是否符合预期的核心机制。通过断言,我们能够明确地定义测试的预期结果,并在实际结果与预期不符时立即发现问题。

在测试的时候,代码可以跑只能说明我们的测试代码没有错误,不能代表我们测试的方法逻辑没有错误

(一)JUnit 断言方法参考表

1. 基础断言方法
断言方法描述示例
assertEquals(expected, actual)检查两个值是否相等assertEquals(5, calculator.add(2, 3))
assertEquals(expected, actual, message)检查两个值是否相等,带自定义错误消息assertEquals(5, result, "加法计算结果错误")
assertEquals(expected, actual, delta)检查两个浮点数在误差范围内是否相等assertEquals(3.14, pi, 0.01)
assertNotEquals(unexpected, actual)检查两个值是否不相等assertNotEquals(0, calculator.divide(5, 2))
assertTrue(condition)检查条件是否为trueassertTrue(list.contains("item"))
assertFalse(condition)检查条件是否为falseassertFalse(list.isEmpty())
assertNull(object)检查对象是否为nullassertNull(optionalValue.orElse(null))
assertNotNull(object)检查对象是否不为nullassertNotNull(user.getName())
2. 对象引用断言
断言方法描述示例
assertSame(expected, actual)检查两个对象引用是否指向同一个对象assertSame(singleton1, singleton2)
assertNotSame(unexpected, actual)检查两个对象引用是否指向不同对象assertNotSame(new Object(), new Object())
3. 异常断言
断言方法描述示例
assertThrows(exceptionType, executable)检查代码是否抛出指定类型的异常assertThrows(IllegalArgumentException.class, () -> method(null))
assertThrows(exceptionType, executable, message)检查代码是否抛出指定类型的异常,带自定义错误消息assertThrows(IOException.class, () -> readFile(), "应该抛出IO异常")
assertDoesNotThrow(executable)检查代码是否不抛出任何异常assertDoesNotThrow(() -> validMethod())
4. 集合和数组断言
断言方法描述示例
assertArrayEquals(expected, actual)检查两个数组是否相等assertArrayEquals(new int[]{1,2}, new int[]{1,2})
assertIterableEquals(expected, actual)检查两个可迭代对象是否相等assertIterableEquals(Arrays.asList("a","b"), list)
assertLinesMatch(expected, actual)检查两个字符串列表是否匹配assertLinesMatch(expectedLines, outputLines)
5. 组合断言
断言方法描述示例
assertAll(heading, executables...)执行多个断言,报告所有失败assertAll("用户属性", () -> assertEquals("John", user.firstName), () -> assertEquals("Doe", user.lastName))
assertAll(executables...)执行多个断言,报告所有失败assertAll(() -> assertTrue(x > 0), () -> assertTrue(y > 0))
6. 超时断言
断言方法描述示例
assertTimeout(duration, executable)检查代码是否在指定时间内完成assertTimeout(Duration.ofMillis(100), () -> fastOperation())
assertTimeoutPreemptively(duration, executable)检查代码是否在指定时间内完成,超时立即终止assertTimeoutPreemptively(Duration.ofSeconds(1), () -> potentiallyLongOperation())
7. 失败断言
断言方法描述示例
fail()直接使测试失败if (condition) fail()
fail(message)直接使测试失败,带自定义消息fail("测试不应执行到此点")

示例:

    /*** 断言*/@Testpublic void testGenderWithAssert(){UserService userService = new UserService();String gender = userService.getGender("100000200010011011");//断言//Assertions.assertEquals("男", gender);Assertions.assertEquals("男", gender, "性别获取错误有问题");}/*** 断言*/@Testpublic void testGenderWithAssert2(){UserService userService = new UserService();//断言Assertions.assertThrows(IllegalArgumentException.class, () -> {userService.getGender(null);});}

六、常见注解

注解说明备注
@Test测试类中的方法用它修饰才能成为测试方法,才能启动执行单元测试
@ParameterizedTest参数化测试的注解(可以让单个测试运行多次,每次运行时仅参数不同)用了该注解,就不需要@Test注解了
@ValueSource参数化测试的参数来源,赋予测试方法参数与参数化测试注解配合使用
@DisplayName指定测试类、测试方法显示的名称(默认为类名、方法名)
@BeforeEach用来修饰一个实例方法,该方法会在每一个测试方法执行之前执行一次初始化资源(准备工作)
@AfterEach用来修饰一个实例方法,该方法会在每一个测试方法执行之后执行一次释放资源(清理工作)
@BeforeAll用来修饰一个静态方法,该方法会在所有测试方法之前只执行一次初始化资源(准备工作)
@AfterAll用来修饰一个静态方法,该方法会在所有测试方法之后只执行一次释放资源(清理工作)

示例:

    // 测试配置@DisplayName("测试用户性别")@ParameterizedTest@ValueSource(strings = {"100000200010011011", "100000200010011031", "100000200010011051"})// 测试方法public void testGetGender2(String idCard) {// 初始化被测服务UserService userService = new UserService();// 调用被测方法String gender = userService.getGender(idCard);// 断言:验证返回结果是否为“男”Assertions.assertEquals("男", gender);}

在这里插入图片描述

七、测试覆盖率

(一)什么是测试覆盖率?

测试覆盖率是衡量测试代码对源代码覆盖程度的指标,它反映了测试的全面性和有效性。高覆盖率并不意味着没有bug,但低覆盖率通常意味着测试不充分。

在这里插入图片描述

在IDEA里,我们可以选择使用覆盖率运行,这样我们就可以得到我们测试覆盖率

(二)覆盖率数据总览

类名类覆盖率方法覆盖率行覆盖率分支覆盖率
HelloWorld0% (0/1)0% (0/1)0% (0/1)0% (0/1)
UserService100%37% (3/8)60% (6/10)70%
1. HelloWorld 类分析
  • 各项覆盖率均为 0%
  • 结论:该类完全未被测试,是需要重点关注和改进的测试盲区
2. UserService 类分析
  • 类覆盖率 100%:测试已正确实例化该类
  • 方法覆盖率 37% (3/8):8个方法中只有3个被测试,存在严重遗漏
  • 行覆盖率 60% (6/10):10行可执行代码中有4行未执行
  • 分支覆盖率 70%:10个分支点中有3个未被覆盖

八、Maven依赖Scope标签

Scope的作用

控制依赖项的作用范围生命周期阶段,决定依赖在哪些阶段被引入classpath

常用Scope类型对比

Scope类型作用范围是否参与打包典型应用场景
compile所有阶段✔️核心依赖(Spring, Hibernate)
test测试阶段测试框架(JUnit, Mockito)
provided编译/测试容器提供依赖(Servlet API)
runtime运行/测试✔️JDBC驱动(mysql-connector)
system编译/测试✔️本地系统库(谨慎使用)
import依赖管理-多模块依赖管理

test范围(最常用)

<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope>
</dependency>

九、总结

软件测试是保障软件质量与可靠性的核心实践。它贯穿于整个开发生命周期,从​​单元测试​​、​​集成测试​​到​​系统测试​​和​​验收测试​​(V模型),运用​​黑盒、白盒与灰盒​​等测试方法,构建起一个多层次、多角度的质量保障体系。

在实践中,我们已从原始的main方法测试,演进到使用​​JUnit​​等专业化测试框架。通过丰富的​​断言​​(assertEquals, assertThrows等)和​​注解​​(@Test, @BeforeEach等),我们能够以更简洁、规范且自动化的方式编写用例,并利用​​Maven​​(配合test scope依赖)高效管理测试生命周期。

​​测试覆盖率​​(如行覆盖、分支覆盖)为我们提供了量化测试有效性的重要视角,帮助我们洞察测试盲区(如未覆盖的方法或分支),但它仅是衡量测试充分性的一个维度,而非质量本身的唯一标准。

总而言之,一套成熟、高效的测试策略,是构建稳健、可维护软件系统的基石。它将验证与调试工作前置并自动化,最终为我们交付产品的信心保驾护航。

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

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

相关文章

FFMPEG 10BIT下 Intel b570 qsv 硬解AV1,H265视频编码测试

上10bitffmpeg 8.0 b570最新驱动 &#xff0c;CPU 12100F 显卡 Intel b570 ffmpeg -hwaccel_output_format qsv -i "XXX.mkv" -vf "formatp010le" -c:v hevc_qsv -global_quality 19 -quality best -rc_mode ICQ -preset veryslow -g 120 -refs 5 -b…

SQL分类详解:掌握DQL、DML、DDL等数据库语言类型

如果你是一名数据库运维工程师&#xff0c;或者正在学习数据库技术&#xff0c;那么理解SQL的不同类型是非常重要的。让我们一起看看SQL到底有哪些种类&#xff0c;以及它们各自的作用。 1. 什么是SQL&#xff1f; SQL&#xff08;Structured Query Language&#xff09;是一种…

[特殊字符] 预告!我正在开发一款让自动化操作变得「像呼吸一样自然」的AI神器

各位技术爱好者和创作者朋友们&#xff0c;我要解决一个行业痛点&#xff01;在上一个项目中&#xff08;&#x1f525; 重磅预告&#xff01;我要用AI开发一个自媒体神器&#xff0c;彻底解决创作者的7大痛点&#xff01;&#xff09;&#xff0c;我本来雄心勃勃地打算直接用R…

加密软件哪个好用?加密软件-为数据共享提供安全保障

企业与合作伙伴协作时需共享大量数据&#xff0c;若缺乏保护&#xff0c;数据可能被非法获取&#xff0c;影响合作信任&#xff0c;甚至引发商业纠纷。加密软件可确保共享数据仅授权方可见&#xff0c;为数据共享提供安全保障&#xff0c;推动合作顺利开展。​1.固信软件固信加…

FPGA复位

1:能不复位尽量不要复位&#xff0c;减少逻辑扇出数&#xff1a;比如打拍信号。2:xilinx的FPGA推荐高复位&#xff0c;ATERAL的FPGA推荐低复位。3:尽量使用异步复位&#xff1a;大多数厂商目标库内的触发器都只有异步复位端口&#xff0c;采用同步复位需消耗较多逻辑资源。一&a…

Cursor 教我学 Python

文章目录1. 写在最前面2. Python 语法2.1 yield2.1.1 yield 和 return 的区别2.1.2 golang 中实现 yield 语法3. aiohttp 库3.1 原始写法3.2 修改写法3.2 耗时对比分析4. 碎碎念5. 参考资料1. 写在最前面 最近加了很多 Python Coding 的任务&#xff0c;虽然在 AI 加持下能够顺…

Ollama:本地大语言模型部署和使用详解

1.什么是Ollama&#xff1f; Ollama是一个开源的大语言模型管理工具&#xff0c;具有以下特点&#xff1a; 简单易用&#xff1a;提供简单的命令行接口本地部署&#xff1a;模型运行在本地&#xff0c;保护数据隐私跨平台支持&#xff1a;支持Windows、macOS、Linux丰富的模型…

云计算学习100天-第41天 -普罗米修斯2

目录 五、添加被监控端 1、在web1[192.168.88.100]上部署node exporter 2、在Prometheus服务器上添加监控节点 3、浏览器查看添加结果 六、Grafana的部署 概述 部署步骤 七、监控MySQL数据库 1、配置MySQL 2、配置mysql exporter 3、配置prometheus监控mysql 五、添…

集成电路学习:什么是SVM支持向量机

SVM:支持向量机 SVM,即支持向量机(Support Vector Machine),是一种常用的机器学习算法,特别适用于分类和回归问题。以下是对SVM的详细解析: 一、SVM的基本原理 SVM的基本思想是在特征空间中寻找一个最优的超平面,使得不同类别的样本能够被最大化地分开。这个最优…

盲盒抽谷机小程序开发:如何用3D技术重构沉浸式体验?

在盲盒经济中&#xff0c;“沉浸感”是提升用户停留时长与转化率的核心武器。某品牌通过3D扭蛋机旋转、卡牌翻转特效&#xff0c;使用户停留时长从15秒延长至45秒&#xff0c;转化率提升25%&#xff1b;另一品牌上线AR试戴功能后&#xff0c;单次抽谷时长延长至2分钟&#xff0…

集采与反腐双重压力下,医药销售的破局之道:从资源依赖到价值重构

在医药行业进入集采常态化与反腐纵深推进的新阶段&#xff0c;“资源匮乏”“拜访受阻” 成为萦绕在众多医药销售人员心头的难题。当传统的资金投入、学术活动等资源型打法逐渐失效&#xff0c;行业正面临一场从 “资源驱动” 到 “价值驱动” 的深刻变革。那些曾在市场中创造过…

Elasticsearch常用命令(未完)

网上针对es常用命令好多都是写的感觉非常复杂难以理解&#xff0c;所以我还是自己整理了一下相关的常用命令。 对es输入指令可以用很多种方法比如用es的谷歌浏览器插件&#xff0c;亦或者postman&#xff0c;我个人比较喜欢用postman比较简单直接 1.删除指定索引下的所有数据…

【系统架构设计(七)】 需求工程之:面向对象需求分析方法:统一建模语言(UML)(下)

文章目录一、用例图1. 用例模型建立的系统化流程第一步&#xff1a;识别参与者第二步&#xff1a;合并需求获得用例第三步&#xff1a;细化用例描述第四步&#xff1a;调整用例模型&#xff08;可选步骤&#xff09;2. 用例之间的关系类型二、类图与对象图概念类之间的关系三、…

数据结构——树(04二叉树,二叉搜索树专项,代码练习)

文章目录一、概念二、构造1.1先序序列 构造BST1.2中序序列 转换为BST1.3中序序列链表转换为BST1.4BST转换为中序序列链表1.7BST的序列化和反序列化1.6BST的种数二、BST的增删改查2.1验证是否为BST2.2查找值为val的节点2.3插入一个值为val的节点2.4删除一个值为val的节点2.5恢复…

ArkUI核心功能组件使用

1.Tabs&#xff08;选项卡&#xff09; 1.1 概述 Tabs组件的页面组成包含两个部分&#xff0c;分别是TabContent和TabBar。TabContent是内容页&#xff0c;TabBar是导航页签栏。 TabBar是导航页签栏&#xff0c;页面结构如下图所示&#xff0c;根据不同的导航类型&#xff0c;布…

Qt5 多媒体大纲

一、入门准备 基础知识 熟悉 Qt 的信号槽机制、事件循环 掌握 .pro 工程文件配置&#xff08;QT multimedia multimediawidgets&#xff09; 熟悉常见的音视频格式与编解码器基础 环境配置 Qt Creator Qt 5.x 确认安装了 multimedia 模块与 mediaservice 插件 熟悉调试…

音频数据集采样率选择建议

你好&#xff01;这是一个非常棒且非常重要的问题&#xff0c;在音频机器学习项目中&#xff0c;选择合适的采样率是平衡计算效率和模型性能的关键。 直接回答你的问题&#xff1a;将音频下采样到 800 Hz 对于绝大多数音频分类任务来说都太低了&#xff0c;几乎肯定会丢失大量关…

深度学习系列 | Seq2Seq端到端翻译模型

一、通俗总结Seq2Seq 就像一个 “序列转换器”&#xff1a;先把输入的一段话 “压缩成一个核心意思”&#xff0c;再根据这个意思 “一句句生成另一段话”&#xff0c;能搞定翻译、听写这类 “输入输出不一样长” 的任务&#xff0c;但太长的内容可能记不全&#xff0c;还容易越…

Spring MVC BOOT 中体现的设计模式

Spring:创建型:单例模式:Bean默认就是单例的&#xff0c;是饿汉模式的&#xff0c;但是可以通过Lazy设置为懒汉工厂模式&#xff1a;可自定义FactroyBean&#xff0c;实现Bean自己的生产工厂结构型:代理模式&#xff1a;AOP就是典型的动态代理&#xff0c;有jdk和cglib两种实现…

Chrome浏览器调用ActiveX控件之allWebOffice在线编辑控件

背景 allWebOffice控件能够实现在浏览器窗口中在线操作文档的应用&#xff08;阅读、编辑、保存等&#xff09;&#xff0c;支持编辑文档时保留修改痕迹&#xff0c;支持书签位置内容动态填充&#xff0c;支持公文套红&#xff0c;支持文档保护控制等诸多办公功能&#xff0c;本…