文章一览
发布于 2025-02-08
使用Allatori混淆代码
33 热度
0 条评论
Java
使用Allatori混淆代码 在软件开发的世界中,保护你的知识产权和源代码是至关重要的。尤其是当你发布Java应用程序时,确保你的代码难以被反编译或理解显得尤为重要。这正是代码混淆工具如Allatori发挥作用的地方。本文将介绍如何使用Allatori来混淆你的Java代码,并提供一些实用的配置示例。 什么是Allatori? Allatori是一个强大的Java混淆器,它能够通过重命名类、方法和变量名等方式来保护你的Java应用程序不被轻易理解和反编译。此外,Allatori还提供了诸如字符串加密、控制流混淆等高级功能,进一步增强了代码的安全性。 官方文档 Allatori文档 开始之前 下载Allatori的Demo => Demo下载 使用Allatori(本文才用的是Maven方式使用) 在自己的项目中引入 如上图,在项目中引入allatori.jar和allatori.xml pom.xml配置 <plugins><!-- 正在将Allatori配置文件复制到“target”目录。目标文件将被过滤(配置文件中使用的Maven属性将被解析) --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.6</version> <executions> <execution> <id>copy-and-filter-allatori-config</id> <phase>package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${basedir}/target</outputDirectory> <resources> <resource> <directory>${basedir}/allatori</directory> <includes> <include>allatori.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> <!-- 运行 Allator --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <id>run-allatori</id> <phase>package</phase> <goals> <goal>exec</goal> </goals> </execution> </executions> <configuration> <executable>java</executable> <arguments> <argument>-Xms128m</argument> <argument>-Xmx512m</argument> <argument>-jar</argument> <argument>${basedir}/allatori/allatori.jar</argument> <argument>${basedir}/target/allatori.xml</argument> </arguments> </configuration> </plugin> <!-- 替换jar(混淆后会有原始jar和混淆jar,如果需要直接deploy可以使用该插件替换jar包直接将混淆后的jar部署) --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <version>1.8</version> <executions> <execution> <id>rename-encrypted-jar</id> <phase>package</phase> <goals> <goal>run</goal> </goals> <configuration> <tasks> <move file="${project.build.directory}/${project.artifactId}-${project.version}-obfuscated.jar" tofile="${project.build.directory}/${project.artifactId}-${project.version}.jar" overwrite="true"/> </tasks> </configuration> </execution> </executions> </plugin> </plugins> allatori.xml配置 基本配置 一个基本的Allatori配置文件看起来如下所示: <config> <input> <jar in="your-app.jar" out="your-app-obfuscated.jar"/> </input> <keep-names> <!-- 这里可以指定你不希望被混淆的类、字段和方法 --> </keep-names> </config> 在这个例子中,in属性指定了输入的JAR文件路径,而out属性则定义了输出(即混淆后的)JAR文件路径。 保持特定类不被混淆 有时候,你需要保持某些类或包下的所有类不被混淆。例如,如果你有一个API接口或者需要保留反射机制使用的类名,你可以这样做: <keep-names> <class template="class com/your/package/"> <field template=""/> <method template="()"/> </class> </keep-names> 这里,com/your/package/**表示匹配该目录及其所有子目录下的所有类。**通配符用于匹配任意层级的子目录。 忽略的包或类 <!-- 忽略的包或类,这些文件将不被混淆 --> <ignore-classes> <!-- 不要混淆主类 --> <class template="class com.navi.akkaload.AkkaloadApplication" /> <!-- 不要混淆第三方的代码,否则会运行jar包会报错java.lang.NoClassDefFoundError --> <class template="class org.dom4j." /> <class template="class akka.actor." /> <class template="class alibaba" /> <class template="class org" /> <class template="class rabbitmq" /> <class template="class springframework" /> <class template="class lombok" /> </ignore-classes> <!-- 到期时间(到期后无法启动jar) 格式:yyyy/mm/dd--> <!--<expiry date="2021/04/03" string="SERVICE EXPIRED!"/>--> <!-- 随机命名混淆字符--> <!--<property name="random-seed" value="abcdef ghnljk svi"/>--> 高级选项 Allatori还支持更多高级功能,比如字符串加密、水印添加等。这些功能可以通过在配置文件中添加相应的标签来启用: <property name="string-encryption" value="true"/> <property name="watermark" value="Your Watermark Text"/> keep-names和ignore-classes区别 在Allatori Java混淆器中,keep-names和ignore-classes这两个配置项虽然都用于指定不应被混淆的类、方法或字段,但它们的作用和使用场景有所不同。 keep-names keep-names配置主要用于指定哪些类、方法或字段的名字应该保持原样,不进行混淆。这意味着这些元素将保留其原始名称,从而便于调试或者保证某些依赖于反射机制的功能正常工作。例如: <keep-names> <class template="class com/example/MyClass"> <field template=""/> <method template="()"/> </class> </keep-names> 在这个例子中,com.example.MyClass中的所有字段和方法都不会被混淆,保留其原始名称。 ignore-classes 相比之下,ignore-classes则用于完全排除某些类或包,使其不受混淆过程的影响。这意味着不仅这些类的名称不会被改变,而且其中的方法和字段也不会被混淆。这通常用于那些你确定不应该被混淆的第三方库或是你自己代码中的一些特定部分。例如: <ignore-classes> <class template="class com/example/external/"/> </ignore-classes> 这个配置会使得com.example.external包及其所有子包下的类都不参与混淆过程。 总结 keep-names:主要用于指定需要保留原始名称的类、方法或字段。它允许你精细地控制哪些具体的部分不需要被混淆,同时其他未指定的部分仍可以按照默认规则进行混淆。 ignore-classes:则是更广泛地排除整个类或包,使其完全避开混淆流程。这对于不想对某些外部库或特定模块进行任何修改的情况非常有用。 理解这两者的区别有助于更好地定制你的混淆策略,确保既能有效保护代码安全,又能维持必要的功能完整性。 结论 通过使用Allatori进行代码混淆,你可以有效地提高你的Java应用程序的安全性,减少代码被反编译的风险。虽然代码混淆不能完全阻止有决心的攻击者,但它确实增加了破解的难度和时间成本,为你的软件提供了一层额外的保护。 记住,正确的配置是成功的关键。花时间了解Allatori的各种选项,并根据自己的具体需求调整配置,将帮助你最大限度地利用这个强大的工具。 补充自动化部署 pom.xml中添加maven私仓地址 <!-- 发布jar包时需要有的maven私服配置 --> <distributionManagement> <!--id要和setting文件中server节点中配置的一样--> <repository> <id>maven-releases</id> <name>maven-releases</name> <url>releases-私仓地址</url> </repository> <!--id要和setting文件中server节点中配置的一样--> <snapshotRepository> <id>maven-snapshots</id> <name>maven-snapshots-私仓地址</name> <url>snapshots</url> </snapshotRepository> </distributionManagement> maven配置文件中配置私仓信息 <servers> <server> <id>releases</id> <username>用户名</username> <password>密码</password> </server> <server> <id>snapshots</id> <username>用户名</username> <password>密码</password> </server> </servers> 执行deploy指令即可
发布于 2025-01-09
还在用Jenkins?快来试试Jpom 一款国人都说好的自动化部署系统
34 热度
0 条评论
开源项目
随着 DevOps 理念的普及和技术的发展,持续集成和持续部署(CI/CD)已经成为现代软件开发中不可或缺的一部分。提到 CI/CD 工具,许多人首先想到的是 Jenkins。然而,近年来,越来越多的国内开发者开始关注并使用一个名为 Jpom 的自动化部署系统。今天,我们就来深入了解这款被国人们誉为“好用”的工具——Jpom。 什么是 Jpom? Jpom 是一款开源的、轻量级的 Java 应用程序自动化部署系统,专为中小型项目设计。它不仅支持 Java 应用,还兼容其他语言编写的程序。Jpom 提供了简单直观的 Web 界面,让用户可以轻松管理应用生命周期中的各个环节,包括构建、测试、部署等。 Jpom 的优势 用户友好界面- Jpom 拥有简洁明了的操作界面,降低了新用户的上手难度。对于不熟悉命令行操作的团队成员来说,这是一个极大的便利。 多环境支持- 支持多种运行环境配置,如开发、测试、生产等,确保不同阶段的应用版本一致性和稳定性。 丰富的插件生态- 虽然 Jpom 自身功能已经非常强大,但它也允许通过插件扩展其能力。社区活跃,提供了大量高质量插件以满足不同需求。 安全性和权限管理- 内置的安全机制保障了系统的可靠性和数据隐私。同时,细粒度的权限管理系统使得团队协作更加高效有序。 性能优化- 高效的任务调度算法保证了任务执行的速度与效率,减少了等待时间,提高了开发迭代速度。 文档详尽- 官方提供了详细的文档和支持,帮助用户快速解决问题,减少学习成本。 Jpom 与 Jenkins 的对比 总结 如果你还在为复杂的 Jenkins 配置烦恼,不妨考虑一下 Jpom。作为一款由国人开发并广泛好评的自动化部署工具,它凭借易用性、灵活性以及强大的功能赢得了众多开发者的青睐。无论你是初创企业的技术负责人,还是大型企业的运维工程师,Jpom 都能为你带来意想不到的帮助。快来试试吧!
发布于 2024-08-06
[开源项目分享]一款前后端分离的最美博客 star 2.3k
705 热度
0 条评论
开源项目
一、项目介绍 大家好,POETIZE已经3年了。 从2021年6月16日开始搭建项目,到2022年8月21日第一个版本发布。期间曾因为换工作而暂停几个月,也因老头环而鸽了几个月。 项目经历了大大小小的更新,每次更新都是在美观度上的精雕细琢,也修复了很多问题,添加了很多功能。 项目提供部分源码供大家参考学习,只需要在Gitee拉取项目,按照Readme启动说明就能直接启动,但需要一定的编程能力。完整版就需要您赞助本项目了。 感谢对POETIZE的支持。 二、开源协议 使用Apache-2.0开源许可协议。 三、技术栈 这是一个 SpringBoot + Vue2 + Vue3 的产物,支持移动端自适应,配有完备的前台和后台管理功能。 前端技术:Vue2(博客系统),Vue3(IM 聊天室系统),Element UI(Vue2),Element-Plus UI(Vue3),Naive UI(Vue3) 后端技术:Java,SpringBoot,MySQL,Mybatis-Plus,t-io,qiniu-java-sdk,spring-boot-starter-mail 网站分两个模块: 博客系统:具有文章,表白墙,图片墙,收藏夹,乐曲,视频播放,留言,友链,时间线,后台管理等功能。 聊天室系统:具有朋友圈(时间线),好友,群等功能。 本网站采用前后端分离进行实现,两个前端项目通过Nginx代理,后端使用Java。 部署网站需要安装Nginx、Java、MySQL,然后打包前后端项目并部署。 文件服务可以使用七牛云,也可以使用服务器。默认使用服务器。 Vue3(IM 聊天室系统)是非必须的。如果部署,则需要依赖博客,然后从博客的“联系我”进入,因为登录模块在博客。 四、项目预览 五、项目地址 博客前端(开源版):gitee.com/littledoke... 聊天室前端(开源版):gitee.com/littledoke... 后端(开源版):gitee.com/littledoke... 博客前端、聊天室前端、后端汇总版(上述三个仓库放在一个仓库里,代码无差别)(开源版):gitee.com/littledoke... 七牛云登录/注册地址(文件服务/CDN):https://s.qiniu.com/Mz6Z32
发布于 2024-08-06
基于html2canvas、jspdf 实现网页打印效果
673 热度
0 条评论
JavaScript
Vue2项目中实现将页面中指定DOM导出为PDF格式。自定义隐藏指定DOM中不想打印出来的内容。 首先是需要用到的依赖:html2canvas、jspdf 首先将导出功能封装成一个按钮组件(当然如果使用频率不高就没必要,反而更方便隐藏不想打印的内容) <template><!-- 导出分析报告 --> <el-button :loading="exporting" :disabled="disabled" @click="exportPic">导出分析报告</el-button> </template> <script> import html2canvas from 'html2canvas'; import JsPDF from 'jspdf' export default { name: 'exportPicButton', props: { operationClass: String, disabled: Boolean }, data() { return { exporting: false } }, methods: { /** 导出分析报告 */ exportPic() { if (!this.operationClass) return; // 防御避免导出时重复点击,导完后才允许点击 if (this.exporting) return; // 显示导出中 this.exporting = true; this.$emit('before-export'); // 截图 this.$nextTick(() => { setTimeout(() => { const dom = document.querySelector(.${this.operationClass}); html2canvas(dom, { height: dom.scrollHeight, windowHeight: dom.scrollHeight, // 解决导出卡顿问题 ignoreElements: e => { if ( e.contains(dom) || dom.contains(e) || e.tagName === 'STYLE' || e.tagName === 'LINK' || e.tagName === 'HEAD' || e.getAttribute('data-html2canvas') != null // header里面的样式不能筛掉 ) { return false; } return true; } }).then(canvas => { let contentWidth = canvas.width; let contentHeight = canvas.height; let pageHeight = contentWidth / 592.28 * 841.89; let leftHeight = contentHeight; let position = 0; let imgHeight = 592.28 / contentWidth * contentHeight; let pageData = canvas.toDataURL('image/jpeg', 1.0); let PDF = new JsPDF('', 'pt', 'a4'); // 边距 const margin = 20; // 调整图片宽度和高度以适应页面并保持比例 let imgWidth = 595.28 - 2 * margin; imgHeight = imgWidth / 595.28 * imgHeight; if (leftHeight < pageHeight) { PDF.addImage(pageData, 'JPEG', margin, margin, imgWidth, imgHeight); } else { while (leftHeight > 0) { PDF.addImage(pageData, 'JPEG', margin, margin + position, imgWidth, imgHeight); leftHeight -= pageHeight; position -= 841.89; if (leftHeight > 0) { PDF.addPage(); } } } // 当前时间 const now = new Date(); const nowStr = ${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}; PDF.save(分析报告${nowStr}.pdf) // 关闭导出中 this.exporting = false; }).catch((err) => { // 显示按钮 this.exporting = false; // todo 处理异常 console.log(err); }).finally(() => { this.$emit('after-export'); }); }, 500) }); } } } </script> 组件只有一个按钮内容,props中接收的operationClass(需要打印DOM的class名称)、disabled(打印/导出按钮是否可用),emit事件有before-export(导出前要做的事情,如影响导出必须为同步)、after-export(导出后要做的事情) 使用方式: <template> <div class="main" v-loading="pageLoading"> <div class="page-title"> <export-pic-button v-show="showHidden" size="small" :disabled="!canExportAnalysisReport" operation-class="content" @before-export="() =>{ showHidden = false; pageLoading = true; }" @after-export="() => { showHidden = true; pageLoading = false; }" </export-pic-button> </div> <div class="content"> 要打印的内容 <div class="hidden" v-show="showHidden">打印时不想展示的内容</div> </div> </div> </template> <script> import ExportPicButton from "@/pages/index/views/zkgl/analyse/components/ExportPicButton.vue"; export default { name: 'export-pic', components: {ExportPicButton}, data() { return { // 是否可导出 canExportAnalysisReport: true, // 按钮是否显示 showHidden: true, // 页面加载中 pageLoading: false, }; }, }; </script> 导出PDF为分析报告2024-08-06.pdf
发布于 2024-02-28
Springboot中使用Redis还在只会存字符串,快来看看各种数据类型的使用技巧!
1059 热度
1 条评论
Java
在现代的Web应用程序中,缓存是一项至关重要的技术,以提高性能和减少数据库负载。而 Redis 作为一种高性能的内存数据库,被广泛应用于缓存、会话管理、消息队列等场景。本篇博客将介绍如何在 Spring Boot 中使用 Redis,并详细介绍 Redis 支持的数据类型及其使用方法。 1. 引入 Redis 依赖 首先,在 Spring Boot 项目中引入 Redis 的依赖: <dependency><groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 2. 配置 Redis 连接信息 在 application.properties 或 application.yml 文件中配置 Redis 的连接信息: spring.redis.host=localhost spring.redis.port=6379 spring.redis.password= 3. Redis 数据类型及使用方法 3.1. 字符串(String) 字符串是最简单的 Redis 数据类型,可以存储文本、数字等数据。在 Spring Boot 中,可以使用 StringRedisTemplate 来操作字符串数据: @Resource private StringRedisTemplate stringRedisTemplate; // 设置字符串值 stringRedisTemplate.opsForValue().set("key", "value"); // 获取字符串值 String value = stringRedisTemplate.opsForValue().get("key"); 3.2. 哈希(Hash) 哈希数据类型适合存储对象的属性信息,可以用于存储用户信息、配置信息等。在 Spring Boot 中,可以使用 HashOperations 来操作哈希数据: @Resource private RedisTemplate<String, Object> redisTemplate; HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash(); // 设置哈希字段值 hashOps.put("user:id:123", "name", "Alice"); hashOps.put("user:id:123", "age", "25"); // 获取哈希字段值 String name = (String) hashOps.get("user:id:123", "name"); 3.3. 列表(List) 使用 Redis 的列表数据类型可以实现队列、栈等功能。在 Spring Boot 中,可以通过 RedisTemplate 的 opsForList() 方法来操作列表数据: @Resource private RedisTemplate<String, String> redisTemplate; // 向列表头部插入值 redisTemplate.opsForList().leftPush("myList", "value1"); redisTemplate.opsForList().leftPush("myList", "value2"); // 向列表尾部插入值 redisTemplate.opsForList().rightPush("myList", "value3"); redisTemplate.opsForList().rightPush("myList", "value4"); // 获取列表范围内的值 List<String> values = redisTemplate.opsForList().range("myList", 0, -1); 3.4. 集合(Set) 使用 Redis 的集合数据类型可以实现存储唯一值的需求。在 Spring Boot 中,可以通过 RedisTemplate 的 opsForSet() 方法来操作集合数据: @Resource private RedisTemplate<String, String> redisTemplate; // 向集合添加成员 redisTemplate.opsForSet().add("mySet", "member1"); redisTemplate.opsForSet().add("mySet", "member2"); // 获取集合所有成员 Set<String> members = redisTemplate.opsForSet().members("mySet"); 3.5. 有序集合(Sorted Set) 使用 Redis 的有序集合数据类型可以按照分数(score)排序存储成员。在 Spring Boot 中,可以通过 RedisTemplate 的 opsForZSet() 方法来操作有序集合数据: @Resource private RedisTemplate<String, String> redisTemplate; // 向有序集合添加成员及分数 redisTemplate.opsForZSet().add("mySortedSet", "member1", 10.0); redisTemplate.opsForZSet().add("mySortedSet", "member2", 20.0); // 获取有序集合指定范围的成员 Set<ZSetOperations.TypedTuple<String>> membersWithScores = redisTemplate.opsForZSet().rangeWithScores("mySortedSet", 0, -1); 4. 在选择 Redis 数据类型时,需要根据具体的业务场景和需求来决定使用哪种数据类型。 4. 1. 列表(List) 适用场景: 队列(FIFO):适合实现任务队列、消息队列等。 栈(LIFO):适合实现撤销操作、浏览历史等。 优势: 可以保持元素的插入顺序。 支持从列表两端进行插入和删除操作。 4. 2. 集合(Set) 适用场景: 存储唯一值:适合存储用户标签、点赞数等。 集合运算:如并集、交集、差集等操作。 优势: 元素不重复,可确保数据的唯一性。 支持集合运算,方便对多个集合进行操作。 4. 3. 有序集合(Sorted Set) 适用场景: 排行榜:适合按分数排序存储并展示排名。 时间轴:适合按时间顺序存储事件或消息。 优势: 按照分数(score)排序,方便获取排名信息。 可以实现范围查询,获取指定范围内的成员。 如何选择: 如果需要按顺序存储数据,并且需要支持队列或栈的操作,可以选择列表。 如果需要存储唯一值,并且需要进行集合运算,可以选择集合。 如果需要按分数排序存储数据,并且需要根据排名或范围查询,可以选择有序集合。
发布于 2024-02-26
2023,2022,2021年面试题合集
1472 热度
0 条评论
面试整理
2023年算法知识合集 LeetCode题解 如何高效使用 LeetCode 大家都是如何刷 LeetCode 的? 用动画的形式呈现解LeetCode题目的思路 代码随想录 LABULADONG 的算法网站 宫水三叶的刷题日记 LeetCode题目分类与面试问题整理 汇总各大互联网公司容易考察的高频leetcode题 多种编程语言实现 LeetCode、剑指 Offer、程序员面试金典 《代码随想录》LeetCode 刷题攻略:视频难点剖析,50余张思维导图,支持多语言版本 前端算法进阶指南 和小浩学算法 2023年综合题合集 编程面试大学 前端面试题汇总 2023-05-大厂面试最爱问的Event Loop 2023-持续更新大厂前端面试题 2023.03.28-两万字三月前端面经(含回答) 2023.03.28-更新前端面试问题总结 2023-02-27-2023前端面试题总结 2023-02-27-高级前端必会面试题(边面边更) 2023-02-27-如何才能做好准备好前端面试 2023-02-27-阿里前端二面经典手写面试题汇总 2023-02-23-美团前端常见面试题整理 2023-02-一道面试题带你了解事件流和Event Loop webpack面试题及答案 2023-前端面试必备个人总结(持续更新中) 2023-前端必会手写面试题整理1 2023-前端面试题 2023-前端面试系列-- webpack & Git篇 2023-前端面试系列--网络篇 2023-前端面试系列-- JS 篇 2023-大厂面试题第一部分 2023-高频前端面试题合集之网络篇 2023-前端二面react面试题集锦 2023年小程序面试题合集 2023-07-15-uni-app开发经验总结 2023-07-13-uni-app面试题汇总 2023-03-18-(备忘录)微信小程序面试题 2023-03-12-小程序面试题总结 2023-03-2-uniapp和小程序面试题 2022-09-03-小程序面试题 2022-08-01-小程序面试题 2021-08-06-微信小程序面试总结 尚硅谷微信小程序面试题 Vue 2023-04-29-vue3面试题八股集合 2023-03-13--滴滴前端高频vue面试题(边面边更) 2023-03-01-前端常见vue面试题(必备) 2023-02-28-2023 前端 vue 面试题及答案 2023-02-28-2023前端vue面试题及答案 2023-02-27-前端一面常见vue面试题汇总 2023-02-23-每日一题之Vue的异步更新实现原理是怎样的 2023-02-21-社招前端一面必会vue面试题 2023-02-21-高级前端二面必会vue面试题合集 2023-02-19-美团前端一面高频vue面试题整理 2023-02-19-社招前端经典vue面试题(附答案) 2023-02-14-前端二面经典vue面试题总结 2023-02-14-前端一面必会vue面试题总结 2023-02-13-高级前端二面vue面试题(持续更新中) 2023-02-13-百度前端常考vue面试题(附答案) 2023-02-07-滴滴前端一面必会vue面试题 2023-02-07-面试官:vue2和vue3的区别有哪些 2023-02-06-前端一面高频vue面试题(边面边更) vue高频面试知识点汇总【2023新春版】 2023-前端面试系列-- Vue 篇 2023前端之VUE面试题汇总 React 2023-06-30-前端面试系列之 React 必备知识点 2023-06-04-2023 6月份面试题 React篇 2023-05-23-前端面试:React高频面试题 2023-02-21-社招前端常考react面试题总结 2023-01-React面试题 2023-react面试题大合集 2023-视频-年最新珠峰React全家桶【基础-进阶-项目-源码-淘系-面试题】 2023-一文带你梳理React面试题(2023年版本) 2022年react最全面试题 2021-高频前端面试题汇总之React篇(上) 2021-高频前端面试题汇总之React篇(下) 2019年17道高频React面试题及详解 Go 面试题 2023-04-04-Go必看的进阶面试题详解 2022-10-Go常见面试题【由浅入深】2022版 2022-08-GO必知必会面试题汇总 用Go语言刷力扣专栏 Golang 面试题搜集 2022年 2022-09-13-[面经] 5年前端 - 历时1个月收获7个offer 2022-05-01-2022必会的前端手写面试题 【视频】2022web前端面试题大汇总/web前端面试题详解(持续更新) 宏任务和微任务的理解 2022-最新WEB前端面试题大汇总/全栈面试题集合/Vue面试题/React面试题 2022-高频前端面试题合集之JavaScript篇(上) 2022-高频前端面试题合集之JavaScript篇(中) 2022-高频前端面试题合集之JavaScript篇(下) 2022-TypeScript最新高频面试题指南 2022-最新?前端高频面试题总结(一) 2022-高频前端面试题——CSS篇 2022-年我的面试万字总结(浏览器网络篇) 2022-年我的面试万字总结(CSS篇) 2022-年我的面试万字总结(HTML篇) 2022-年我的面试万字总结(代码篇) 2022-年我的面试万字总结(JS篇上) Vue 2022-09-05-Vue3.0面试题汇总 2022-关于Vue的一些高频面试题总结 2022-年我的面试万字总结(Vue上) 2022-年我的面试万字总结(Vue下) Node NodeJs(前端面试题整合) Node.js面试题,侧重后端应用与对Node核心的理解 Nest.js面试题 2021年 2022-04-高频前端面试题汇总之手写代码篇 原生JS灵魂之问, 请问你能接得住几个?(上) 原生JS灵魂之问(中),检验自己是否真的熟悉JavaScript 原生JS灵魂之问(下), 冲刺🚀进阶最后一公里 浏览器灵魂之问,请问你能接得住几个? FE-Interview 2021-高频前端面试题汇总之JavaScript篇(上) 2021-高频前端面试题汇总之JavaScript篇(下) 2021-高频前端面试题汇总之CSS篇 2021-高频前端面试题汇总之Vue篇(上) 2021-高频前端面试题汇总之Vue篇(下) 2021-高频前端面试题汇总之React篇(上) 2021-高频前端面试题汇总之React篇(下) 更早 「网络安全」面试常见的 web 网络安全知识整理 前端 100 问 必须掌握的面试题-50个Angular面试 转载于 麋鹿一直在路上——2023,2022,2021年面试题合集
发布于 2024-02-26
60个开源项目,提升自己、释放双手
1992 热度
0 条评论
开源项目
60个开源项目 1、Taskover 个人任务管理工具 源码地址:github.com/kesin/tas... Taskover 是基于Rails+VueJs的一款开源个人任务管理工具,通过简单易用的任务归纳分类方式,用来帮助高效地追踪管理各项任务,最大化的提升工作效率。 2、app-version APP 版本管理系统 源码地址:github.com/xtTech/ap... 3、PearProject 轻量级的在线项目/任务协作系统 源码地址:github.com/a54552239... 轻量级的在线项目/任务协作系统,远程办公协作 4、pyteam 任务管理系统 源码地址:github.com/atjiu/pyt... 仿 teambition 使用 koa 开发的一个无刷新的任务管理系统,当然功能没有人家teambition多了,不过我觉得用的最多的也就这些功能了 5、Jpom Java 项目在线管理 源码地址:github.com/dromara/J... 6、MasterLab 基于敏捷开发的项目管理工具 源码地址:github.com/gopeak/ma... MasterLab是一款简单高效、基于敏捷开发的项目管理工具,以事项驱动和敏捷开发最佳实践作为设计思想,同时参考了Jira和Gitlab优秀特性发展而来,适用于互联网团队进行高效协作和敏捷开发,交付极致卓越的产品 7、Naftis Istio 管理面板 源码地址:github.com/jukylin/i... istio-ui用于管理istio配置,目的是减轻运维的配置工作。主要实现:注入,istio配置和模板(还在开发中)等功能 8、Application Manager 程序管理器:gitee.com/mirrors/Ap... 9、task-flow 自定义后台任务流:gitee.com/qzd1989/ta... 10、LibMan 第三方客户端库管理工具:gitee.com/mirrors/Li... 11、KooTeam 在线协作与文档管理系统:gitee.com/sinbo/koot... 12、PageNow 大数据可视化开发平台:gitee.com/jman325_ad... 13、Flowable 流程表单设计:gitee.com/lwj/flowab... 14、考试答题:gitee.com/beautiful-... 15、在线考试:gitee.com/davz/yf-ex... 16、在线培训考试系统:gitee.com/wzhouzhou/... 17、内容管理系统:www.oschina.net/p/ma... 18、文件系统:gitee.com/qiwen-clou... 19、UI框架:gitee.com/TSpecific/... 20、layui框架:gitee.com/pear-admin... 21、一个后台拥有两种前端框架:gitee.com/smallc/Spr... 22、书籍:github.com/justjavac... 23、博客:gitee.com/fehey/grid... 24、智能大屏:gitee.com/beautiful-... 25、大屏:gitee.com/daoke0818/... 26、74套大屏:gitee.com/52itstyle/... 27、智能大屏2:github.com/ggymm/dat... 28、数据可视化报表:gitee.com/jeecg/Jimu... 29、报表设计器:gitee.com/doc_wei01/... 30、飞机大战:www.oschina.net/p/pl... 31、python后台管理:gitee.com/liqianglog... 32、框架: gitee.com/ssssssss-t... 33、微服务框架:gitee.com/geek_qi/cl... 34、分布式框架:gitee.com/shuzheng/z... 35、ruoyi-pro:gitee.com/zhijiantia... 36、快速开发:gitee.com/JeeHuangBi... 37、权限框架:gitee.com/52itstyle/... 38、智慧消防:www.oschina.net/p/tm... 39、自动化测试:github.com/seagull19... 40、大屏:gitee.com/MTrun/big-... 41、拖拽:gitee.com/lowcode-ch... 42、可视化:gitee.com/datagear/d... 43、移动端:gitee.com/was666/as-... 44、redis管理:gitee.com/MaxBill/Re... 45、Api:gitee.com/freakchick... 46、报表:gitee.com/happy3344y... 47、在线表格:gitee.com/mengshukej... 48、在线文档:gitee.com/zmister/Mr... 49、在线文档2:github.com/fantastic... 50、知识库:gitee.com/kyxxjs/km_... 51、微社区:gitee.com/rocboss/pa... 52、在线文档预览:gitee.com/kekingcn/f... 53、低代码平台:gitee.com/godoforang... 54、低代码平台2:gitee.com/MTrun/go-v... 55、问卷调查:gitee.com/wkeyuan 56、问卷:gitee.com/TDuckApp/t... 57、问卷表单:gitee.com/wkeyuan/DW... 58、问卷2:gitee.com/surveyking... 59、低代码开发平台:github.com/zhangdais... 60、数据可视化服务平台解决方案 ——Davinci:github.com/edp963/da...
发布于 2023-12-07
Element UI 级联选择器 el-cascader 实现懒加载和搜索功能
2241 热度
0 条评论
Vue
当代的 Web 应用程序通常需要提供高度灵活和易用的用户界面,以满足用户对数据选择和搜索的需求。在前端开发中,级联选择器(Cascader)是一种常见的组件,可以帮助用户从多层级的数据中进行选择。 Element UI 是一个流行的 Vue.js 组件库,提供了丰富的 UI 组件,其中包括了 <el-cascader> 组件。该组件可以实现级联选择功能,但默认情况下不支持懒加载和搜索的结合。在本篇博客中,我们将重点介绍如何结合懒加载和搜索来增强 <el-cascader> 的功能。 先看效果 基础功能:级联、懒加载!、搜索 后端代码 Controller.java /** 获取地区信息列表 @param pxRegion 地区信息 @return 地区信息列表 / @GetMapping(value = "/getRegionList") public AjaxResult getRegionList(PxRegion pxRegion) { return AjaxResult.success(pxRegionService.getRegionList(pxRegion)); } 地区管理Service接口 /* 地区管理Service接口 @author 裴浩宇 @date 2023-12-06 / public interface IPxRegionService { /* 查询地区管理列表 @param pxRegion 地区管理 @return 地区管理集合 / List<PxRegion> getRegionList(PxRegion pxRegion); } 地区管理Service业务层处理 /* 地区管理Service业务层处理 @author 裴浩宇 @date 2023-12-06 */ @Service public class PxRegionServiceImpl implements IPxRegionService { @Resource private PxRegionMapper pxRegionMapper; /** 查询地区管理列表 @param pxRegion 地区管理 @return 地区管理 / @Override public List<PxRegion> getRegionList(PxRegion pxRegion) { List<PxRegion> regionList = pxRegionMapper.getRegionList(pxRegion); // 如果名称不为空,说明是模糊查询需要处理数据 if (StringUtils.isNotEmpty(pxRegion.getName())) { // 遍历所有地区 for (PxRegion region : regionList) { if (region.getLb() == 1) { // 市的话默认带出该市的区 // 参数 PxRegion param = new PxRegion(); param.setLb(2L); param.setSsdqdm(region.getId()); // 获取该市下的区 List<PxRegion> children = pxRegionMapper.getRegionList(param); region.setChildren(children); } else if (region.getLb() == 2) { region.setChildren(null); // 区的话 // 参数 PxRegion param = new PxRegion(); // 找到区的所属市 param.setId(region.getSsdqdm()); List<PxRegion> city = pxRegionMapper.getRegionList(param); city.get(0).setChildren(Collections.singletonList(region)); // 将市加入 regionList.add(city.get(0)); regionList.remove(region); } } } return regionList; } } 地区管理Mapper接口 /* 地区管理Mapper接口 @author 裴浩宇 @date 2023-12-06 */ public interface PxRegionMapper { /** 查询地区管理列表 @param pxRegion 地区管理 @return 地区管理集合 */ public List<PxRegion> getRegionList(PxRegion pxRegion); } 地区管理Mapper.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.pnkx.mapper.PxRegionMapper"> <resultMap type="PxRegion" id="PxRegionResult"> <result property="id" column="id"/> <result property="name" column="name"/> <result property="py" column="py"/> <result property="ssdqdm" column="ssdqdm"/> <result property="lb" column="lb"/> <result property="yb" column="yb"/> <result property="jlzt" column="jlzt"/> </resultMap> <sql id="selectPxRegionVo"> select id, name, py, ssdqdm, lb, yb, jlzt from yy_dq_ybdmk </sql> <select id="getRegionList" parameterType="PxRegion" resultMap="PxRegionResult"> <include refid="selectPxRegionVo"/> <where> <if test="id != null "> and id = #{id} </if> <if test="name != null and name != ''"> and name like concat('%', #{name}, '%') </if> <if test="py != null and py != ''"> and py = #{py} </if> <if test="ssdqdm != null "> and ssdqdm = #{ssdqdm} </if> <if test="lb != null "> and lb = #{lb} </if> <if test="yb != null "> and yb = #{yb} </if> <if test="jlzt != null "> and jlzt = #{jlzt} </if> </where> </select> </mapper> 前端代码 Region组件 <template> <div class="cascader"> <el-cascader v-model="selectedValues" :options="options" @change="handleChange" :before-filter="handleFilter" :props="props" separator=" - " filterable clearable placeholder="请选择省市区" /> </div> </template> <script> import {getRegionList} from "@/api/px/blog/region"; export default { name: "Region", props: { address: { type: Array, default: "" } }, watch: { address: { async handler() { // 获取省列表 await this.getRegionList(0); // 获取市列表 if (this.address[0]) await this.getRegionList(1, this.address[0]); // 获取区列表 if (this.address[1]) await this.getRegionList(2, this.address[1]); // 赋值 this.selectedValues = [ Number(this.address[0]), Number(this.address[1]), Number(this.address[2]), ]; }, immediate: true, deep: true }, }, data() { const _this = this; return { // 选中的省市区值 selectedValues: [], // 数据源,省市区的JSON数据 options: [], // 映射关系 props: { // value对应属性 value: "id", // label对应属性 label: "name", // 开启懒加载 lazy: true, lazyLoad: function (node, resolve) { const {level} = node; getRegionList({ lb: level, ssdqdm: node.value }).then(res => { resolve(res.data.filter(item => { if (level === 0) { // 判断是否已存在省 return !_this.options?.find(province => province.id === item.id) } else if (level === 1) { // 判断是否已存在市 return !_this.options.find(province => province.id === item.ssdqdm)?.children.find(city => city.id === item.id) } else if (level === 2) { // 找到省 const theProvince = _this.options.find(province => province.children.find(city => city.id === item.ssdqdm)); // 判断是否已存在区 return !theProvince?.children.find(city => city.id === item.ssdqdm)?.children.find(area => area.id === item.id) } }).map(item => { return { ...item, children: [], leaf: level >= 2 } })); }) } } }; }, methods: { /** 搜索 @param searchValue / async handleFilter(searchValue) { if (searchValue) { await this.getRegionList(undefined, undefined, searchValue); this.selectedValues = Number(searchValue); } }, /* 选择省市区 @param value / handleChange(value) { this.$emit("update:address", value); }, /* 获取地区列表 @param level @param parentCode @param name */ async getRegionList(level, parentCode, name) { await getRegionList({ lb: level, ssdqdm: parentCode, name }).then(res => { res.data.forEach(item => { if (item.lb === 0) { // 省 this.options.find(province => province.id === item.id) || this.options.push(item); } else if (item.lb === 1) { // 市 this.options.find(province => province.id === item.ssdqdm)?.children?.find(city => city.id === item.id) || this.options.find(province => province.id === item.ssdqdm).children.push({ ...item, children: item.children.map(area => { return { ...area, leaf: true } }) }); } else if (item.lb === 2) { // 区 // 找到省 const theProvince = this.options.find(province => province.children.find(city => city.id === item.ssdqdm)); // 找到市放入区 theProvince.children.find(city => city.id === item.ssdqdm)?.children?.find(area => area.id === item.id) || theProvince.children.find(city => city.id === item.ssdqdm)?.children.push({ ...item, leaf: true }); } }) }) } } } </script> 使用 <template> <region :address.sync="address"/> </template> <script> import Region from "@/components/Region/index.vue"; export default { components: { Region }, data() { return { // 地址 address: [140000, 140400, 140406] } }, watch: { address(val) { this.$message.success('选中: ' + val.join('-')); } } } </script> 在上述代码中,接口大致有三个参数:level(层级)、parentCode(父级编号)、name(名称用于模糊查询) 前端组件在初始时回显选中的地区,改变时触发emit事件使父组件修改,并且支持懒加载和搜索。 源码下载 点击下载源码
发布于 2023-12-01
Java 23种设计模式——组合模式(Composite Pattern)
2040 热度
0 条评论
Java
什么是组合模式? 组合模式是一种结构型设计模式,它允许我们将对象组织成树状结构,并以统一的方式处理这些对象。组合模式通过将对象分为两个主要角色来实现这一目标:叶节点和容器节点。叶节点表示树结构中的最底层对象,而容器节点则表示由叶节点或其他容器节点组成的对象。 组合模式的结构 组合模式的结构包含以下几个核心组件: Component(组件):定义叶节点和容器节点的共同接口,可以在该接口上定义默认行为和访问子节点的方法。 Leaf(叶节点):表示树结构中的最底层对象,没有子节点。 Composite(容器节点):由叶节点或其他容器节点组成的对象,可以包含子节点,并实现在Component接口中定义的方法。 下面是一个简化的类图,展示了组合模式的结构: +------------------+| Component | +------------------+ | +operation() | | +add(Component) | | +remove(Component) | | +getChild(int) | +------------------+ / / / / / +--------------+ +--------------+ | Leaf | | Composite | +--------------+ +--------------+ | +operation() | | +operation() | +--------------+ +--------------+ 组合模式的应用场景 组合模式适用于以下情况: 当你希望以统一的方式处理对象集合时,可以使用组合模式。这种方式使得客户端可以将单个对象和组合对象一视同仁。 当你有一个对象树,且对象之间具有层次关系时,组合模式可以帮助你更好地管理和操作这些对象。 当你希望添加或移除树中的对象时,组合模式能够提供一种一致的方式来执行这些操作。 示例代码 shapes shapes/Shape.java: 通用形状接口 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; public interface Shape { int getX(); int getY(); int getWidth(); int getHeight(); void move(int x, int y); boolean isInsideBounds(int x, int y); void select(); void unSelect(); boolean isSelected(); void paint(Graphics graphics); } shapes/BaseShape.java: 提供基本功能的抽象形状 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; abstract class BaseShape implements Shape { public int x; public int y; public Color color; private boolean selected = false; BaseShape(int x, int y, Color color) { this.x = x; this.y = y; this.color = color; } @Override public int getX() { return x; } @Override public int getY() { return y; } @Override public int getWidth() { return 0; } @Override public int getHeight() { return 0; } @Override public void move(int x, int y) { this.x += x; this.y += y; } @Override public boolean isInsideBounds(int x, int y) { return x > getX() && x < (getX() + getWidth()) && y > getY() && y < (getY() + getHeight()); } @Override public void select() { selected = true; } @Override public void unSelect() { selected = false; } @Override public boolean isSelected() { return selected; } void enableSelectionStyle(Graphics graphics) { graphics.setColor(Color.LIGHT_GRAY); Graphics2D g2 = (Graphics2D) graphics; float[] dash1 = {2.0f}; g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 2.0f, dash1, 0.0f)); } void disableSelectionStyle(Graphics graphics) { graphics.setColor(color); Graphics2D g2 = (Graphics2D) graphics; g2.setStroke(new BasicStroke()); } @Override public void paint(Graphics graphics) { if (isSelected()) { enableSelectionStyle(graphics); } else { disableSelectionStyle(graphics); } // ... } } shapes/Dot.java: 点 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; public class Dot extends BaseShape { private final int DOT_SIZE = 3; public Dot(int x, int y, Color color) { super(x, y, color); } @Override public int getWidth() { return DOT_SIZE; } @Override public int getHeight() { return DOT_SIZE; } @Override public void paint(Graphics graphics) { super.paint(graphics); graphics.fillRect(x - 1, y - 1, getWidth(), getHeight()); } } shapes/Circle.java: 圆形 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; public class Circle extends BaseShape { public int radius; public Circle(int x, int y, int radius, Color color) { super(x, y, color); this.radius = radius; } @Override public int getWidth() { return radius * 2; } @Override public int getHeight() { return radius * 2; } public void paint(Graphics graphics) { super.paint(graphics); graphics.drawOval(x, y, getWidth() - 1, getHeight() - 1); } } shapes/Rectangle.java: 三角形 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; public class Rectangle extends BaseShape { public int width; public int height; public Rectangle(int x, int y, int width, int height, Color color) { super(x, y, color); this.width = width; this.height = height; } @Override public int getWidth() { return width; } @Override public int getHeight() { return height; } @Override public void paint(Graphics graphics) { super.paint(graphics); graphics.drawRect(x, y, getWidth() - 1, getHeight() - 1); } } shapes/CompoundShape.java: 由其他形状对象组成的复合形状 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class CompoundShape extends BaseShape { protected List<Shape> children = new ArrayList<>(); public CompoundShape(Shape... components) { super(0, 0, Color.BLACK); add(components); } public void add(Shape component) { children.add(component); } public void add(Shape... components) { children.addAll(Arrays.asList(components)); } public void remove(Shape child) { children.remove(child); } public void remove(Shape... components) { children.removeAll(Arrays.asList(components)); } public void clear() { children.clear(); } @Override public int getX() { if (children.size() == 0) { return 0; } int x = children.get(0).getX(); for (Shape child : children) { if (child.getX() < x) { x = child.getX(); } } return x; } @Override public int getY() { if (children.size() == 0) { return 0; } int y = children.get(0).getY(); for (Shape child : children) { if (child.getY() < y) { y = child.getY(); } } return y; } @Override public int getWidth() { int maxWidth = 0; int x = getX(); for (Shape child : children) { int childsRelativeX = child.getX() - x; int childWidth = childsRelativeX + child.getWidth(); if (childWidth > maxWidth) { maxWidth = childWidth; } } return maxWidth; } @Override public int getHeight() { int maxHeight = 0; int y = getY(); for (Shape child : children) { int childsRelativeY = child.getY() - y; int childHeight = childsRelativeY + child.getHeight(); if (childHeight > maxHeight) { maxHeight = childHeight; } } return maxHeight; } @Override public void move(int x, int y) { for (Shape child : children) { child.move(x, y); } } @Override public boolean isInsideBounds(int x, int y) { for (Shape child : children) { if (child.isInsideBounds(x, y)) { return true; } } return false; } @Override public void unSelect() { super.unSelect(); for (Shape child : children) { child.unSelect(); } } public boolean selectChildAt(int x, int y) { for (Shape child : children) { if (child.isInsideBounds(x, y)) { child.select(); return true; } } return false; } @Override public void paint(Graphics graphics) { if (isSelected()) { enableSelectionStyle(graphics); graphics.drawRect(getX() - 1, getY() - 1, getWidth() + 1, getHeight() + 1); disableSelectionStyle(graphics); } for (Shape child : children) { child.paint(graphics); } } } editor editor/ImageEditor.java: 形状编辑器 package com.pnkx.designpatterns.composite.editor; import com.pnkx.designpatterns.composite.shapes.CompoundShape; import com.pnkx.designpatterns.composite.shapes.Shape; import javax.swing.; import javax.swing.border.Border; import java.awt.; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; public class ImageEditor { private EditorCanvas canvas; private CompoundShape allShapes = new CompoundShape(); public ImageEditor() { canvas = new EditorCanvas(); } public void loadShapes(Shape... shapes) { allShapes.clear(); allShapes.add(shapes); canvas.refresh(); } private class EditorCanvas extends Canvas { JFrame frame; private static final int PADDING = 10; EditorCanvas() { createFrame(); refresh(); addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { allShapes.unSelect(); allShapes.selectChildAt(e.getX(), e.getY()); e.getComponent().repaint(); } }); } void createFrame() { frame = new JFrame(); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); JPanel contentPanel = new JPanel(); Border padding = BorderFactory.createEmptyBorder(PADDING, PADDING, PADDING, PADDING); contentPanel.setBorder(padding); frame.setContentPane(contentPanel); frame.add(this); frame.setVisible(true); frame.getContentPane().setBackground(Color.LIGHT_GRAY); } public int getWidth() { return allShapes.getX() + allShapes.getWidth() + PADDING; } public int getHeight() { return allShapes.getY() + allShapes.getHeight() + PADDING; } void refresh() { this.setSize(getWidth(), getHeight()); frame.pack(); } public void paint(Graphics graphics) { allShapes.paint(graphics); } } } Main.java: 客户端代码 package com.pnkx.designpatterns.composite; import com.pnkx.designpatterns.composite.editor.ImageEditor; import com.pnkx.designpatterns.composite.shapes.Circle; import com.pnkx.designpatterns.composite.shapes.CompoundShape; import com.pnkx.designpatterns.composite.shapes.Dot; import com.pnkx.designpatterns.composite.shapes.Rectangle; import java.awt.*; public class Main { public static void main(String[] args) { ImageEditor editor = new ImageEditor(); editor.loadShapes( new Circle(10, 10, 10, Color.BLUE), new CompoundShape( new Circle(110, 110, 50, Color.RED), new Dot(160, 160, Color.RED) ), new CompoundShape( new Rectangle(250, 250, 100, 100, Color.GREEN), new Dot(240, 240, Color.GREEN), new Dot(240, 360, Color.GREEN), new Dot(360, 360, Color.GREEN), new Dot(360, 240, Color.GREEN) ) ); } } 执行结果 总结 组合模式是一种强大且灵活的设计模式,它允许我们以统一的方式处理对象集合,并将对象组织成树状结构。通过定义共同接口和使用叶节点和容器节点,组合模式使得处理复杂对象结构变得简单而一致。在开发具有层次结构的系统时,组合模式是一个有用的工具。 源码 组合模式源码下载 Java 23种设计模式(含源码)
发布于 2023-11-23
Java 23种设计模式——适配器模式(Adapter)
2158 热度
0 条评论
Java
适配器模式是一种常见的设计模式,它可以用来将一个类的接口转换成客户希望的另外一个接口,从而让原本由于接口不兼容而不能一起工作的类能够协同工作。在Java中,适配器模式通常涉及三个角色:目标接口、适配器和被适配者。 目标接口 目标接口是客户端所期望使用的接口,也可以是一个抽象类。它定义了客户端需要调用的方法,但是这些方法的参数类型或返回值类型可能与原有的接口不符。目标接口的存在是为了让客户端可以通过统一的接口来访问不同的子系统,而不需要关注具体的实现细节。 下面是一个示例代码,演示了如何定义一个目标接口: public interface Target {void request(); } 被适配者 被适配者是需要被适配的类,它定义了客户端不关心的接口。被适配者通常是一个已经存在的类,它的接口与目标接口不兼容,需要通过适配器进行转换。被适配者的存在是为了提供一些功能,但是其接口与目标接口不匹配,无法直接使用。 以下是一个示例代码,演示了如何定义一个被适配者类: public class Adaptee { public void specificRequest() { System.out.println("Adaptee's specific request"); } } 适配器 适配器是实现了目标接口的类,并且包装了一个需要适配的类的实例,以便将其接口转换成客户端所期望的接口。适配器通常包含一个被适配者对象的引用,在目标接口方法中调用被适配者对象的方法,并将其返回值转换成客户端期望的类型。 以下是一个示例代码,演示了如何定义一个适配器类: public class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void request() { adaptee.specificRequest(); } } 在这个示例中,Adapter 类实现了 Target 接口,并且包装了 Adaptee 的实例。在 request 方法中调用了 Adaptee 的 specificRequest 方法。 客户端代码 客户端代码需要创建一个适配器对象,并且通过适配器对象来调用被适配者的方法。客户端不需要知道适配器和被适配者之间的具体关系,只需要知道目标接口即可。 以下是一个示例代码,演示了如何在客户端代码中使用适配器模式: public class Main { public static void main(String[] args) { Adaptee adaptee = new Adaptee(); Target adapter = new Adapter(adaptee); adapter.request(); } } 在这个示例中,客户端首先创建一个 Adaptee 对象,然后创建一个适配器对象,并将 Adaptee 对象作为参数传递给适配器。最后,客户端通过适配器来调用目标接口的方法。 总结 适配器模式是一种非常有用的设计模式,它可以帮助我们解决接口不兼容的问题。通过使用适配器模式,我们可以将一个类的接口转换成客户端所期望的另外一个接口,从而让原本由于接口不兼容而不能一起工作的类能够协同工作。在Java中,实现适配器模式通常需要定义目标接口、被适配者和适配器三个角色。 Java 23种设计模式(含源码)