新版druid监控页面SQL不显示问题

新版druid数据源驱动的SQL监控如果用以前的老版本配置是无法监控到SQL的: application.yml spring: datasource: druid: filters: - stat - wall - log4j启动应用之后访问druid监控页面,除了SQL相关的页面都正常工作,但是访问SQL监控页面时没有看到SQL记录。查看监控页面 数据源 菜单发现 filter类名 显示的是空,估计是filter配置有问题导致。 查阅官方文档发现filter配置有变更,改成以下形式即可统计SQL,同时在数据源页面 filter类名 会显示正常。 application.yml spring: datasource: druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 5000 # 状态监控 filter: stat: enabled: true db-type: mysql log-slow-sql: true slow-sql-millis: 2000 # 监控过滤器 web-stat-filter: enabled: true exclusions: - "*.js" - "*.gif" - "*.jpg" - "*.png" - "*.css" - "*.ico" - "/druid/*" # druid 监控页面 stat-view-servlet: enabled: true url-pattern: /druid/* reset-enable: false login-username: root login-password: root数据源filter类名:com.alibaba.druid.filter.stat.StatFilter ...

July 10, 2019 · 1 min · jiezi

了解-Daily-Scrum-活动

如Scrum指南中所述,每日Scrum是一个15分钟的时间盒事件,供开发团队同步活动并为接下来的24小时制定计划。每日Scrum每天都在Sprint举行。在此,开发团队计划在接下来的24小时内开展工作。通过检查自上次每日Scrum以来的工作并预测即将到来的Sprint工作,这可以优化团队协作和性能。Daily Scrum每天都在同一时间和地点举行,以降低复杂性。 Scrum核心的检查与适应 开发团队使用Daily Scrum检查Sprint目标的进展情况,并检查Sprint Backlog中完成工作的进度趋势。Daily Scrum优化了开发团队满足Sprint目标的可能性。每天,开发团队应该了解它打算如何作为一个自组织团队一起工作,以实现Sprint目标,并在Sprint结束时创建预期的增量。开发团队或团队成员经常在每日Scrum之后会面,以进行详细讨论,或者调整或重新规划Sprint的其余工作。 每日Scrums改善沟通,消除其他会议,识别发展障碍,删除,突出和促进快速决策,并提高开发团队的知识水平。这是一次关键的检查和改编会议。 会议结构由开发团队制定,如果重点关注Sprint目标的进展,可以采用不同的方式进行。一些开发团队将使用问题,一些将更多基于讨论。 Scrum Master在每日Scrum中的角色在Scrum Master的保证了开发团队的会议,但开发团队负责进行日常Scrum。Scrum Master教导开发团队将每日Scrum保持在15分钟的时间内。 Daily Scrum是开发团队的内部会议。如果其他人在场,Scrum Master会确保他们不会中断会议。

July 8, 2019 · 1 min · jiezi

fastjson默认是无序的

fastjson默认是无序的不知道大家注意到没有,fastjson默认是无序的。我是在做参数加密的时候发现的。JSONObject object = new JSONObject();然后, put,put,put。最后得到的结果和put的顺序不一致。 查看fastjson的源码: 可以看到构造函数根据ordered参数判断使用LinkedHashMap(有序)还是HashMap(无序)的。默认是无序的因此,如果我们需要json是有序的话,我们可以在构造函数中传入ordered参数(true)实现。 未完待续,有问题请留言!个人博客地址: https://blog.ailijie.top/arch...

July 8, 2019 · 1 min · jiezi

Spring-Cloud-微服务-入门实战与进阶

其中之一就是,时不时可以为读者朋友们送一些朋友的新书!这次找「猿天地」公众号的作者 尹吉欢 要了2 本《Spring Cloud微服务 入门 实战与进阶》。这是一部从技术原理、工程实践和进阶提升 3 个维度讲解 SpringCloud 微服务架构与开发的著作。 作者在 SpringCloud 微服务领域有丰富的工程实践经验,它将带领读者零基础入门 Spring Cloud 微服务,并快速掌握动手实践能力,最终进阶为 SpringCloud 微服务领域的技术达人。 全书共 21 章,分为 4 个部分: 第一部分准备篇(第 1~2 章) 首先对微服务和 Spring Cloud 的概念、优劣势、功能模块等做了整体性的介绍,然后演示了如何搭建 Spring Cloud 的开发环境,最后对 Spring Cloud做了详细的介绍。 第二部分基础篇(第 3~7 章) 对 Eureka注册中心、客户端负载均衡Ribbon、声明式REST客户端Feign、Hystrix服务容错处理、API网关等 Spring Cloud 的重要模块的技术原理、配置、使用等做了详尽的讲解。 第三部分实战篇(第 8~14 章) 对微服务架构中的普遍问题给出了实战解决方案,包括选择配置中心 Apollo、自研发配置中心、分布式跟踪、微服务安全认证、SpringBoot Admin管理微服务、快速生成 API文档等实用性内容。 宝宝起名网 第四部分高级篇(第 15~21 章) 重点讲解了 Spring Cloud 的扩展性的使用,比如API网关 Zuul, Spring Cloud Gateway、微服务的缓存和存储、分布式事务解决方案、任务调度、分库分表,以及大量优秀的生产实践经验等。 本书基于比较稳定的 Spring Cloud Finchley.SR2版本和 Spring Boot 2.0.6.RELEASE 版本编写。 ...

July 8, 2019 · 1 min · jiezi

Java程序员该掌握SpringBoot了

为什么会出现SpringBoot 随着使用 Spring 运用的越来越广泛,Spring这个开源框架也在慢慢的壮大,但是问题也随之伴随而来了,大量的配置文件让开发者很是烦恼,要将大量的时间用在配置上。 Spring g也意官方也识到了这些问题,所以 2013 年初开始的 Spring Boot 项目的研发,2014年4月,Spring Boot 1.0.0 发布。 在2016年,在国内 Spring Boot 开始正真使用了起来,Springboot也在逐渐的完善,从图中可以看出用户指数在不断的上升。 简单介绍Springboot Spring Boot 是由 Pivotal 团队开发的框架,其作用是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置,简单理解就是springboot并不是什么新型的框架,而是整合了spring,springmvc等框架,默认了很多配置,从而减少了开发者的开发时间。 Spring Boot 简化了基于 Spring 的应用开发,通过少量的代码就能创建一个独立的、产品级别的 Spring 应用。 Spring Boot 为 Spring 平台及第三方库提供开箱即用的设置。 SpringBoot有点: 使用 Spring 项目引导页面可以在几秒构建一个项目支持关系数据库和非关系数据库支持运行期内嵌容器,如 Tomcat、Jetty强大的开发包,支持热启动自动管理依赖自带应用监控支持各种 IED,如 IntelliJ IDEA 、NetBeans 生辰八字起名字 SpringBoot能带来哪些便捷? 用SpringBoot 单元测试更简单: 我们只需要在项目中引入spring-boot-start-test依赖包,加上注解,就可以对数据库、Web 等各种情况进行测试,十分方便。 用SpringBoot 配置变得更简单: springboot最显著的有点就是让本来繁琐的配置,变的十分简单,使得程序开发者有更多的时间去写真正的业务代码。 用SpringBoot 部署变得更简单: pring Boot 内嵌Tomcat容器。使用 Spring Boot 开发项目,不需要关心容器的环境问题,专心写业务代码即可。( 用Jenkins 部署 Spring Boot 项目非会使项目构建更简单,感兴趣的可以去看看) ...

July 8, 2019 · 1 min · jiezi

SpringBoot一搭建

有几天没写了、今天我们讲下SpringBoot的搭建 之前搭建项目要配置各种各样的配置文件,实在是太麻烦了。因而SpringBoot应用而成,它集成了各种框架的使用配置,就像Mavne集成了所有的jar一样。 学习一样东西,先看到它的样子以及成果,而后再去进一步了解他的原理,这样学习起来相对快些。 那么我们今天就动手搭建一个最简单SpringBoot项目。 如果有STS(Spring Tools Suite:就是一个被包装过的Eclipse)的话会省事一点点、如果没有也没关系。没有必要为了这么一点省事、去安装一个STS。先介绍下用STS怎么搭建SpringBoot项目,然后再介绍没有STS的情况下搭建项目 1、使用STS搭建项目 搭建步骤:File->New->Other->Spring Boot->Spring Start Project、接下来就如图了。 2、不使用STS搭建项目 2.1、登录start.spring.io、界面如下 2.2、根据图上提示一步一步操作即可 2.3、把下载下来的项目导入IDE即可 3、搭建后效果 3.1、看看目录结构 3.2、再看看Application.java类 3.3、看看Pom.xml文件 这里自动引入了spring-boot-starter-test依赖、还有刚才手动选择的web依赖spring-boot-starter-web也被引入了 现在一个SpringBoot项目基础框架已经搭建起来了,来看看怎么启动。启动SpringBoot项目有三种方式 生辰八字起名字 1、直接在Application这个中使用Run As Java Application运行main方法 2、项目根目录使用“mvn spring-boot:run”命令运行 mvn spring-boot:run 3、使用mvn install生成jar、然后在使用java -jar *.jar 运行 mvn install java -jar xxxx.jar 刚才我们引入了web依赖,现在看看如何使用SpringBoot运行“Hello World” 简单点,我们直接在Application.java这个类里写。 现在启动项目,访问localhost:8080。看到了什么。 提示: 1、这里使用的SpringBoot2.1.0、因此需要使用java8。 2、我搭建时maven私服、pom文件的<parent>会依赖失败。报错内容 也不管私服是什么原因了、直接在IDE的配置中使用maven的默认配置(不使用私服)。然后在update project下项目。问题解决了 到这简单搭建就完成了, 简单吧。

July 8, 2019 · 1 min · jiezi

SpringBoot框架由浅入深深度解读

一、入门 1.简介 Spring Boot是一个简化Spring开发的框架。用来监护spring应用开发,约定大于配置,去繁就简,just run 就能创建一个独立的,产品级的应用。 我们在使用Spring Boot时只需要配置相应的Spring Boot就可以用所有的Spring组件,简单的说,spring boot就是整合了很多优秀的框架,不用我们自己手动的去写一堆xml配置然后进行配置。从本质上来说,Spring Boot就是Spring,它做了那些没有它你也会去做的Spring Bean配置。 2.优点 3.单体应用与微服务 单体应用是把所有的应用模块都写在一个应用中,导致项目越写越大,模块之间的耦合度也会越来越高。微服务是一种架构风格,用微服务可以将应用的模块单独部署,对不同的模块进行不同的管理操作,不同的模块生成小型服务,每个功能元素最后都可以成为一个可以独立替换、独立升级的功能单元,各个小型服务之间通过http进行通信。 4.Spring Boot的核心特点 ·微服务: 使用Spring Boot可以生成独立的微服务功能单元 ·自动配置: 针对很多Spring应用程序常见的应用功能,Spring Boot能自动提供相关配置 ·起步依赖: 告诉Spring Boot需要什么功能,它就能引入需要的库。 ·命令行界面: 这是Spring Boot的可选特性,借此你只需写代码就能完成完整的应用程序,无需传统项目构建。 ·Actuator: 让你能够深入运行中的Spring Boot应用程序。 5.简单案例:使用maven创建HelloWorld项目 第一步:首先要配置Spring Boot 依赖 <parent><groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> </parent>Spring Boot的版本仲裁中心;以后我们导入依赖默认是不需要写版本。(没有在dependencies里面管理的依赖自然需要声明版本号) <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> spring-boot-starter-web: spring-boot-starter:spring-boot场景启动器。帮我们导入了web模块正常运行所依赖的组件。 Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter 相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器 ·第二步:新建包结构如下图 ·第三步:编辑相对应的类代码 ·HelloController.java @RestController//标注是一个Controller类 并且支持json数据格式输出public class HelloController { //通过 http://localhost:8080/sayHello访问该方法 @RequestMapping("/sayHello") public String hello(){ return "hello world"; } } ...

July 8, 2019 · 2 min · jiezi

什么是敏捷中的跨职能团队

什么是敏捷中的跨职能团队?跨职能 - 团队或个人会员?摘要传统上,项目是围绕组件团队(即UX,Dev,Business,Tester和......)组织的,任何需要一系列组件专业知识的版本都需要涉及多个组件团队。通常,不同的团队将有不同的优先级,这不可避免地导致产品发布周期中的瓶颈。 根据维基百科的说法,跨职能团队是一群具有不同职能专长的人,致力于实现共同目标。提高团队质量的最佳方法之一就是使其具有交叉功能。跨职能团队拥有将想法转变为工作产品的所有必要技能。 在Scrum的指南表示“在Scrum团队由的产品负责人,开发团队和Scrum Master的。Scrum团队是自组织和跨职能的。与组件团队方法相比,跨职能团队是由来自公司不同职能领域的人员组成的团队。 - 它不仅应由技术专家(后端,前端开发人员,QA工程师等)组成,还应包括业务分析师,市场营销和用户体验专家或其他积极参与项目的人员。 。 敏捷中的跨职能团队跨职能团队是帮助Scrum团队取得成功和高效工作的关键因素之一。跨职能团队具有更大的灵活性,能够更快地响应不断变化的需求,并能更好地处理持续的支持和维护。 Mike Cohn表示,“也许敏捷中最普遍和最持久的神话是,跨职能团队是每个人拥有完成工作所需的所有技能的团队。这根本不是真的......跨职能团队的成员具有各种技能,但这并不意味着每个成员都具备所有技能“ 实际上,敏捷跨职能团队不仅意味着团队本身具有跨职能性,而且每个团队成员最好也可以扮演多个角色。作为专家并不意味着会员以了解其他事情为代价而知道一件事情,理想情况下,人才形象应该是T形的,因为他/她在一个专业领域具有深度,而在其他领域具有广度。 T形专业人士跨职能团队的优势可以改善跨职能领域的协调,增加产品和流程的创新,缩短开发周期,以便从关键客户接触点获得反馈。跨职能团队消除了大多数(如果不是全部)冲突优先级问题,因为团队中的每个人都具有实现共同目标的相同优先级。 Scrum角色 什么是Scrum团队?什么是Scrum的自组织团队?Scrum团队如何运作? - 简要指南如何成为Scrum项目的优秀产品负责人?什么是产品负责人在Scrum中的角色?敏捷开发:如何成为合格的Scrum Master?什么是Scrum中的猪和鸡?项目经理与Scrum Master对项目所有者什么是三个Scrum角色?什么是Scrum Master?角色和责任什么是敏捷中的跨职能团队?作为Scrum Master,您如何帮助您的项目所有者?

July 8, 2019 · 1 min · jiezi

读取燃尽图

什么是燃尽图?燃尽图是在“剩余工作”和“时间”之间绘制的折线图。Scrum团队使用这些图表来跟踪他们在sprint中“烧毁”任务时的进度。 Burndown图表相当容易理解,但团队通常很难理解图表背后的全部含义。 燃尽图的一部分 X轴: 图表的X轴始终表示时间(通常以天为单位)。 Y轴: y轴表示在sprint中要完成的剩余工作。这可以由剩余任务(在任务的数量中)或剩余的努力量(在故事点/小时中)来表示。 进度线: 进度线表示您的团队在冲刺方面的进展情况。它每天都会使用新的剩余估算进行更新。随着冲刺的进行,这条线将指示您的团队是否正常,以及是否需要采取任何纠正措施。 准则: 这是在图表上从左到右向下绘制的对角线。理想情况下,您的冲刺进度线应尽可能接近指南。如果您的团队能够在整个sprint中以稳定的速度完成所有故事,那么您的进度线最终将与指南完全相同。 选择正确的跟踪指标Y轴作为剩余任务的数量: 这是在任何给定日期保留在sprint中的任务数的图表。随着任务在一段时间内完成,进度线开始向下移动。这种方法的最大优点是图形易于理解。 使用这种类型的燃尽图表的缺点是不估计任务。并非所有任务都需要同等的努力来完成。通常情况下,在冲刺结束时离开困难任务的团队发现尽管在大部分时间内处于“正常”状态,但他们无法完成所有任务。 Y轴作为剩余的故事点: 这是随着时间的推移剩余多少故事/估计点的图表。故事点是通过其复杂性来估计任务的好方法。这标准化了完成任务所需的工作量。 该图最终类似于先前的方法,但估计点消除了跟踪任务完成带来的模糊性。 燃尽图何时有用?Burndown图表在以下两个方面都很有用:主动冲刺和冲刺回顾。 冲刺期间: 在正在进行的冲刺期间查看燃尽图表可以帮助Scrum主管或产品负责人回答以下问题: 冲刺是否按目标进展?所有的故事(或任务)都能按时完成吗?需要采取哪些纠正措施来达到目标?冲刺后: Burndown Charts是Sprint团队表现的指标。在sprint回顾中,燃尽图可作为围绕估计准确性,冲刺性能,障碍以及随后冲刺期间所做更改的讨论的参考。 阅读燃尽图通过将冲刺进度线与指南进行比较来读取Burndown图。两条线彼此越接近,在截止日期之前完成所有任务的机会就越大。 落后于时间表:如果进度线高于指南,则意味着您的团队落后于计划,并且理想情况下应该完成更多工作。正轨:进度线和指南紧密相连。如果他们保持目前的速度,团队将击中目标。提前计划:进度线低于指南。即使在冲刺结束日期之前,该团队仍有望达到目标。这可能是因为在计划冲刺时任务被高估了。团队可以向sprint添加更多任务,以确保每个人都在sprint中占用。Burndown图表模式以下是使用燃尽图跟踪冲刺时可能会观察到的一些常见模式。 不正常的状态更新(或)不正确的故事细分 以块为单位更新其状态的团队(例如,在每周结束时)以阶梯图形结束。定期状态更新可提供更准确的图表。理想情况下,Sprint团队应该每天更新他们的任务。 阶梯图有时也是不正确的任务分解的结果。如果任务没有足够的细分,个别问题可能需要很长时间才能完成:导致几天内缺乏进展的错觉。避免这种情况的最佳方法是将大型任务分解为小的可执行工作块。 冲刺结束时更新进度 这是一个团队的燃尽图表,它在冲刺审查会议前一天更新所有状态。此图表在回顾期间增加的价值非常小,团队应确保每天更新其状态。 提前完成。 此图表代表一个团队过高估计完成任务所需的时间。 如果该团队保持当前的速度,他们最终将在sprint结束日期之前完成所有任务。产品所有者需要向sprint添加更多任务(由估计的急剧上升表示),以便在整个sprint期间保持团队忙碌。 落后于时间表。 这是一个努力跟上理想指南的团队的燃尽图。 这很可能是因为他们低估了完成任务所需的工作量。纠正课程的两种方法是 为团队添加更多人(或)通过删除故事或任务来缩小sprint的范围。敏捷与Scrum基础综合Scrum指南什么是Scrum的三大支柱?什么是敏捷软件开发?Scrum在3分钟内完成什么是5个Scrum值?Scrum的演变是什么?经典项目管理与敏捷项目管理为什么Scrum难以掌握?什么是Scrum中的速度?什么是敏捷?什么是Scrum?敏捷中的三个Amigos发展战略是什么?经验过程控制与定义过程控制如何保持Scrum的透明度?Scrum vs Waterfall vs Agile vs Lean vs Kanban什么是Scrum框架中的3355?

July 8, 2019 · 1 min · jiezi

好好面试你必须要懂的SpringAop

【干货点】此处是【好好面试】系列文的第10篇文章。看完该篇文章,你就可以了解Spring中Aop的相关使用和原理,并且能够轻松解答Aop相关的面试问题。 在实际研发中,Spring是我们经常会使用的框架,毕竟它们太火了,也因此Spring相关的知识点也是面试必问点,今天我们就大话Aop。特地在周末推文,因为该篇文章阅读起来还是比较轻松诙谐的,当然了,更主要的是周末的我也在充电学习,希望有追求的朋友们也尽量不要放过周末时间,适当充电,为了走上人生巅峰,迎娶白富美。【话说有没有白富美介绍(o≖◡≖)】接下来,直接进入正文。 为什么要有aop我们都知道Java是一种面向对象编程【也就是OOP】的语言,不得不说面向对象编程是一种及其优秀的设计,但是任何语言都无法十全十美,对于OOP语言来说,当需要为部分对象引入公共部分的时候,OOP就会引入大量的重复代码【这些代码我们可以称之为横切代码】。而这也是Aop出现的原因,没错,Aop就是被设计出来弥补OOP短板的。Aop便是将这些横切代码封装到一个可重用模块中,继而降低模块间的耦合度,这样也有利于后面维护。 Aop是什么东西学过Spring的都知道,Spring内比较核心的功能便是Ioc和Aop,Ioc的主要作用是应用对象之间的解耦,而Aop则可以实现横切代码【如权限、日志等】与他们绑定的对象之间的解耦,举个浅显易懂的小栗子,在用户调用很多接口的地方,我们都需要做权限认证,判断用户是否有调用该接口的权限,如果每个接口都要自己去做类似的处理,未免有点sb了,也不够装x,因此Aop就可以派上用场了,将这些处理的代码放到切片中,定义一下切片、连接点和通知,刷刷刷跑起来就ojbk了。 想要了解Aop,就要先理解以下几个术语,如PointCut、Advice、JoinPoint。接下来尽量用白话文描述下。 PointCut【切点】其实切点的概念很好理解,你想要去切某个东西之前总得先知道要在哪里切入是吧,切点格式如下:execution( com.nuofankj.springdemo.aop.Service.*(..))可以看出来,格式使用了正常表达式来定义那个范围内的类、那些接口会被当成切点,简单明了。 AdviceAdvice行内很多人都定义成了通知,但是我总觉得有点勉强。所谓的Advice其实就是定义了Aop何时被调用,确实有种通知的感觉,何时调用其实也不过以下几种: Before 在方法被调用之前调用After 在方法完成之后调用After-returning 在方法成功执行之后调用After-throwing 在方法抛出异常之后调用Around 在被通知的方法调用之前和调用之后调用JoinPoint【连接点】JoinPoint连接点,其实很好理解,上面又有通知、又有切点,那和具体业务的连接点又是什么呢?没错,其实就是对应业务的方法对象,因为我们在横切代码中是有可能需要用到具体方法中的具体数据的,而连接点便可以做到这一点。 给出一个Aop在实际中的应用场景先给出两个业务内的接口,一个是聊天,一个是购买东西接下来该给出说了那么久的切片了可以从中看到PointCut【切点】是 execution( com.nuofankj.springdemo.aop.Service.*(..))Advice是 BeforeJoinPoint【连接点】是 MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();代码浅显易懂,其实就是将ChatService和BuyService里边给userId做权限校验的逻辑抽出来做成切片。 那么如何拿到具体业务方法内的具体参数呢?这里是定义了一个新的注解作用可以直接看注释,使用地方如下可以看到对应接口使用了AuthPermission的注解,而取出的地方在于是的,这样便可以取出来对应的接口传递的userId具体是什么了,而校验逻辑可以自己处理。 送佛送到西,不对,撸码撸整套,接下来给出运行的主类可以看到,上面有一个接口传递的userId是1,另一个是123,而上面权限认证只有1才说通过,否则会抛出异常。 运行结果如下运行结果可想而知,1的通过验证,123的失败。 Aop的原理解析关于原理解析,由于大家都不喜欢看篇幅太长的文章,因此打算拆分成两篇进行,下篇文章会对Aop的原理和设计思想进行解析,有兴趣的朋友可以关注我一波。 公众号主营:服务端编程相关技术解说,具体可以看历史文章。 公众号副业:各种陪聊吹水,包括技术、就业、人生经历、大学生活、内推等等。 欢迎关注,一起侃大山

July 7, 2019 · 1 min · jiezi

Redis基本操作之Java实现所有类型

前不久分享过Redis的基本数据结构及基本命令详解。在熟悉了redis的基本操作之后(如果还有对redis的基本操作不熟悉的,可以点击前面的连接先熟悉下),今天给大家分享下实际开发中对redis操作的Java实现版本。 Maven依赖使用maven来构建项目在当下应该已经是主流了,所以我们也不例外使用了Maven。因为使用了spring对redis封装的jar,所以也需要引入spring基本jar,Maven依赖如下: <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.1.5.RELEASE</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.5.RELEASE</version></dependency><dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.1.3.RELEASE</version></dependency><dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.2</version></dependency><dependency> <groupId></groupId> <artifactId>redis</artifactId></dependency>实现代码实现代码篇幅有点长,而且segmentfault代码div的高度有限制,建议大家把代码拷贝到开发工具中再阅读。 package spring.redis;import org.springframework.beans.factory.InitializingBean;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.dao.DataAccessException;import org.springframework.data.redis.connection.RedisConnection;import org.springframework.data.redis.core.RedisCallback;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.RedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import org.springframework.stereotype.Service;import java.io.UnsupportedEncodingException;import java.util.*;import java.util.stream.Collectors;@Servicepublic class SpringRedisHandler implements InitializingBean { //redis编码 private static final String redisCode = "utf-8"; private static final String EmptyString = ""; @Autowired private RedisTemplate<String, String> jtRedis; /** * 设置key-value【不含超时时间】 * * @param key * @param value */ public void set(String key, Object value) { this.set(key, String.valueOf(value), 0L); } /** * 设置key-value【含超时时间】 * * @param key * @param value * @param liveTime */ public void set(String key, Object value, long liveTime) { this.set(key.getBytes(), String.valueOf(value).getBytes(), liveTime); } @SuppressWarnings({"unchecked", "rawtypes"}) private void set(final byte[] key, final byte[] value, final long liveTime) { jtRedis.execute(new RedisCallback() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { connection.set(key, value); if (liveTime > 0) { connection.expire(key, liveTime); } return 1L; } }); } /** * get key的值 * * @param key * @return */ public String get(final String key) { return jtRedis.execute(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { try { return new String(connection.get(key.getBytes()), redisCode); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return ""; } } }); } /** * 是否存在key * * @param key * @return */ public boolean exists(final String key) { return jtRedis.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.exists(key.getBytes()); } }); } /** * 某数据中所有key的总数 * * @return */ public long dbSize() { return jtRedis.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { return connection.dbSize(); } }); } /** * 检测redis服务器是否能平通 */ public String ping() { return jtRedis.execute(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { return connection.ping(); } }); } /** * value增加某个值 * * @param key * @param value * @return */ public Long incr(String key, long value) { return incr(key.getBytes(), value); } private Long incr(byte[] key, long value) { return jtRedis.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { return connection.incrBy(key, value); } }); } /** * 自增 * * @param key * @return */ public Long incr(String key) { return incr(key.getBytes(), 1); } /** * 自减 * * @param key * @return */ public Long decr(String key) { return decr(key.getBytes(), 1); } /** * value减少某个值 * * @param key * @param value * @return */ public Long decr(String key, long value) { return decr(key.getBytes(), value); } private Long decr(byte[] key, long value) { return jtRedis.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { return connection.decrBy(key, value); } }); } /** * 删除key * * @param key * @return */ public Long del(String key) { return del(key.getBytes()); } private Long del(byte[] key) { return jtRedis.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { return connection.del(key); } }); } /** * flushdb:删除db下的所有数据 */ @SuppressWarnings({"rawtypes", "unchecked"}) public void flushDb() { jtRedis.execute(new RedisCallback() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { connection.flushDb(); return 1L; } }); } /** * 设置hash * * @param key * @param field * @param value * @return */ public Boolean hSet(String key, String field, String value) { return jtRedis.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException { return redisConnection.hSet(key.getBytes(), field.getBytes(), value.getBytes()); } }); } /** * 获取hash的属性值 * * @param key * @param field * @return */ public String hGet(String key, String field) { return jtRedis.execute(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection redisConnection) throws DataAccessException { return new String(redisConnection.hGet(key.getBytes(), field.getBytes())); } }); } /** * 批量设置hash * * @param key * @param values */ public void hMSet(String key, Map<String, Object> values) { jtRedis.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection redisConnection) throws DataAccessException { redisConnection.hMSet(key.getBytes(), stringObjectMapToBytes(values)); return null; } }); } /** * 批量获取hash的多个属性 * * @param key * @param fields * @return */ public List<String> hMGet(String key, String... fields) { return jtRedis.execute(new RedisCallback<List<String>>() { @Override public List<String> doInRedis(RedisConnection redisConnection) throws DataAccessException { List<String> listFileds = new ArrayList<>(); for (int i = 0; i < fields.length; i++) { listFileds.add(fields[i]); } List<byte[]> byteFileds = stringListToByte(listFileds); return bytesListToString(redisConnection.hMGet(key.getBytes(), byteFileds.toArray(new byte[byteFileds.size()][byteFileds.size()]))); } }); } /** * 获取hash的所有属性 * * @param key * @return */ public Map<String, String> hGetAll(String key) { return jtRedis.execute(new RedisCallback<Map<String, String>>() { @Override public Map<String, String> doInRedis(RedisConnection redisConnection) throws DataAccessException { return bytesMapToString(redisConnection.hGetAll(key.getBytes())); } }); } /** * 针对hash中某个属性增加指定的值 * * @param key * @param field * @param value * @return */ public Double hIncrBy(String key, String field, double value) { return jtRedis.execute(new RedisCallback<Double>() { @Override public Double doInRedis(RedisConnection redisConnection) throws DataAccessException { return redisConnection.hIncrBy(key.getBytes(), field.getBytes(), value); } }); } /** * hash是存在某属性 * * @param key * @param field * @return */ public Boolean hExists(String key, String field) { return jtRedis.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException { return redisConnection.hExists(key.getBytes(), field.getBytes()); } }); } /** * 删除hash的某属性 * * @param key * @param field * @return */ public Long hDel(String key, String field) { return jtRedis.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection redisConnection) throws DataAccessException { return redisConnection.hDel(key.getBytes(), field.getBytes()); } }); } /** * 向zset中的某个key添加一个属性几分数(可以根据分数排序) * * @param key * @param score * @param field * @return */ public Boolean zAdd(String key, double score, String field) { return jtRedis.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException { return redisConnection.zAdd(key.getBytes(), score, field.getBytes()); } }); } /** * 给zset中的某个key中的某个属性增加指定分数 * * @param key * @param score * @param field * @return */ public Double zIncrBy(String key, double score, String field) { return jtRedis.execute(new RedisCallback<Double>() { @Override public Double doInRedis(RedisConnection redisConnection) throws DataAccessException { return redisConnection.zIncrBy(key.getBytes(), score, field.getBytes()); } }); } /** * 从list左侧插入一个元素 * * @param key * @param values * @return */ public Long lPush(String key, String value) { return jtRedis.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { return connection.lPush(key.getBytes(), value.getBytes()); } }); } /** * 从list左侧插入多个元素 * * @param key * @param values * @return */ public Long lPush(String key, List<String> values) { return jtRedis.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { List<byte[]> bytes = stringListToByte(values); return connection.lPush(key.getBytes(), bytes.toArray(new byte[bytes.size()][bytes.size()])); } }); } /** * 从list的左侧取出一个元素 * * @param key * @return */ public String lPop(String key) { return jtRedis.execute(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { if (connection.lLen(key.getBytes()) > 0) { return new String(connection.lPop(key.getBytes())); } else { return EmptyString; } } }); } /** * 向list的右侧插入一个元素 * * @param key * @param value * @return */ public Long rPush(String key, String value) { return jtRedis.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { return connection.rPush(key.getBytes(), value.getBytes()); } }); } /** * list的rpush,从右侧插入多个元素 * * @param key * @param values * @return */ public Long rPush(String key, List<String> values) { return jtRedis.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { List<byte[]> bytes = stringListToByte(values); return connection.rPush(key.getBytes(), bytes.toArray(new byte[bytes.size()][bytes.size()])); } }); } /** * 从list的右侧取出一个元素 * * @param key * @return */ public String rPop(String key) { return jtRedis.execute(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { if (connection.lLen(key.getBytes()) > 0) { return new String(connection.rPop(key.getBytes())); } else { return EmptyString; } } }); } /** * 给set中添加元素 * * @param key * @param values * @return */ public Long sadd(String key, List<String> values) { return jtRedis.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { List<byte[]> bytes = stringListToByte(values); return connection.sAdd(key.getBytes(), bytes.toArray(new byte[bytes.size()][bytes.size()])); } }); } /** * 获取set中的所有元素 * * @param key * @return */ public List<String> smembers(String key) { return jtRedis.execute(new RedisCallback<List<String>>() { @Override public List<String> doInRedis(RedisConnection connection) throws DataAccessException { return bytesListToString(connection.sMembers(key.getBytes())); } }); } /** * set中是否包含某元素 * * @param key * @param value * @return */ public Boolean sIsMember(String key, String value) { return jtRedis.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.sIsMember(key.getBytes(), value.getBytes()); } }); } private byte[][] change(List<byte[]> values) { byte[][] result = {}; return result; } private List<byte[]> stringListToByte(List<String> values) { return values .stream() .map(p -> p.getBytes()) .collect( Collectors.toList() ); } private List<String> bytesListToString(Collection<byte[]> values) { return values .stream() .map(p -> new String(p)) .collect( Collectors.toList() ); } private Map<String, String> bytesMapToString(Map<byte[], byte[]> values) { Map<String, String> result = new HashMap<>(); values.forEach((k, v) -> result.put(new String(k), new String(v))); return result; } private Map<byte[], byte[]> stringObjectMapToBytes(Map<String, Object> values) { Map<byte[], byte[]> result = new HashMap<>(); values.forEach((k, v) -> result.put(k.getBytes(), String.valueOf(v).getBytes())); return result; } /** * 正则表达式获取值 * * @param pattern * @return */ public Set<String> keys(String pattern) { return jtRedis.keys(pattern); } @Override public void afterPropertiesSet() throws Exception { RedisSerializer<String> stringSerializer = new StringRedisSerializer(); jtRedis.setKeySerializer(stringSerializer); jtRedis.setValueSerializer(stringSerializer); jtRedis.setHashKeySerializer(stringSerializer); jtRedis.setHashValueSerializer(stringSerializer); }}配置文件配置文件主要有两个,一个是spirng相关配置的配置文件applicationContext-redis.xml,还有一个是redis相关配置文件redis-config.propertiesapplicationContext-redis.xml配置文件内容如下: ...

July 7, 2019 · 7 min · jiezi

springcloudfeign实现服务发现

springcloud-feign实现服务发现 上一篇介绍了nacos实现配置和注册中心,在微服务中只有配置和注册中心远远不够,还需要有服务发现。本文重点介绍一下基于feign实现服务发现。 简单介绍 springcloud使用服务发现进行服务间调用。对外使用网关gateway屏蔽分流转发接口。服务发现客户端到注册中心拉取服务列表实现客户端负载均衡。客户端实现负载均衡主要有两种方式。一种是ribbon另一种是基于ribbon封装的feign。这是feign官方的解释我总结了一下主要有一下几个优点: (1)可插拔的注解支持,包括Feign注解和JAX-RS注解; (2)支持可插拔的HTTP编码器和解码器; (3)支持Hystrix和它的Fallback; (4)支持Ribbon的负载均衡; (5)支持HTTP请求和相应的压缩。 微服务调用常用概念 (1)服务降级:     当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略测不处理或换种简单的方式处理,从而释放资源以保证核心交易正常运作成高效运作。 (2)断路:     熔断这一概念来自于电子工程中的断路器(circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而相应变慢或者失败,上游为了保护系统整体的可用性,可以暂时切断对下游服务的调用。简单理解就是降级是针对业务,断路是上下游故障。 (3)幂等性:     就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。 使用方式 下面我们开始feign的使用: (1)添加springcloudalibaba和openfeign的依赖      <dependency> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\<groupId>org.springframework.cloud\</groupId> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\<artifactId>spring-cloud-starter-openfeign\</artifactId> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\</dependency> &nbsp;&nbsp;&nbsp;&nbsp; \<dependency> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\<groupId>org.springframework.cloud\</groupId> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\<artifactId>spring-cloud-starter-alibaba-nacos-discovery\</artifactId> &nbsp;&nbsp;&nbsp;&nbsp; \</dependency>(2)添加注解 @EnableDiscoveryClient开启服务发现,注解 @EnableFeignClients支持feign客户端。 用@FeignClient("serviceName")注解在对外的接口上,这样接口就支持调用服务,因此支持热插拔。serviceName使用注册中心被调用的服务名字。@RequestMapping注解的方法是服务提供方的接口方法,看起来是不是和springmvc使用方法很像,没错因为这是feign对mvc支持的一种契约。feign默认是@RequestLine。它可以支持各种契约。同样子,他可以使用默认的HttpUrlConnection也可以使用httpclient或者okhttp3. (3)修改application.peoperties配置文件    #服务名字     ----- spring.application.name=feign-service     #服务无端口0会随机选择一个     ----- server.port=0     #服务集群名字     ---- spring.cloud.nacos.discovery.cluster-name=consumer-service    #nacos注册中心地址     ----- spring.cloud.nacos.discovery.server-addr=nacos.blog.ailijie.top       ok,完成。下面是feign的高级使用 feign高级使用 (1)feign日志配置 创建feign日志级别,还有feign的重试机制,这样feign可以使用重试机制同时把每一次的请求用日志记录下来。 (2)feign的断路 在feignclient注解中配置fallbackFactory工厂,进行断路解决。使用factory的好处是可以处理异常信息。还可以使用fallback处理。如果使用fallback,fallbackFactory将不会生效。看一下,Factory需要返回一个IProviderService的实现类,在create方法里可以处理异常。这样在服务端发生异常或者下线后,将会走断路方法。 (2)feign的高级配置     #开启okhttp     ----- feign.okhttp.enabled=true     #开启hystrix断路,不开启feign的降级不生效     ----- feign.hystrix.enabled=true     #hystrix断路超时时间     ----- hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 2000 ...

July 6, 2019 · 1 min · jiezi

JAVA代理模式的理解和应用

代理模式:代理模式通俗一点的解释就是在操作一个对象和对象中的方法时,不是直接操作这个对象,还是通过一个代理对象来操作这个实际的目标对象。应用场景一般是需要在执行某个已经写好的方法前后再添加一段逻辑,比如执行方法前打印日志,或者在执行方法之前和之后打时间戳来计算方法的执行时间,诸如此类的。当然这些操作可以在写方法的时候就去写好,但是这样的话效率非常低,重复代码复制了一遍又一遍,如果要统一改点什么数量多起来的话基本上是个不可能完成的任务,而代理模式就是专门解决这种问题的。 静态代理:静态代理其实代理类Proxy中定义了一个方法,这个方法来调用被代理类Target中的方法,这样我们就可以在执行这个方法的前后增加逻辑了,代理类和被代理类是组合关系。这里实现一个接口是为了有更好的扩展性,代理类Proxy中声明接受这个接口类型,那么被代理类只要实现了这个接口就可以使用代理类Proxy进行代理操作了,这里是JAVA的多态特性。 被代理的目标的实现接口 public interface TargetImpl { void doSomething();}被代理的目标类 public class Target implements TargetImpl { public void doSomething(){ System.out.println("target do something!!!"); }}代理类 public class Proxy implements TargetImpl { private TargetImpl baseObject; public Proxy(TargetImpl baseObject) { this.baseObject = baseObject; } public void doSomething(){ System.out.println("before method"); baseObject.doSomething(); System.out.println("after method"); }}测试类: public class TestMain { public static void main(String[] args){ staticProxy(); } public static void staticProxy(){ Target target = new Target(); Proxy proxy = new Proxy(target); proxy.doSomething(); }}动态代理:上面静态代理类已经帮我们解决了很多冗余代码,但是存在的问题还是很多,比如一个代理类只能对一种类型目标类有效,换一种类型要新增一个代理类,而且如果有很多地方使用目标类就得在每个地方调用代理类,很麻烦,而动态代理则可以解决这种问题。 ...

July 6, 2019 · 2 min · jiezi

springcloud在线扩容

准备工作Eureka-client一个,Eureka-server一个,config-server一个 1.1 pom的准备 config-server <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>eureka-client和Eureka-server都添加 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>1.2 配置文件eureka-client server: port: 8081spring: application: name: eureka-client1eureka: instance: hostname: localhost prefer-ip-address: true client: service-url: default-zone: http://localhost:8761/eureka/eureka-server server: port: 8761eureka: instance: hostname: localhost prefer-ip-address: true client: service-url: default-zone: http://${eureka.instance.hostname}:${server.port}/eureka/ register-with-eureka: false fetch-registry: false server: wait-time-in-ms-when-sync-empty: 0 enable-self-preservation: falseconfig-server目录结构 application.yml server: port: 8888spring: application: name: config-server profiles: active: nativeeureka-client.yml server: port: 8081spring: application: name: eureka-client1eureka: client: service-url: default-zone: http://localhost:8761/eureka/ eureka-server-peer1.yml ...

July 6, 2019 · 1 min · jiezi

这可能是把Spring事务讲得最清楚的一篇有齐代码从此妈妈用担心我的面试

什么是事务?-事务是指为了实现特定的功能,对数据库所进行一组完整的逻辑操作,这组操作要么全部成功,要么全部失败。 什么是事务的四大特性?原子性 原子性这组操作是不可分割的最小单位,要么全部成功,要么全部失败。**隔离性 当一个事务进行操作的时候,其他事务不可以进行操作,这就是事务的隔离性。**一致性 事务回滚之后,数据和操作前保持一致。**持久性 一个事务一旦被提交,它对数据库的改变就是永久的。**针对事务,spring为我们提供了那些接口?三大接口,分别是: PlatformTransactionManager 事务管理器TransactionDefinition 事务属性的定义TransactionStatus 事务运行时状态具体执行的时候事务管理器(PlatformTransactionManager)会根据TransactionDefinition 给出的相关属性(隔离级别、传播行为、是否只读、超时时间)执行事务,执行事务的相关信息保存在TransactionStatus 里面。 什么是Spring事务的隔离级别?什么是Spring事务的传播机制?实现Spring事务有什么方式参考资料视频:https://www.imooc.com/learn/478

July 5, 2019 · 1 min · jiezi

多层秘钥体系在POS刷卡支付领域中的应用

前言POS刷卡交易属于传统的支付领域。在移动支付微信/支付宝的冲击下,刷卡支付的市场份额在逐渐萎缩,毕竟手机扫一扫比拿个银行卡刷卡输密方便多了。虽然移动支付大行其道,但是刷卡支付仍然有其存在的必要性,至少在大额支付领域仍然需要依赖刷卡支付。在使用磁条卡或者芯片卡进行刷卡交易时,必然涉及到敏感信息的传输,必然涉及到加密,而加密必然涉及到秘钥。如何管理秘钥的使用范围以及保障秘钥的传输安全,是加密过程中的重中之重。 本文来源于我的博客网站http://51think.net 一、关注点我们可以把支付流程分成两三个部分,如下图,POS设备进行刷卡,将刷卡信息上送给支付平台,支付平台再将交易信息上送给银行。 两个信息上送阶段均涉及到信息的加密传输,加密的原理是一致的,加密中所使用的秘钥结构需要符合银联的安全规范要求。本文以刷卡信息上送到支付平台为例,介绍多层秘钥体系的应用。 先了解几个名词术语: 1、加密机 加密机是一个硬件设备,具备加密、加密、秘钥管理等功能,它将加解密的操作全部封装在硬件里,具备很强的抗攻击能力。 2、PIN信息 持卡人的个人标识码,即银行卡密码。PIN信息在整个支付链路中都是以密文的方式存在,在POS设备中,PIN信息手动输入后直接到硬件层进行加密,黑客无法劫持。 3、LMK/ZMK/TMK LMK是加密机最顶层秘钥,保密级别最高,称之为本地主密钥。LMK一般是由不同的人保管着不同的秘钥分量,然后不同的保管员在加密机分别输入自己保管的那一部分并合成一个LMK,没有人能够知道LMK的全部信息。ZMK(Zone Master Key)可以理解为区域主密钥,TMK(Terminal Master Key)可以理解为终端收单主密钥。LMK/ZMK/TMK均属于保护秘钥,保护秘钥是用来加密保护工作秘钥的,分层管理,不涉及到加密报文。 4、收单主密钥 收单主密钥在本例中称之为TMK,用来加密保护工作秘钥,签到时由服务器端ZMK加密产生。5、工作密钥(workKey) 工作秘密是真正用来加密敏感信息的秘钥,包含pin秘钥、mac秘钥以及data秘钥。比如pin秘钥用来加密银行卡密码信息,mac秘钥用来保障报文的完整性,data秘钥用来加密银行卡里的磁道信息。 6、设备主秘钥(devKey) 设备主秘钥是集成在POS硬件设备里的,作用和加密机的LMK类似。POS设备在出厂之前,需要从支付机构获取到设备主秘钥明文灌装到设备里。如果受到暴力攻击,设备主秘钥可以自动销毁。二、多层秘钥体系之所以称之为多层秘钥体系,是因为在这种模式下,可以根据安全复杂度要求,设置多层嵌套,层层保护,可以是三层,也可以是四层。下图体现的是一套四层的秘钥体系,加密机可以使用LMK加密产生ZMK,使用ZMK加密产生TMK,使用TMK可以加密产生pinKey、dataKey、macKey 。 由此可以看出,LMK需要绝对安全,否则所有的秘钥将会泄密。可否简化成三层?我们尝试把ZMK这一层删除: 由LMK直接加密产生收单主密钥TMK,只要LMK绝对安全,这样分层也是可以的。但是这种情况下,收单秘钥过于依赖LMK,不方便大规模的秘钥管理。如果加入了ZMK这一层,我们可以在逻辑层面上,将每个POS厂商分配一个ZMK,即使某个厂商的ZMK被泄露了,其他厂商也不会受到影响。如下图: 三、秘钥传输过程从上文得知,TMK作为收单主密钥是用来加密保护工作秘钥的。那我们如何将TMK安全的送达到POS终端?这里必然涉及到网络传输,而网络传输必然会面临着报文被劫持的风险,所以TMK一定是密文的形式返回给POS终端。由此得知,我们需要一个新的秘钥用来加密TMK且这个新的秘钥也存在于POS终端,这个新的秘钥我们称之为设备秘钥。在POS设备出厂前,POS厂商使用API接口的方式从支付平台得到设备秘钥,然后灌装到设备硬件里。有了设备秘钥,后面的签到流程就可以解密相关秘钥了。如下图: 在POS设备中,维护这如下的秘钥关系: 何为签到?签到是银联的POS交易规范流程中的术语,在本例中,我们使用签到流程从支付平台获取收单主密钥和工作秘钥。支付平台控制这两个秘钥的生命周期,如果过期,则会产生新的收单主密钥和工作秘钥返回给POS终端。原则上工作秘钥的过期时间为一天,即使有黑客花费高昂代价解密了工作秘钥,第二天秘钥就会作废。

July 5, 2019 · 1 min · jiezi

Spring-Bean-生命周期之我从哪里来-懂得这个很重要

Spring bean 的生命周期很容易理解。实例化 bean 时,可能需要执行一些初始化以使其进入可用 (Ready for Use)状态。类似地,当不再需要 bean 并将其从容器中移除时,可能需要进行一些清理,这就是它的生命周期 上一篇文章 面试还不知道BeanFactory和ApplicationContext的区别? 中说明了接口 Beanfactory 和 Applicationcontext 可以通过 T getBean(String name, Class<T> requiredType) 方法从 Spring 容器中获取bean,区别是,前者是懒加载形式,后者是预加载的形式。那么问题来了: 这些 Spring Beans 是怎么生成出来的呢?在正式回答这个问题之前,先解答一些有关 Java Bean, Spring Bean 和 Spring IoC 容器这些概念性的疑惑,我希望通过下面这个例子形象说明这些问题: 小学生 (Java Bean)通过提交资料申请(元数据配置)加入了少先队(Spring Ioc 容器),学习了一些精神与规定之后,变成了少先队员(Spring Bean)从这里可以看出,Java Bean 和 Spring Bean 都是具有特定功能的对象,小学生还是那个小学生,只不过加入了少先队之后有了新的身份,新的身份要按照组织 (Spring Ioc)的规定履行特定义务 来看下图加深一下了解 首先要有容器,实例化 Spring Ioc 容器是非常简单的,接口 org.springframework.context.ApplicationContext 表示Spring IoC容器,负责实例化,配置和组装上述 bean。 容器通过读取配置元数据获取有关要实例化,配置和组装的对象的指令。 配置元数据通常以XML,Java 注解或代码的形式表示。 它允许你自己表达组成应用程序的对象以及这些对象之间丰富的相互依赖性,比如这样: ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"spring.xml", "spring1.xml"});有了容器,我们需要做哪些处理,使其内部对象变为 Ready for Use 的状态? 我们需要通过 Spring 容器实例化它们,Spring 为我们提供了三种方式:三种初始化方式InitializingBeanSpring 为我们提供了 InitializingBean 接口 ...

July 5, 2019 · 2 min · jiezi

Springcloudnacos实现配置和注册中心

Springcloud-nacos实现配置和注册中心最近,阿里开源的nacos比较火,可以和springcloud和dubbo共用,对dubbo升级到springcloud非常的方便。这里学习一下他的配置和注册中心。我主要记录一下它的使用方式和踩得坑。 nacos简单介绍Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。 Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。 以上是从nacos官网摘抄下来的,总结一下nacos有以下有点几个优点: (1)它支持配置中心管理(含图形化界面) (2)部署交付简单 (3)包含注册中心,不用独自搭建配置和注册中心。 (4)完美集成spring cloud和dubbo拓展性好 nacos使用方式,具体参考官方配置(1)从 Github 上下载源码方式 ------ git clone https://github.com/alibaba/na... ------ cd nacos/ ------ mvn -Prelease-nacos clean install -U ------ ls -al distribution/target/ ------ cd distribution/target/nacos-server-$version/nacos/bin (2)解压,启动nacos ------ unzip nacos-server-$version.zip 或者 tar -xvf nacos-server-$version.tar.gz ------ cd nacos/bin ------ sh startup.sh -m standalone 完成,集群高可用请自行研究,本文不过多赘述。 nacos实现配置中心springcloud使用nacos作为配置中心特别简单。只需要添加依赖,使用bootstrap配置注册中心地址即可。 (1)添加nacos的配置中心依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> (2)配置bootstrap.properties文件 ---- #配置服务的名字 ---- spring.application.name=provider-service ---- #nacos配置中心的地址 ---- spring.cloud.nacos.config.server-addr=nacos.ailijie.top ---- #nacos配置的编码方式 ---- spring.cloud.nacos.config.encode=utf-8 ...

July 5, 2019 · 1 min · jiezi

SpringBoot系列教程JPA之delete使用姿势详解

原文: 190702-SpringBoot系列教程JPA之delete使用姿势详解 常见db中的四个操作curd,前面的几篇博文分别介绍了insert,update,接下来我们看下delete的使用姿势,通过JPA可以怎样删除数据 一般来讲是不建议物理删除(直接从表中删除记录)数据的,在如今数据就是钱的时代,更常见的做法是在表中添加一个表示状态的字段,然后通过修改这个字段来表示记录是否有效,从而实现逻辑删除;这么做的原因如下 物理删除,如果出问题恢复比较麻烦无法保证代码一定准确,在出问题的时候,删错了数据,那就gg了删除数据,会导致重建索引Innodb数据库对于已经删除的数据只是标记为删除,并不真正释放所占用的磁盘空间,这就导致InnoDB数据库文件不断增长,也会导致表碎片逻辑删除,保留数据,方便后续针对数据的挖掘或者分析<!-- more --> I. 环境准备在开始之前,当然得先准备好基础环境,如安装测试使用mysql,创建SpringBoot项目工程,设置好配置信息等,关于搭建项目的详情可以参考前一篇文章 190612-SpringBoot系列教程JPA之基础环境搭建下面简单的看一下演示添加记录的过程中,需要的配置 1. 表准备沿用前一篇的表,结构如下 CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `name` (`name`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;2. 项目配置配置信息,与之前有一点点区别,我们新增了更详细的日志打印;本篇主要目标集中在添加记录的使用姿势,对于配置说明,后面单独进行说明 ...

July 4, 2019 · 3 min · jiezi

积压修饰最佳实践-Best-Practices-for-Backlog-Grooming

如上所述,积压修饰正在进行中。通常,这些更新是在与相关利益相关方会面时进行的。产品所有者将主持会议并让每个人查看现有的用户故事,以决定哪些可以从积压列表中删除。此时也可以添加用户故事,或者拆分用户故事以使其更易于管理。以下是一些提高进度效率的技巧。 先让较高级的项目深入敏捷倡导者Roman Pichler提出了首字母缩略词DEEP,它代表了详细的,适当的,估计的,紧急的和优先的。这些描述了管理良好的积压。 “适当详细”意味着优先级较高的项目应具有比优先级较低的项目更多的细节。应该“估计”积压项目以了解实施的成本。“紧急”表示积压是动态的,总是从想法转向完成工作。“优先排序”说明哪个项目最重要,必须在其他项目之前解决。它们位于列表的顶部,最后移动到最低优先级。 有更好的会议积压修饰 (backlog grooming) 的工作通常在会议中完成,因此更有效地开展这些会议可以提高订单的效率。因此,您并不总是要邀请所有人。唯一重要的人是产品所有者。之后,只邀请那些相关的人。如果需要来自会议中不需要的其他人的输入,则在会议之前获取该信息。 和任何会议一样,准备好了。制定一份明确规定目标的议程,并确保所有人都了解这些目标是什么以及预期如何为实现目标做出贡献。最后,保持会议简短。会议往往会很长,但最好是保持简短。知道必须做什么,并且可能每两周才会遇到一两个小时。 牢记顾客积压修饰必须遵循lodestar,指南始终是客户。将它们视为优先处理积压工作。客户可以像其他所有衡量标准一样。该产品正在为客户生产,因此客户是始终保持视线的目的地。 识别依赖关系有时会有用户故事或任务在另一个完成之前无法启动。如果未识别,这些依赖关系可能会阻止团队成员,延迟进度并阻碍团队生产力。因此,请确保在积压修饰时识别任何依赖关系。 疏理有太约两个Sprints的用户故事的量 (而不是一个Sprint)完成备份修饰会话后,应该有两个可用于团队准备工作的用户故事冲刺。通过这种方式,他们有足够的工作让他们保持参与,直到下一次积压工作,并且如果优先级发生变化團隊也可以继续补上其他工作。 倾听这个建议适用于无数的个人和专业情况,无论如何重复,它总是需要进一步关注。虽然具有既定目标的议程对于智能积压培训至关重要,但这并不意味着它是一成不变的。产品所有者必须保持开放的心态,倾听团队的意见,并根据需要进行调整。 专业在积压的修饰会议中会有不同的意见,但团队中的每个人都在努力创造最好的产品。他们可能会在如何做到这一点上发生冲突,但他们的动机是激情和经验。产品所有者或经理必须牢记这一点并对所有人采取友好行动。让每个人都听到并受到尊重,但让团队保持专注。

July 4, 2019 · 1 min · jiezi

Scrum會議太多了-你的團隊在Scrum活動中使用了多少時间

Scrum事件 - 會議太多了,太耗時了!!!我们究竟化了多少间?幾乎每一個我將Scrum引入新公司的團隊都曾在某些方面抱怨Scrum會議的持續時間。我得到的評論如下: “Scrum會議太多了”“什麼?兩個星期衝刺的四小時計劃會議!“”我們在這裡舉行了很多會議,現在你們正在通過更多的會議來殺死我們“”我不能讓我的團隊花費這麼多時間參加會議“”我太忙了“以下是Scrum中的事件以及每個事件的小時數: 新手團隊可能會看到這些數字在每個事件中都有大量的時間,但是如果您遵循檢查原則並實際深入了解時間; 你可能會對一些數字感到有些驚訝。 出於數學目的,我將假設平均上午9點到下午5點的工作,工作時間為8小時。我還將承擔週一至週五的工作,因為這在我與之合作的所有團隊中都很常見。 以下是Sprint中每個事件花費的時間細分: 2%的回顧團隊應該花時間檢查他們的流程和實踐,並尋求更好,更快地做事的機會。沒有這種檢查和精神,適應就完全喪失,敏捷的本質不存在。 2.5%的評論和反饋在衝刺結束時,團隊與利益相關者交談並尋求他們對剛剛完成的工作的反饋非常重要。這種風險管理策略通過允許團隊展示他們建立的內容以及利益相關者同意或糾正它來幫助儘早糾正錯位目標。不花時間做這些反饋只會導致錯誤的產品交付給利益相關者,而不是他們的期望。這通過定期反饋支持對增量和迭代開發的整體信念。 接下來是審查即將開展的工作,並允許產品負責人向團隊和利益相關者展示工作管道的健康狀況。它有助於透明地溝通產品,項目和即將開展的工作。 2.5%協調每日Standup每天,團隊只需要一個15分鐘的活動來計劃誰在接下來的24小時內做什麼,直到下一個每日Scrum。 5%規劃和設計最大的Scrum活動是規劃下一個sprint,並就如何處理工作進行高級設計。傳統開發團隊在規劃和設計方面花費了更多精力,而Scrum團隊完全減少了這一點。然而,這一事件是最具爭議性的事件,可能團隊認為這5%時间仍然很長。我個人不認為要求任何團隊有5%的時間來計劃和設計即將開展的工作是一個很大的問題。當然,專業人士應該能夠認識到計劃的重要性。 88%工作時间明显我们还有大比率時間去工作。這是一些更多的事實如下: 會議每天1小時(平均) 如果你把它看作平均每天只有一個小時的會議,是多是少见仁见智吧。 時間盒 時間框是事件的最長建议時間,這意味著如果您可以更快地地它们完成,只要能同時提供相同的質量; 那麼請快點完成它。 如果你的會議次數多於Scrum會議,那麼你做錯了,組織的功能失調需要解決。示例計算

July 4, 2019 · 1 min · jiezi

用户故事地图-User-Story-Map

User Story Mapping 是Jeff Patton倡导的一项技术。它为我们提供了一种将整个产品或服务设想为用户完成的一系列任务的方法。 从纯粹的实际角度来说,它涉及构建一个用户故事网格,这些故事在标题下排列,代表用户在产品中的体验。这可以通过团队成员之间的一系列对话迭代完成。因此,第一次尝试可能看起来像这样,用户故事按其各自的功能分组(有些可能称这些顶级功能'Epics')。 在这里,我们将产品的高级功能(骨干,如果您愿意)分解为组件用户故事。很容易看出每个用户故事属于哪个功能,因此每个用户故事都在整个产品的上下文中呈现,而不仅仅是列表中的项目。 虽然这种方法有助于组织我们的想法 - 它已经比简单的故事列表更具信息性 - 它实际上还没有构成故事地图,因为它没有考虑用户旅程的流程。 开发故事地图让我们通过想象一个简单的电子商务网站让我们的例子更加具体,产品愿景板提到了三个特征: 产品页面产品搜索查看最初的故事地图可能如下所示: 我们有“产品页面”功能,其中包含与下面列出的功能相关的用户故事,同样适用于“产品搜索”和“结帐”功能。但是这些故事还没有特别好地发展,并且没有迹象表明每个故事的重要性。 例如,用户需要在订购之前阅读产品说明,但这是在他们阅读评论之前或之后发生的吗?哪个为用户提供更多价值? 在进行了更多的研究并收集了来自利益相关者的更多意见之后,另一次迭代可能看起来像这样。 请注意,我们通过将其中的一些细分为更小的部分来改进我们的用户故事,我们引入了一个新的维度,故事按照用户旅程中的位置排列,我们已经开始安排最高的我们地图顶部附近的优先故事 在这个方向上进一步发展,很容易看出我们最终是如何得出一张地图,指出在前几个版本中需要包含哪些故事。 建立故事地图 (Visual Paradigm)故事地图是一个用于需求收集的4级层次结构。故事地图从不同来源(即积压)收集的用户特征集合开始,这些用户特征将通过执行某些任务作为活动来实现。这些任务可以转换为史诗,然后转换为软件开发的用户故事。 故事地图结构:用于实现目标的用户功能(待办事项记录)>活动>任务>史诗>故事 规划故事地图的步骤为了促进敏捷开发,Story Map可以接收从不同来源识别的用户功能。如上所述,它可能是来自EA合同的要求,来自项目管理计划的工作包或特殊分析(例如 - 是和将来的分析),使用图中的用例与敏捷软件开发集成等等。 假设我们已经从多个不同的来源累积了故事地图积压中的用户特征列表。通过执行某些任务,将实现用户功能作为活动。每个任务都可以进一步分解为几个史诗(更大的用户故事)。每个史诗都包含一个用户故事列表,这些用户故事被分解为适合适合sprint迭代的大小。以下是规划故事地图所涉及的步骤: 将用户要素从左向右拖动到地图的顶行。地图顶行中的每个功能都是呼叫用户活动。 创建完成活动所需的许多步骤,称为用户任务。 这些用户任务中的每一个都可以分解为多个史诗。 在史诗下,可以定义用户故事列表,其大小适合放入sprint。 请注意:我们可以考虑从左到右安排实施的优先级,从顶部到底部安排用户故事。 相关链接敏捷用户故事映射工具有效的用户故事工具

July 3, 2019 · 1 min · jiezi

Scrum的五个价值观勇气-承诺-尊重-专注和开放

Scrum的五个价值观:勇气,承诺,尊重,专注和开放。了解自组织首先要了解这些价值观是如何融入您的项目中的实际事物: 每个人都致力于项目的目标当团队有权做出决策以实现这些目标时,可以实现这种承诺水平,每个人都对项目的计划和执行方式有发言权。第3章中的电子书阅读器团队最初被要求建立一个互联网店面。为了使产品成功,他们不得不忽略这一要求,以便提供更有价值的项目。这是唯一可能的,因为他们被允许做出这个决定 只有团队,Scrum Master和产品负责人。他们不需要通过官僚机构来完成任务。 团队成员互相尊重当团队成员相互尊重时,他们能够相互信任,以完成他们所做的工作。但对于程序员和其他技术人员而言,这种尊重并不总是那么容易。许多程序员,尤其是高技能程序员,往往纯粹基于技术能力。这可能是有效采用Scrum的障碍。如果程序员不尊重产品负责人,他在谈论项目目标时不会听取产品负责人的意见。 一个优秀的Scrum Master会找到方法来增加团队成员之间的相互尊重。例如,他可以向程序员展示产品负责人对用户的思考方式以及公司需求有深刻的理解。随着程序员开始了解该知识如何对项目成功有用,他们开始重视并尊重产品负责人的意见。 每个人都专注于工作当Scrum团队成员正在进行sprint工作时,这是他在sprint 期间唯一的工作。 他可以自由地完成完成sprint积压所需的任何工作,并处理sprint期间对该积压所做的任何更改。当每个团队成员专注于sprint目标并且可以自由地完成满足这些目标所需的任何工作时,整个团队能够组织自己并在需要变更时轻松地重定向。 另一方面,分心的团队是一个效率较低的团队。在现代工作场所中有一个神话,人们 - 特别是程序员 - 在多任务处理时工作得最好,因为他们可以在第一个项目被阻止时转移到第二个项目的任务。这不是人们在现实生活中的工作方式!在项目之间切换,甚至在同一项目中的不相关任务之间切换会增加意外的延迟和工作量,因为上下文切换需要大量的认知开销。放下你目前的工作需要花费大量的心理努力,然后从另一个项目的中心开始。你最终不得不回顾你上次做的事情,只是为了提醒自己你试图解决的问题。告诉团队成员切换到另一个项目上的任务不仅需要执行新任务所需的时间, 不买吗?试试这个思想实验。假设你有两个为期一周的任务。并假装通过物理定律的一些惊人的弯曲,多任务处理不会增加任何开销。您可以无缝地切换这些任务,而无需增加一秒的开销或延迟,因此这两项任务将花费两周的时间。即使在这些完美(和不可能)的情况下,多任务也没有意义。如果你没有多任务,你将在第一周结束时完成第一个任务,第二个任务在第二周结束时完成。但是,如果你执行多任务,那么你必须在第一周花费至少一些时间完成第二项任务,所以直到下周某个时候它才会完成。这就是为什么即使人类擅长多任务处理(我们也不是),这样做也没有意义。 多任务处理不是团队成员分心的唯一方式。他们经常被要求参加无用的会议和无关的委员会,开展与项目无关的活动,并为其他项目提供支持工作。一个好的Scrum团队被允许忽视这些分心而不冒他们的职业或晋升风险。24(现在必须完成的当前项目的支持工作可以添加到sprint backlog中 - 但是只有在取出其他东西才能使其适合时间框时。) 团队重视开放性当您在Scrum团队工作时,团队中的其他人应该始终了解您正在进行的工作以及如何将项目推向其当前目标。 这就是为什么基本Scrum模式中的实践旨在鼓励团队成员之间的开放性。例如,任务板允许每个人看到每个团队成员正在完成的所有工作,以及剩下多少工作要做。Burndown图表让每个人都可以自己测量sprint实现sprint目标的速度。每日Scrum在有效完成时,是一项几乎纯粹的开放式练习,因为每个人都分享他或她的任务,挑战和整个团队的进步。所有这些都可以帮助团队营造相互支持和鼓励的氛围。 为Scrum团队创造一种开放文化听起来很棒,也很积极,而且确实如此。但它通常是Scrum团队最困难的事情之一,因为它是Scrum价值观与公司先前存在的文化冲突的最常见方式之一。 许多公司都有一种不鼓励透明度的文化,并用一种依赖于不透明的严格等级来取而代之。建立这样一种文化的管理者可以通过多种方式从中受益。在一个不透明的组织中,告诉团队实现一个不切实际的目标(“我不关心你是怎么做的,只是完成它!”)会更容易,迫使团队加班加点来实现它。当球队不可避免地无法实现这一目标时,它会让经理对CYA有可能的拒绝(“这不是我的错,他们搞砸了!”)。 这就是为什么开放性和自组织通常是Scrum采用的不可触及的“第三轨”。这是使Scrum采用正确的一个核心概念,但它也要求公司以不同于过去的方式对待团队。接触发展的细节否定了不透明的经理人对CYA的掩护。许多初出茅庐的Scrum团队发现,一旦不透明的管理人员开始看到香肠是如何制作的,他们的采用工作就会受到影响。 开放性威胁着卡通,尖尖,不透明的经理。但实际上,即使是一支优秀的团队也很难采用。从开发人员的角度考虑开放性,开发人员被视为代码的一部分的专家,或者是计划的“守护者”的项目经理,或者是许多用户的唯一联系人的产品负责人关于软件内容的主要决策者。这些团队成员中的每一个都有权将这些事物视为对项目的贡献。将这些内容打开给团队是非常困难的,并且鼓励其他团队成员在未获得许可的情况下共享所有权并进行更改。 这是个人团队成员抵制开放的一种非常自然的方式。但是,当他们通过这个并分享所有权 - 包括在出现问题时的责任 - 与整个团队的所有这些事情,每个人都会受益,因为这是相互信任并快速提供更有价值的软件的唯一方式。 团队成员有勇气站出来参与该项目当你选择开放而不是不透明时,你会让团队变得更强大,而不是以牺牲团队为代价来强化自己。这样做需要勇气,但是当你这样做时,你最终会得到更好的产品和更好的工作环境。 Scrum团队有勇气通过有益于项目的价值观和原则来生活。要想避免价值与Scrum和敏捷价值观发生冲突的公司不断回击,需要勇气。这需要每个团队成员,特别是Scrum Master保持警惕。但它也要求每个人都愿意相信提供有价值的软件将有助于他或她克服对这些价值观的抵制。这也需要勇气,尤其是在与老板坐下来进行审查的时候。要对自己说,“帮助这个团队生产有价值的软件对我来说比对自己的个人贡献吹嘘权利更重要。” 那么你将如何在团队中建立勇气?您如何让团队相信自己,并相信Scrum不仅可以帮助他们构建更有价值的软件,而且他们的公司将看到他们新方法的价值? Agile & Scrum Basis Comprehensive Scrum GuideWhat are Scrum's Three Pillars?What is Agile Software Development?Scrum in 3 MinutesWhat are the 5 Scrum Values?What is the Evolution of Scrum?Classical Project Management vs Agile Project ManagementWhy is Scrum Difficult to Master?What is Velocity in Scrum?What is Agile? What is Scrum?What are the Three Amigos Development Strategy in Agile?Empirical Process Control vs Defined Process ControlHow to Maintain Transparency in Scrum?Scrum vs Waterfall vs Agile vs Lean vs KanbanWhat is 3355 in Scrum Framework?Why Scrum? How Does Scrum Overcome 8 Pain Points We Always face?The Best Free and Commercial Agile Tools - Every Scrum Team Needs!What are the 8 Wastes in Lean?Extreme Programming (XP) vs ScrumWhat is Timeboxing in Scrum?Agile Myth: Documentation and Planning not Needed?

July 3, 2019 · 1 min · jiezi

GETPOST与后端接口详记

前言HTTP通信的7种方式在HTTP通信中主要分为GET和POST。如PUT,DELETE是类POST的传输方式,与POST没有实质区别。OPTION是查看服务器支持的请求方法。HEAD是测试服务器的该资源情况,不返回实体的主体部分。TRACE请求可以获取回服务器接收到的该请求的原始报文,从而判断路径中的代理和防火墙是否对该条请求进行修改。 HTTP请求报文发送格式不因请求方式不同而改变HTTP报文格式如下<请求方法> <请求路径> <协议版本><请求头> <主体body> 无论用任何请求方法,都可以发送这样的请求报文,报文结构是HTTP的协议规范,请求方法只是告诉服务器如何来看待这条请求。因为在有些文章中会提到GET请求不能传body数据,而真实的情况是有些服务端框架接收到GET请求后会自动将GET请求的body部分丢弃,所以大家要注意。所以为了规范,大家在使用GET请求时还是不要将请求数据放在body中。何时用GET请求为了规范,使用GET请求时就不要在主体body中放数据了,避免不必要的错误。所以请求中的数据是放在URL上的。 浏览器网址栏和页面跳转。为了获取信息且不需要传大量条件信息的接口。例如用GET做登录请求 GET /online/test/?name=haha&amp; password=miaomiao HTTP/1.1Host: 127.0.0.1:8080Content-Type: application/x-www-form-urlencodedcache-control: no-cachePostman-Token: 14a1347a-c540-48f0-9d49-6299f86c3a73何时用POST请求post请求可以在body中传送大量信息 以上传name=haha,password=miaomiao为例,查看不同body数据格式 form-data(表单,可以传文件)POST /online/test/? HTTP/1.1Host: 127.0.0.1:8080Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gWcache-control: no-cachePostman-Token: c6e21725-caec-4a40-841c-7b92f87f6999Content-Disposition: form-data; name="name"hahaContent-Disposition: form-data; name="password"miaomiao------WebKitFormBoundary7MA4YWxkTrZu0gW--若在表单中附加一张图片 POST /online/test/? HTTP/1.1Host: 127.0.0.1:8080Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gWcache-control: no-cachePostman-Token: 437207ba-e007-4de8-bbcd-bdc88db6e445Content-Disposition: form-data; name="name"hahaContent-Disposition: form-data; name="password"miaomiaoContent-Disposition: form-data; name="profile"; filename="C:\Users\Think\Pictures\profile.jpg------WebKitFormBoundary7MA4YWxkTrZu0gW--其中表单的每一项都有Content-Disposition描述。------WebKitFormBoundary7MA4YWxkTrZu0gW--是随机生成的分隔标记x-www-form-urlencoded(也是表单,但不可以传文件)POST /online/test/? HTTP/1.1Host: 127.0.0.1:8080Content-Type: application/x-www-form-urlencodedcache-control: no-cachePostman-Token: 35566466-0568-4731-9c1c-6eda7dfca105name=hahapassword=miaomiaoundefined=undefined因为不用传文件,所以不用单独描述,将表单信息拼凑在一起就可以了。application/x-www-form-urlencoded: 窗体数据被编码为名称/值对。这是标准的编码格式。multipart/form-data: 窗体数据被编码为一条消息,页上的每个控件对应消息中的一个部分。raw(text,json,xml...)(其它文本格式)以json格式为例 POST /online/test/? HTTP/1.1Host: 127.0.0.1:8080Content-Type: application/jsoncache-control: no-cachePostman-Token: 06a9f071-4424-428a-b17b-9b4fe6f209a2{ "name":"haha", "password":"miaomiao"}------WebKitFormBoundary7MA4YWxkTrZu0gW--binary(二进制)用作传输文件,包括前面的form-data上传文件,文件都是以一定的编码方式写在body中的,在服务器端获取该请求的输入流后,即可按行接收流中的数据信息。 在Spring Boot中接口的接收数据总结URL接收GET参数列表对于数字基本类型和包装类型都可接收,但是若前端并没传这个数字,那么包装类型可以在代码里判空,异常处理就好,但是基本类型是不能判空的,所以对于数字首选包装类型。 @RequestMapping(value = "/",method = RequestMethod.GET) @ResponseBody private ResInfo test(String name,String password){ return new ResInfo(200,"登录成功: "+name+" "+password); }PostMan接收 ...

July 2, 2019 · 2 min · jiezi

Spring-注解编程之模式注解

上篇文章研究 Spring XML Schema 扩展进制,这段时候一直研究 Spring 注解编程的原理。原本以为有了之前研究基础,可以很快理解注解编程原理。没想到这个过程非常困难,注解编程源码难度是 XML 扩展好几倍。o(╥﹏╥)o。 Spring 框架中有很多可用的注解,其中有一类注解称模式注解(Stereotype Annotations),包括 @Component, @Service,@Controller,@Repository 等。只要在相应的类上标注这些注解,就能成为 Spring 中组件(Bean)。 需要配置开启自动扫描。如在 XML 中配置 <context:component-scan base-package="xxx.xxx.xx"/>` 或使用注解 @ComponentScan。从最终的效果上来看,@Component, @Service,@Controller,@Repository 起到的作用完全一样,那为何还需要多个不同的注解? 从官方 wiki 我们可以看到原因。 A _stereotype annotation_ is an annotation that is used to declare the role that a component plays within the application. For example, the @Repository annotation in the Spring Framework is a marker for any class that fulfills the role or _stereotype_ of a repository (also known as Data Access Object or DAO).不同的模式注解虽然功能相同,但是代表含义却不同。 ...

July 1, 2019 · 2 min · jiezi

十几位资深架构师整理了2019最新架构师学习体系分享给大家

不管是开发、测试、运维,每个技术人员心里都有一个成为技术大牛的梦,毕竟“梦想总是要有的,万一实现了呢”!正是对技术梦的追求,促使我们不断地努力和提升自己。 然而“梦想是美好的,现实却是残酷的”,很多同学在实际工作后就会发现,梦想是成为大牛,但做的事情看起来跟大牛都不沾边, 例如: 程序员说“天天写业务代码还加班,如何才能成为技术大牛” 测试说“每天都有执行不完的测试用例” 运维说“扛机器接网线敲shell命令,这不是我想要的运维人生” 提升技术的误区: 有人认为想成为技术大牛最简单直接、快速有效的方式是“拜团队技术大牛为师”,让他们平时给你开小灶,给你分配一些有难度的任务。 有这种想法是错误的,主要有这几个原因: 1、首先,大牛是很忙的,一个团队里面,如果大牛平时经常给你开小灶,难免会引起其他团队成员的疑惑,我个人认为如果团队里的大牛如果真正有心的话,多给团队培训是最好的。然而做过培训的都知道,准备一场培训是很耗费时间的,课件和材料至少2个小时(还不能是碎片时间),讲解1个小时,大牛们一个月做一次培训已经是很高频了。在此我向大家推荐一个架构学习交流圈。交流学习企鹅圈号:948368769 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多 2、大牛不多,不太可能每个团队都有技术大牛,只能说团队里面会有比你水平高的人,即使他每天给你开小灶,最终你也只能提升到他的水平;而如果是跨团队的技术大牛,由于工作安排和分配的原因,直接请教和辅导的机会是比较少的,单凭参加几次大牛的培训,是不太可能就成为技术大牛的。 学习方式 如何想办法真正的提升自己:more and more 做的更多,做的比你主管安排给你的任务更多。熟悉更多业务,不管是不是你负责的;熟悉更多代码,不管是不是你写的 这样做有很多好处,举几个简单的例子: 1:需求分析的时候更加准确,能够在需求阶段就识别风险、影响、难点 2:问题处理的时候更加快速,因为相关的业务和代码都熟悉,能够快速的判断问题可能的原因并进行排查处理 3:方案设计的时候考虑更加周全,由于有对全局业务的理解,能够设计出更好的方案 4:找到正确的学习路线 一、构成架构师的技能体系 二、阅读源码,分析源码知识点总汇 源码分析专题详细介绍了源码中所用到的经典设计思想及常用设计模式,先打好内功基础,了解大牛是如何写代码的,从而吸收大牛的代码功力。 结合Spring5和MyBatis源码,带你理解作者框架思维,帮助大家寻找分析源码的切入点,在思想上来一次巨大的升华。 三、分布式架构技能学习有了大牛的代码功底之后,接下来可以更好地学习分布式架构技术。 分布式架构的好处和优点---->必然性,适应市场需求,能够去找一些更大的平台发展,提升自己的综合技术能力和薪资。 从分布式架构原理,到分布式架构策略,再到分布式架构中间件,最后会有分布式架构实战,让程序员可以在技术深度和技术广度上得到飞跃的提升,成为互联网行业所需要的T型人才。 四、微服务架构技能总汇随着业务的发展,代码量的膨胀和团队成员的增加,传统单体式架构的弊端越来越凸显,严重制约了业务的快速创新和敏捷交付。为了解决传统单体架构面临的挑战,先后演进出了SOA服务化架构、RPC框架、分布式服务框架,最后就是当今非常流行的微服务架构。微服务化架构并非银弹,它的实施本身就会面临很多陷阱和挑战,涉及到设计、开发、测试、部署、运行和运维等各个方面,一旦使用不当,则会导致整个微服务架构改造的效果大打折扣,甚至失败。 五、并发编程从Java基础接触多线程,到分布式架构环境下的高并发访问,并发编程充分利用好各个服务器处理器,以最高的效率处理各个任务协同有序工作。透彻理解锁的应用 六、优化调优大家都知道,这个一直是让程序员比较头疼的问题。当系统架构变得复杂而庞大之后,xing能方面就会下降,如果想成为一名优秀的架构师,xing能优化就是你必须思考的问题。 七、Java开发必知工具一名优秀的架构师必须有适合自己的兵器,也就是工欲善其事必先利其器,不管是小白,还是资深开发,都需要先选择好的工具。工程化专题的学习能帮助你和团队提升开发效率,让自己有更多时间来思考。 Git:可以更好地管理你和你团队的代码。 Maven:可以更好地管理jar包和项目的构建等。 Jenkins:可以更好地持续编译,集成,发布你的项目。在此我向大家推荐一个架构学习交流圈。交流学习企鹅圈号:948368769 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多 Sonar:一个开源的代码质量分析平台,便于管理代码的质量,可检查出项目代码的漏洞和潜在的逻辑问题(提升代码的质量,更加高效地提升开发效率)。 八、实践一个双十一电商项目电商项目目的是把所学的分布式,微服务,性能调优等知识运用起来,只有在项目中你才能巩固知识,提升自己。实践电商项目会利用云服务器搭建真实的开发和部署环境,让你从零到项目实战,体验真实的企业级项目开发过程,让你具备独立开发和搭建分布架构系统的能力。 总结要想有机会,首先你得从人群中冒出来,要想冒出来,你就必须做到与众不同,要做到与众不同,你就要做得更多! 成为技术大牛梦想虽然很美好,但是要付出很多,不管是Do more还是Do better还是Do exercise,都需要花费时间和精力,这个过程中可能很苦逼,也可能很枯燥,这里我想特别强调一下:前面我讲的都是一些方法论的东西,但真正起决定作用的,其实还是我们对技术的热情和兴趣!

July 1, 2019 · 1 min · jiezi

2019淘宝OceanBase分布式系统负载均衡案例分享

摘要:Heroku的问题让我们意识到,在负载均衡测试时发现问题并妥善解决的成功经验有没有?于是,挖掘出“淘宝在双十一压测OB时发现存在严重的随机访问导致负载不均问题,并通过加权算法妥善解决”的成功案例,也就是本文。 在CSDN云计算频道日前所做的文章《响应高达6秒 用户揭露Heroku私自修改路由造成高支出》中,网友们认为这是“因随机调度+Rails的单线程处理导致延迟增加的负载均衡失败的案例”。但在负载均衡测试时就能发现问题并妥善解决的成功经验有没有?在随后的微博中,支付宝的@Leverly评论:“去年双11前的压测OB就发现了存在严重的随机访问导致负载不均问题,还好通过加权算法很好的解决了。” 引发了我们的关注,于是有了本文。重点是淘宝在“双十一”背后,OceanBase分布式系统负载均衡的经验分享。 云计算所具备的低成本、高性能、高可用性、高可扩展性等特点与互联网应用日益面临的挑战不谋而合,成为近年来互联网领域的热门话题。作为一名技术人员不难理解在云计算的底层架构中,分布式存储是不可或缺的重要组成部分。国外知名的互联网公司如Google、Amazon、Facebook、Microsoft、Yahoo等都推出了各自的分布式存储系统,在国内OceanBase是淘宝自主研发的一个支持海量数据的高性能分布式数据库系统,实现了数千亿条记录、数百TB数据上的跨行跨表事务[1]。 在分布式系统中存在着著名的“短板理论”[2],一个集群如果出现了负载不均衡问题,那么负载最大的机器往往将成为影响系统整体表现的瓶颈和短板。为了避免这种情况的发生,需要动态负载均衡机制,以达到实时的最大化资源利用率,从而提升系统整体的吞吐。在此我向大家推荐一个架构学习交流圈。交流学习企鹅圈号:948368769 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多 本文将结合OceanBase的实际应用和大家分享一个去年淘宝双十一前期的准备工作中遇到负载均衡相关案例,抛砖引玉,期望对大家的工作有所启发。 OceanBase架构介绍OceanBase是一个具有自治功能的分布式存储系统,由中心节点RootServer、静态数据节点ChunkServer、动态数据节点UpdateServer以及数据合并节点MergeServer四个Server构成[1],如图1所示。 Tablet:分片数据,最基本的存储单元,一般会存储多份,一个Table由多个tablet构成; RootServer:负责集群机器的管理、Tablet定位、数据负载均衡、Schema等元数据管理等。 UpdateServer:负责存储动态更新数据,存储介质为内存和SSD,对外提供写服务; ChunkServer:负责存储静态Tablet数据,存储介质为普通磁盘或者SSD。 MergeServer:负责对查询中涉及多个Tablet数据进行合并,对外提供读服务; 在一个集群中,Tablet的多个副本分别存储在不同的ChunkServer,每个ChunkServer负责一部分Tablet分片数据,MergeServer和ChunkServer一般会一起部署。 双十一前期准备 对于淘宝的大部分应用而言,“双十一”就是一年一度的一次线上压测。伴随流量不断刷新着历史新高,对每个系统的可扩展性提出了很大的挑战。为了迎战双十一各产品线对有可能成为瓶颈部分的流量进行预估和扩容成为刻不容缓的任务。在本文要分享的案例中,应用方根据历史数据预估读请求的访问峰值为7w QPS,约为平时的5-6倍,合计每天支持56亿次的读请求。当时OceanBase集群部署规模是36台服务器,存储总数据量为200亿行记录,每天支持24亿次的读请求。 当前集群的读取性能远不能满足需求,我们首先进行了一次扩容,上线了10台Chunkserver/Mergeserver服务器。由于OceanBase本身具有比较强的可扩展性,为集群加机器是一件非常简单的操作。中心节点Rootserver在新机器注册上线后,会启动Rebalance功能以Tablet为单位对静态数据进行数据迁移,见下图的示意,最终达到所有ChunkServer上数据分片的均衡分布。 扩容完成后引入线上流量回放机制进行压力测试,以验证当前集群的性能是否可以满足应用的双十一需求。我们使用了10台服务器,共2000-4000个线程并发回放线上读流量对集群进行压测,很快发现集群整体的QPS在达到4万左右后,压测客户端出现大量超时现象,平均响应延迟已经超过阈值100ms,即使不断调整压力,系统的整体QPS也没有任何增大。此时观察整个集群机器的负载状态发现只有极个别服务器的负载超高,是其他机器的4倍左右,其他机器基本处于空闲状态,CPU、网络、磁盘IO都凸现了严重的不均衡问题。 负载不均衡导致了整体的吞吐取决于负载最高的那台Server,这正是前文提到的典型 “短板理论”问题。 负载不均问题跟踪客户端连接到OceanBase之后一次读请求的读流程如下图所示: Client 从RootServer获取到MergeServer 列表; Client将请求发送到某一台MergeServer; MergeServer从RootServer获取请求对应的ChunkServer位置信息; MergeServer将请求按照Tablet拆分成多个子请求发送到对应的ChunkServer; ChunkServer向UpdateServer请求最新的动态数据,与静态数据进行合并; MergeServer合并所有子请求的数据,返回给Client; OceanBase的读请求流程看起来如此复杂,实际上第1步和第3步中Client与RootServer以及MergeServer与RootServer的两次交互会利用缓存机制来避免,即提高了效率,同时也极大降低了RootServer的负载。 分析以上的流程可知,在第2步客户端选择MergeServer时如果调度不均衡会导致某台MergeServer机器过载;在第4步MergeServer把子请求发送到数据所在的ChunkServer时,由于每个tablet会有多个副本,选择副本的策略如果不均衡也会造成ChunkServer机器过载。由于集群部署会在同一台机器会同时启动ChunkServer和MergeServer,无法简单区分过载的模块。通过查看OceanBase内部各模块的提供的监控信息比如QPS、Cache命中率、磁盘IO数量等,发现负载不均问题是由第二个调度问题引发,即MergeServer对ChunkServer的访问出现了不均衡导致了部分ChunkServer的过载。 ChunkServer是存储静态Tablet分片数据的节点,分析其负载不均的原因包含如下可能: 数据不均衡: ChunkServer上数据大小的分布是不均衡的,比如某些节点因为存储Tablet分片数据量多少的差异性而造成的不均衡; 流量不均衡:数据即使是基本均衡的情况下,仍然会因为某些节点存在数据热点等原因而造成流量是不均衡的。 通过对RootServer管理的所有tablet数据分片所在位置信息Metadata进行统计,我们发现各个ChunkServer上的tablet数据量差异不大,这同时也说明扩容加入新的Server之后,集群的Rebalance是有效的(后来我们在其他应用的集群也发现了存在数据不均衡问题,本文暂不解释)。 尽管排除了数据不均衡问题,流量不均衡又存在如下的几种可能性: 存在访问热点:比如热销的商品,这些热点数据会导致ChunkServer成为访问热点,造成了负载不均; 请求差异性较大:系统负载和处理请求所耗费的CPUMemory磁盘IO资源成正比,而资源的耗费一般又和处理的数据量是成正比的,即可能是因为存在某些大用户而导致没有数据访问热点的情况下,负载仍然是不均衡的。 经过如上的分析至少已经确定ChunkServer流量不均衡问题和步骤4紧密相关的,而目前所采用的tablet副本选择的策略是随机法。一般而言随机化的负载均衡策略简单、高效、无状态,结合业务场景的特点进行分析,热点数据所占的比例并不会太高,把ChunkServer上的Tablet按照访问次数进行统计也发现并没有超乎想象的“大热点”,基本服从正太分布。在此我向大家推荐一个架构学习交流圈。交流学习企鹅圈号:948368769 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多 可见热点Tablet虽访问频率稍高对负载的贡献率相对较大,但是热点tablet的占比很低,相反所有非热点tablet对负载的贡献率总和还是很高的,这种情况就好比“长尾效应”[3]。 负载均衡算法设计如果把热点ChunkServer上非热点Tablet的访问调度到其他Server,是可以缓解流量不均问题的,因此我们设计了新的负载均衡算法为:以实时统计的ChunkServer上所有tablet的访问次数为Ticket,每次对Tablet的读请求会选择副本中得票率最低的ChunkServer。 同时考虑到流量不均衡的第二个原因是请求的差异较大问题,ChunkServer对外提供的接口分为Get和Scan两种,Scan是扫描一个范围的所有行数据,Get是获取指定一行数据,因此两种访问方式的次数需要划分赋予不同的权重(,)参与最终Ticket的运算: 除此之外,简单的区分两种访问模式还是远远不够的,不同的Scan占用的资源也是存在较大差异的,引入平均响应时间(avg_time)这个重要因素也是十分必要的: 负载均衡算法要求具有自适应性和强的实时性,一方面新的访问要实时累积参与下次的负载均衡的调度,另一方面历史权重数据则需要根据统计周期进行非线性的衰减(y 衰减因子),减少对实时性的影响: 采用新的算法后,很好的缓解了负载不均衡的问题,整体负载提升了1倍,整体QPS吞吐提升到了8w。 ...

July 1, 2019 · 1 min · jiezi

面试还不知道BeanFactory和ApplicationContext的区别

接口 BeanFactory 和 ApplicationContext 都是用来从容器中获取 Spring beans 的,但是,他们二者有很大不同我看到过很多问 BeanFactory 和 ApplicationContext 不同点的问题,考虑到这,我应该使用前者还是后者从 Spring 容器中获取 beans 呢?请向下看 什么是 Spring Bean这是一个非常简单而又很复杂的问题,通常来说,Spring beans 就是被 Spring 容器所管理的 Java 对象,来看一个简单的例子 package com.zoltanraffai; public class HelloWorld { private String message; public void setMessage(String message){ this.message = message; } public void getMessage(){ System.out.println("My Message : " + message); } }在基于 XML 的配置中, beans.xml 为 Spring 容器管理 bean 提供元数据 什么是 Spring 容器Spring 容器负责实例化,配置和装配 Spring beans,下面来看如何为 IoC 容器配置我们的 HelloWorld POJO ...

July 1, 2019 · 2 min · jiezi

怎么重复使用inputStream

引语:    之前做项目的时候遇到一个问题,就是从网络中读取的图片要上传到oss,而且要对图片进行裁剪和压缩,其中上传和裁剪都要使用到图片的inputStream,又因为inputstream不能重复读,导致裁剪是成功的,而上传是失败的.我们今天就提供两种方法来解决,inputStream不能重复读的问题. 问题分析:inputStream的内部有个pos指针,当读取的时候指针会不断的移动,当移动到末尾的时候,就无法再次读取了.我们写个简单的例子来看下: String text = "测试inputStream内容"; InputStream inputStream = new ByteArrayInputStream(text.getBytes()); byte[] readArray = new byte[inputStream.available()]; int readCount1 = inputStream.read(readArray); System.out.println("读取了" + readCount1 + "个字节"); byte[] readArray2 = new byte[inputStream.available()]; int readCount2 = inputStream.read(readArray2); System.out.println("读取了" + readCount2 + "个字节"); /** * 执行结果是 * 读取了23个字节 * 读取了-1个字节 */从执行结果可以看出确实inputstream的设计是只能读取一次.注意: 这里稍微提一下inputStream.available()这个方法,本地的文件可以直接知道文件的大小,但是如果是网络中的数据,这个方法最好不要用,因为传输的时候不是连续的,数据的大小会读取不准 问题解决:那么我们实际项目中应该怎么解决呢?总不能就真的只使用一次inputSteam吧.我们来看解决方法: 方法一: 使用ByteArrayOutputStream来缓存字节,然后每次读取从缓存的ByteArrayOutputStream中拿取.很自然的想到把inputStream的缓存起来(当然不一定说是要放在ByteArrayOutputStream,其他的方式也可以,都是缓存起来的思路,实现方式有很多种,这种比较方便) String text = "测试inputStream内容"; InputStream rawInputStream = new ByteArrayInputStream(text.getBytes()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = rawInputStream.read(buffer)) > -1) { outputStream.write(buffer, 0, len); } outputStream.flush(); InputStream in1 = new ByteArrayInputStream(outputStream.toByteArray()); InputStream in2 = new ByteArrayInputStream(outputStream.toByteArray()); int readCount1 = in1.read(buffer); int readCount2 = in2.read(buffer); System.out.println("读取了" + readCount1 + "个字节"); System.out.println("读取了" + readCount2 + "个字节"); /** * 执行结果是 * 读取了23个字节 * 读取了23个字节 *这里是先将inputStream的数据读取到output中,然后要反复使用inputStream中的内容的时候,我们将output中的数据取出(很神奇的设定,output可以反复取,input只能读一次) ...

June 30, 2019 · 1 min · jiezi

9springcloud整合logback打印sql语句

公众号 java乐园 Logback是由log4j创始人设计的又一个开源日志组件。logback当前分成三个模块:logback-core、logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个 改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging。logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。 Logback是要与SLF4J结合起来用的。Logback和log4j是非常相似的,如果你对log4j很熟悉,那对logback很快就会得心应手。spring boot内部使用Commons Logging来记录日志,但也保留外部接口可以让一些日志框架来进行实现,例如Java Util Logging,Log4J2还有Logback。如果想用某一种日志框架来进行实现的话,就必须先进行配置,默认情况下spring boot使用Logback作为日志实现的框架。spring boot从控制台打印出来的日志级别只有ERROR, WARN 还有INFO。(1)如果你想要打印debug级别的日志,可以通过application.yml文件配置: debug: true也可以在启动脚本添加参数: java -jar d: \sc-xxx.jar --debug(2) 配置logging.level.*来具体输出哪些包的日志级别 logging:level:root: INFOorg.springframework.web: DEBUGorg.hibernate: ERROR (3) 将日志输出到文件 默认情况下spring boot是不将日志输出到日志文件中,但可以通过在application.yml文件中配置logging.file文件名称和logging.path文件路径,将日志输出到文件 logging: path: F:\\springcloudLog file: info.log level root: info备注:A、 这里若不配置具体的包的日志级别,日志文件信息将为空B、若只配置logging.path,那么将会在F: springcloudLog文件夹生成一个日志文件为spring.log(ps:该文件名是固定的,不能更改)。如果path路径不存在,会自动创建该文件夹C、若只配置logging.file,那将会在项目的当前路径下生成一个info.log日志文件。这里可以使用绝对路径如,会自动在d盘下创建文件夹和相应的日志文件。 logging: file: d:\\ springcloudLog \\info.logD、logging.path和logging.file同时配置,不会在这个路径有F: springcloudLog info.log日志生成,logging.path和logging.file不会进行叠加(要注意)F、logging.path和logging.file的value都可以是相对路径或者绝对路径这就是基础的日志配置,可以直接在application.yml配置,还可以在classpath路径下,通过定义具体的日志文件来配置,例如:logback.xml 1、 新建项目sc-eureka-client-provider-logback,对应的pom.xml文件如下 <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>spring-cloud</groupId> <artifactId>sc-eureka-client-provider-logback</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sc-eureka-client-provider-logback</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!-- 说明是一个 eureka client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- spring boot实现Java Web服务 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 把tomcat-jdbc连接池排除掉,这样spring-boot就会寻找是否有HikariCP可用 --> <exclusions> <exclusion> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jdbc</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> </dependencies></project>可以到默认已经引入logback的jar包 ...

June 30, 2019 · 2 min · jiezi

8服务发现服务消费者Feign

公众号:java乐园 spring cloud的Netflix中提供了两个组件实现软负载均衡调用,分别是Ribbon和Feign。上一篇和大家一起学习了Ribbon。Ribbon :Spring Cloud Ribbon是基于HTTP和TCP的客户端负载工具,它是基于Netflix Ribbon实现的,它可以在客户端配置 ribbonServerList(服务端列表),然后轮询请求以实现均衡负载。Feign :spring cloud feign 是一个使用起来更加方便的 HTTP 客戶端。 在使用ribbon时,通常会使用RestTemplate实现对http请求的封装,形成了模板化的调用方法。spring cloud feign在此基础上做了进一步的封装,Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign, 可以做到使用HTTP请求远程服务时能与调用本地方法一样的编码体验,完全感知不到这是远程方法,更感知不到这是个HTTP请求。 1、 新建项目sc-eureka-client-consumer-feign,对应的pom.xml文件如下 <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>spring-cloud</groupId> <artifactId>sc-eureka-client-consumer-feign</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sc-eureka-client-consumer-feign</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!-- 说明是一个 eureka client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.5.RELEASE</version> </dependency> --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies></project>备注:spring cloud 2.x后spring-cloud-starter-feign已经被标识为过期,推荐使用spring-cloud-starter-openfeign ...

June 30, 2019 · 1 min · jiezi

Spring中Bean创建完成后执行指定代码的几种实现方式

在实际开发中经常会遇到在spring容器加载完某个bean之后,需要执行一些业务代码的场景。比如初始化配置、缓存等。有以下几种方式可以实现此需求(欢迎补充) 实现ApplicationListener接口实现ApplicationListener接口并实现方法onApplicationEvent()方法,Bean在创建完成后会执行onApplicationEvent方法 @Componentpublic class DoByApplicationListener implements ApplicationListener<ContextRefreshedEvent> { public DoByApplicationListener() { System.out.println("DoByApplicationListener constructor"); } @Override public void onApplicationEvent(ContextRefreshedEvent event) { if (event.getApplicationContext().getParent() == null) { System.out.println("DoByApplicationListener do something"); } }}实现InitializingBean接口实现InitializingBean接口并实现方法afterPropertiesSet(),Bean在创建完成后会执行afterPropertiesSet()方法 @Componentpublic class DoByInitializingBean implements InitializingBean { public DoByInitializingBean() { System.out.println("DoByInitializingBean constructor"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("InitByInitializingBean do something"); }}使用@PostConstruct注解在Bean的某个方法上使用@PostConstruct注解,Bean在创建完成后会执行该方法 @Componentpublic class DoByPostConstructAnnotation { public DoByPostConstructAnnotation() { System.out.println("DoByPostConstructAnnotation constructor"); } @PostConstruct public void init(){ System.out.println("InitByPostConstructAnnotation do something"); }}使用init-method使用init-metod可以指定Bean在创建完成后,初始化使用的方法,比如有个Bike类 ...

June 30, 2019 · 1 min · jiezi

如何在Spring-Boot中禁用Actuator端点安全性

默认情况下,所有敏感的HTTP端点都是安全的,只有具有ACTUATOR角色的用户才能访问它们。 安全性是使用标准的HttpServletRequest.isUserInRole方法实施的。 我们可以使用management.security.enabled = false 来禁用安全性。只有在执行机构端点在防火墙后访问时,才建议禁用安全性。 如何在自定义端口上运行Spring Boot应用程序? 为了在自定义端口上运行Spring Boot应用程序,您可以在application.properties中指定端口。 server.port = 8090

June 28, 2019 · 1 min · jiezi

关于JavaConfig的初步认识

Spring JavaConfig是Spring社区的产品,它提供了配置Spring IoC容器的纯Java方法。因此它有助于避免使用XML配置。使用JavaConfig的优点在于: 面向对象的配置。由于配置被定义为JavaConfig中的类,因此用户可以充分利用Java中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean方法等。 减少或消除XML配置。基于依赖注入原则的外化配置的好处已被证明。但是,许多开发人员不希望在XML和Java之间来回切换。 JavaConfig为开发人员提供了一种纯Java方法来配置与XML配置概念相似的Spring容器。 从技术角度来讲,只使用JavaConfig配置类来配置容器是可行的,但实际上很多人认为将JavaConfig与XML混合匹配是理想的。 类型安全和重构友好。JavaConfig提供了一种类型安全的方法来配置Spring容器。由于Java 5.0对泛型的支持,现在可以按类型而不是按名称检索bean,不需要任何强制转换或基于字符串的查找。

June 27, 2019 · 1 min · jiezi

JDK动态代理

1 动态代理 动态代理设计模式的原理:使用一个代理对象将原对象(目标对象)包装起来,然后利用该代理对象取代原对象。 任何对原对象的调用都要经过代理。代理对象决定是否以及何时将方法调用转到原对象上。2 动态代理用那些? 1 基于接口的动态代理 : 如 JDk 提供的代理 2 基于继承的动态代理 : 如第三方包 Cglib,javassist 动态代理这里我们进行演示JDK 自身提供的代理:jdk动态代理需要实现两个成员:一个是Proxy代理类,一个是InvocationHandler接口 1 JDK动态代理类 Proxy : 所有动态代理对象的父类,专门用于生成代理类对象或者代理对象 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 2 接口 InvocationHandler 完成动态代理的整个过程(动态代理类的调用处理程序都必须实现InvocationHandler接口) proxy : 动态代理对象 method : 正在被调用的方法对象 args : 正在被调用的方法参数 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;接口和实现方法 public interface Arithmetic { public int add(int a,int b ); public int mul(int a,int b );}public class ArithmeticImpl implements Arithmetic { @Override public int add(int a, int b) { return a + b; } @Override public int mul(int a, int b) { return a * b; }}代理对象 ...

June 27, 2019 · 4 min · jiezi

使用IDEA创建一个SpringMVC的项目

写在前面:参照《spring实战》实现 1. 使用idea创建项目 这里使用gradle构建项目依赖 使用自己本地的gradle环境 2. 构建完成项目之后修改gradle的配置文件(使用阿里库) 3. 引入springmvc的相关依赖 版本配置文件,这个信息都在上面的依赖中使用到了 4. 这里我们使用纯Java配置 a. 创建WebInitializer类用于替代web.xml的功能 b. WebConfig.java配置springmvc的相关参数 c. RootConfig.java配置spring的相关参数 d. 创建一个控制器HomeController e. 在下面的目录里面创建home.jsp 5. 使用tomcat启动项目,访问

June 26, 2019 · 1 min · jiezi

SpringBootLucene案例介绍

SpringBoot+Lucene案例介绍 一、案例介绍 模拟一个商品的站内搜索系统(类似淘宝的站内搜索);商品详情保存在mysql数据库的product表中,使用mybatis框架;站内查询使用Lucene创建索引,进行全文检索;增、删、改,商品需要对Lucene索引修改,搜索也要达到近实时的效果。对于数据库的操作和配置就不在本文中体现,主要讲解与Lucene的整合。 一、引入lucene的依赖 向pom文件中引入依赖 <!--核心包--> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-core</artifactId> <version>7.6.0</version> </dependency> <!--对分词索引查询解析--> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-queryparser</artifactId> <version>7.6.0</version> </dependency> <!--一般分词器,适用于英文分词--> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version>7.6.0</version> </dependency> <!--检索关键字高亮显示 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-highlighter</artifactId> <version>7.6.0</version> </dependency> <!-- smartcn中文分词器 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-smartcn</artifactId> <version>7.6.0</version> </dependency>三、配置初始化Bean类 初始化bean类需要知道的几点: 1.实例化 IndexWriter,IndexSearcher 都需要去加载索引文件夹,实例化是是非常消耗资源的,所以我们希望只实例化一次交给spring管理。 2.IndexSearcher 我们一般通过SearcherManager管理,因为IndexSearcher 如果初始化的时候加载了索引文件夹,那么 后面添加、删除、修改的索引都不能通过IndexSearcher 查出来,因为它没有与索引库实时同步,只是第一次有加载。 3.ControlledRealTimeReopenThread创建一个守护线程,如果没有主线程这个也会消失,这个线程作用就是定期更新让SearchManager管理的search能获得最新的索引库,下面是每25S执行一次。 5.要注意引入的lucene版本,不同的版本用法也不同,许多api都有改变。 @Configurationpublic class LuceneConfig { /** * lucene索引,存放位置 */ private static final String LUCENEINDEXPATH="lucene/indexDir/"; /** * 创建一个 Analyzer 实例 * * @return */ @Bean public Analyzer analyzer() { return new SmartChineseAnalyzer(); } /** * 索引位置 * * @return * @throws IOException */ @Bean public Directory directory() throws IOException { Path path = Paths.get(LUCENEINDEXPATH); File file = path.toFile(); if(!file.exists()) { //如果文件夹不存在,则创建 file.mkdirs(); } return FSDirectory.open(path); } /** * 创建indexWriter * * @param directory * @param analyzer * @return * @throws IOException */ @Bean public IndexWriter indexWriter(Directory directory, Analyzer analyzer) throws IOException { IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer); IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig); // 清空索引 indexWriter.deleteAll(); indexWriter.commit(); return indexWriter; } /** * SearcherManager管理 * * @param directory * @return * @throws IOException */ @Bean public SearcherManager searcherManager(Directory directory, IndexWriter indexWriter) throws IOException { SearcherManager searcherManager = new SearcherManager(indexWriter, false, false, new SearcherFactory()); ControlledRealTimeReopenThread cRTReopenThead = new ControlledRealTimeReopenThread(indexWriter, searcherManager, 5.0, 0.025); cRTReopenThead.setDaemon(true); //线程名称 cRTReopenThead.setName("更新IndexReader线程"); // 开启线程 cRTReopenThead.start(); return searcherManager; }}四、创建需要的Bean类 ...

June 25, 2019 · 4 min · jiezi

spring-statemachine的企业可用级开发指南6持久化

目前为止,我们都是从状态流程的开始阶段创建一个状态机,然后一路走下去。但在实际业务中,状态机可能需要在某个环节停留,等待其他业务的触发,然后再继续下面的流程。比如订单,可能在支付环节需要等待一个剁手的用户隔天再下单,所以这里面涉及到一个创建的状态机该何去何从的问题。在spring statemachine中,给出来的办法就是保存起来,到需要的时候取出来用。 1、持久化到本地内存严格来说,你完全可以自己保存状态机,比如我就自己用map保存下来了。 public class MachineMap { public static Map<String,StateMachine<OrderStates, OrderEvents>> orderMap = new HashMap<String,StateMachine<OrderStates, OrderEvents>>();public static Map<String,StateMachine<FormStates, FormEvents>> formMap = new HashMap<String,StateMachine<FormStates, FormEvents>>();}这个代码一看就明白,我用唯一id作为key(order就是orderId,form就是formId),把状态机保存到map表里面,在实际的业务中,自由存取 @RequestMapping("/sendEvent") void sendEvent(String machineId,String events,String id) throws Exception{ if(machineId.equals("form")) { StateMachine sm = MachineMap.formMap.get(id); Form form = new Form(); form.setId(id); if(sm == null) { if(events.equals("WRITE")) { sm = formStateMachineBuilder.build(beanFactory); sm.start(); MachineMap.formMap.put(id, sm); }else { System.out.println("该表单流程尚未开始,不能做"+events+"转换"); return; } } Message message = MessageBuilder.withPayload(FormEvents.valueOf(events)).setHeader("form", form).build(); sm.sendEvent(message); } if(machineId.equals("order")) { StateMachine sm = MachineMap.orderMap.get(id); Order order = new Order(); order.setId(id); if(sm == null) { if(events.equals("PAY")) { sm = orderStateMachineBuilder.build(beanFactory); sm.start(); MachineMap.orderMap.put(id, sm); }else { System.out.println("该订单流程尚未开始,不能做"+events+"转换"); return; } } Message message = MessageBuilder.withPayload(OrderEvents.valueOf(events)).setHeader("order", order).setHeader("test","test1").build(); sm.sendEvent(message); }}在这段代码里面,我根据不同种类的状态机去map取和id对应的状态机,如果状态机没在map里面,就创建一个,然后塞进map表。其实写这个代码,并没有其他意思,只是为了告诉你,spring打算管理你的map表,所以出了一个接口来规范这件事: ...

June 25, 2019 · 2 min · jiezi

spring层面处理网络抖动导致的重复写入数据实现请求的幂等性

问题描述在用户使用网站创建新闻时,由于网络波动,导致发送了多个创建新闻的请求,使得系统中存在了冗余的数据。 问题分析看过很多文章,大部分思路都是:如果同一用户在很短时间内发送了重复的post请求,那么后台只处理第一个请求,后面的请求则过滤掉。具体实现方式则是添加一个springmvc的拦截器,当一个post请求过来时,首先去缓存中查询短时间内有没有相同请求到来,如果没有,则把该请求放入缓存,并将请求传递下去;如果有,则不再执行后续操作。但是这种方式也会面临一个问题:如果两个请求同时到来,同时读缓存,这样两个请求都不会读到记录,导致重复执行了两个请求。于是我们想到给缓存加锁,这样就可以避免3中出现的情况。但是众所周知,普通的加锁是很影响效率的,我们也不能舍弃效率来保证幂等性。为了提升加锁后的效率,我采用了DCL(双重检查锁,没有学过的小伙伴可以去查一查)来进行缓存的加锁读写。代码由于我的应用是单例的,所以采用guava来做缓存。 import com.csdc.cett.exception.RequestException;import com.csdc.cett.util.MD5;import com.google.common.cache.Cache;import com.google.common.cache.CacheBuilder;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.util.Enumeration;import java.util.concurrent.TimeUnit;/** * 处理重复请求,保持请求的幂等性 * 使用双重检查锁定(DCL) * * @author ksyzz * @since <pre>2019/06/20</pre> */@Componentpublic class RequestInterceptor implements HandlerInterceptor { private static volatile Cache<String, String> loadingCache = CacheBuilder.newBuilder() .maximumSize(500) .expireAfterWrite(5, TimeUnit.SECONDS) .build(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 只需要保持用户登录后的POST请求的幂等性,此处要排除文件分片上传 if ("POST".equals(request.getMethod()) && request.getHeader("Auth-Token") != null && !request.getRequestURI().contains("/upload/files")) { // 要求:用户5秒内不能重复提交相同url,相同参数的请求 // 存储方式为 md5(URI+Auth-Token+RequestParams+InputStream) StringBuilder md5 = new StringBuilder(); // Auth-Token为用户身份标识 String token = request.getHeader("Auth-Token"); md5.append(request.getRequestURI()); md5.append(token); Enumeration<String> parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()) { String key = parameterNames.nextElement(); String parameter = request.getParameter(key); md5.append("&").append(key).append("=").append(parameter); } String cacheKey = MD5.getHashString(md5.toString()); // 校验是否已经提交过请求 if (loadingCache.getIfPresent(cacheKey) == null) { synchronized (loadingCache) { if (loadingCache.getIfPresent(cacheKey) == null) { // 此处value可不设置 loadingCache.put(cacheKey, ""); return true; } } } throw new RequestException("请勿重复提交"); } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { }}

June 25, 2019 · 1 min · jiezi

ArraysasList存在的坑

引语:阿里巴巴java开发规范说到使用工具类Arrays.asList()方法把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException(),我们来看一下为什么会出现这种情况. 问题分析:我们做个测试 public static void main(String[] args) { List<String> list = Arrays.asList("a", "b", "c"); // list.clear(); // list.remove("a"); // list.add("g"); }被注释的三行可以分别解开注释,运行后确实出现了规约中所说的异常.我们来看下Arrays.asList()做了什么操作. public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }看上去是个很正常的方法,然而实际上你点进到ArrayList发现,其实ArrayList并不是我们平时用的ArrayList. private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable { private static final long serialVersionUID = -2764017481108945198L; private final E[] a; ArrayList(E[] array) { a = Objects.requireNonNull(array); } @Override public int size() { return a.length; } @Override public Object[] toArray() { return a.clone(); } @Override @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { int size = size(); if (a.length < size) return Arrays.copyOf(this.a, size, (Class<? extends T[]>) a.getClass()); System.arraycopy(this.a, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } // 后面省略了而是Arrays里面的一个内部类.而且这个内部类没有add,clear,remove方法,所以抛出的异常其实来自于AbstractList. ...

June 25, 2019 · 1 min · jiezi

spring-statemachine的企业可用级开发指南5传递参数的message

在企业开发中,数据在不同的业务间传输是最常见的工作,所以虽然我们的主架构是用的状态机,也就是从流程状态的角度来看待这个项目,但在具体业务中,每个状态的转变中会牵涉到各类业务,这些业务有些需要收到状态机变化的通知,需要把状态值传递给业务类和业务方法,同样的,在处理状态变化是,也需要获取业务数据,方便不同的业务在同一个状态变化环节做各自的业务,下面我们就讲下这个数据在spring statemachine里面的传递。 这次我们的顺序变一下,由外部传入一个订单号到controller开始: @RequestMapping("/testOrderState") public void testOrderState(String orderId) throws Exception { StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory); System.out.println(stateMachine.getId()); // 创建流程 stateMachine.start(); // 触发PAY事件 stateMachine.sendEvent(OrderEvents.PAY); // 触发RECEIVE事件 Order order = new Order(orderId, "547568678", "广东省深圳市", "13435465465", "RECEIVE"); Message<OrderEvents> message = MessageBuilder.withPayload(OrderEvents.RECEIVE).setHeader("order", order).build(); stateMachine.sendEvent(message); // 获取最终状态 System.out.println("最终状态:" + stateMachine.getState().getId());}controller收到request请求的参数,orderId,然后状态机依次触发事件,到触发RECEIVE事件的时候,我们新建了一个Order,并把orderId塞进去了,其实更多的情况应该是我们拿到orderId,然后查询数据库,得到order数据对象,这里为了简化代码,就新建一个啦。 然后就是真正的主角登场了,Message。它其实不是spirng statemachine专属的,它是spring里面通用的一种消息工具,看它的源代码: package org.springframework.messaging; public interface Message<T> { /** * Return the message payload. */T getPayload();/** * Return message headers for the message (never {@code null} but may be empty). */MessageHeaders getHeaders();}它由两个部分组成,看图就知道了,和代码里面是一致的 ...

June 24, 2019 · 1 min · jiezi

Spring-Cloud-上手实战架构解析及实作

Spring简介为什么要使用微服务单体应用:目前为止绝大部分的web应用软件采用单体应用,所有的应用的用户UI、业务逻辑、数据库访问都打包在一个应用程序上。 缺点: 开发相互干扰,随着应用的不断升级沟通协调成本增加 应用上线由于某个功能升级导致需要整体的构建、整体测试、整体发布微服务把单体应用拆分成小的、松藕合分布式服务的形式 每个应用一定是独立构建、独立部署与测试,应用也是独立发布,应用于应用直接通常通过restful API接口的形式进行相互调用。解决了单体应用带来的困扰。 Spring cloud 是什么发展历史 2002,Rod Johonson发表了<<Expert One-on-One J2EE Design and Development>>, 包含了3万行的代码在包com.interface21中 2003,Juerge Hoeller,Yann Caroff 联系Rod,将书中代码开源,Yann提出Spring这个词,冠于书中代码; 并发布0.9,使用Apache 2.0协议;Thomas Risberg负责Spring JDBC;Ben Alex将Acegi Security贡献给Rod和Juergen 2004,1.0发布 2005,<<Professional Java Development with Spring Framework>> <<Pro Spring>>出版;1.2.6发布。 AspectJ Leader Adrian Coyler加入Interface21作为首席科学家; 2006,Security 1.0、Spring webflow 1.0发布;Spring 2.0发布; 2007,Spring Batch、WebService、Integration发布;Spring 2.5发布; 2008,Spring Integration 1.0,Spring 2.5.6,Spring Batch 1.0;买了g2One,一家提供Groovy and Grails的公司; 2009,被VMWare发了42亿美金买下;Spring Python、STS发布、3.0发布(将包拆开,不提供超级包),买了Cloud Foundry; 2010,VMWare买了RabbitMQ公司,获得RabbitMQ和Redis技术; 2011,Spring 3.1、Spring AMQP、Spring Data JPA、Spring-data-common 1.0 、Spring Data Redis、Spring Data Mongodb发布; 2012,Rod Johnson离开VMWare;Spring Android、Mobile发布; 2013,VMWare 和 EMC 合力组建了一家公司,Pivotal。Spring 4.0、Spring Boot发布; 2014,Spring 4.1.3、SpringBoot 1.0发布; 2015,Spring 4.2、4.3发布; 2016,Spring 4.3 GA 2017,Spirng 5.x Spring的出现让EJB等重量级的容器技术逐渐走向末路。Spring 通过对Bean的生命周期的管理,可以快速方便的实现业务的逻辑处理。Spring 可以方便的整合几乎所有的主流的开源项目如JPA,缓存,消息组合等等,方便的进行开发。 ...

June 24, 2019 · 3 min · jiezi

全栈之路JAVA基础课程八mysql事物隔离级别20190624v10

欢迎进入JAVA基础课程 博客地址:https://mp.csdn.net/mdeditor/...本系列文章将主要针对JAVA一些基础知识点进行讲解,为平时归纳所总结,不管是刚接触JAVA开发菜鸟还是业界资深人士,都希望对广大同行带来一些帮助。若有问题请及时留言或加QQ:243042162。 寄语:近日,“有最美辅导员“和“最美大学生”发布仪式在央视播出,树立起新时代辅导员和大学生的学习榜样。当下,我们也应该争做“最美程序员”,给世界一片美好。概述 数据库是可以控制事务的传播和隔离级别的,Spring在之上又进一步进行了封装,可以在不同的项目、不同的操作中再次对事务的传播行为和隔离级别进行策略控制。注意:Spring不仅可以控制事务传播行为(PROPAGATION_REQUIRED等),还可以控制事务隔离级别(ISOLATION_READ_UNCOMMITTED等)。 脏读(Drity Read):另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据。 不可重复读(Non-repeatable read): 解决了脏读后,会遇到,同一个事务执行过程中,另外一个事务提交了新数据,因此本事务先后两次读到的数据结果会不一致。 幻读(Phantom Read): 解决了不重复读,保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)。但是,如果另一个事务同时提交了新数据,本事务再更新时,就会“惊奇的”发现了这些新数据,貌似之前读到的数据是“鬼影”一样的幻觉。 事物的隔离级别隔离级别越高,并发性能越低。MySQL 默认的级别是:Repeatable read 可重复读。 READ UNCOMMITTED(未提交读) 。 在RU的隔离级别下,事务A对数据做的修改,即使没有提交,对于事务B来说也是可见的,这种问题叫脏读。这是隔离程度较低的一种隔离级别,在实际运用中会引起很多问题,因此一般不常用。 READ COMMITTED(提交读) 。 在RC的隔离级别下,不会出现脏读的问题。事务A对数据做的修改,提交之后会对事务B可见,举例,事务B开启时读到数据1,接下来事务A开启,把这个数据改成2,提交,B再次读取这个数据,会读到最新的数据2。在RC的隔离级别下,会出现不可重复读的问题。这个隔离级别是许多数据库的默认隔离级别。 REPEATABLE READ(可重复读)。 在RR的隔离级别下,不会出现不可重复读的问题。事务A对数据做的修改,提交之后,对于先于事务A开启的事务是不可见的。举例,事务B开启时读到数据1,接下来事务A开启,把这个数据改成2,提交,B再次读取这个数据,仍然只能读到1。在RR的隔离级别下,会出现幻读的问题。幻读的意思是,当某个事务在读取某个范围内的值的时候,另外一个事务在这个范围内插入了新记录,那么之前的事务再次读取这个范围的值,会读取到新插入的数据。Mysql默认的隔离级别是RR,然而mysql的innoDB引擎间隙锁成功解决了幻读的问题。 SERIALIZABLE(可串行化)。 可串行化是最高的隔离级别。这种隔离级别强制要求所有事物串行执行,在这种隔离级别下,读取的每行数据都加锁,会导致大量的锁征用问题,性能最差。 参考网站:(1)https://www.cnblogs.com/maypa...(2)https://baijiahao.baidu.com/s...

June 24, 2019 · 1 min · jiezi

Spring-Data-JPA中的getOnefindOne以及findById

我们今天聊一下Spring Data JPA里的三个方法,分别是getOne,findOne以及findById。咋一看三个方法都能返回一个结果集,用哪个好像都没问题。我当初也是这么想的,后来在写作业的过程中出错了,真相只有一个。我的例子是查询一个一对一映射关系的实体,极为简单,我就不上代码了;用findById则可以实现我们的需求,而使用getOne查询后对结果集进行打印,出现下面的异常。 org.hibernate.LazyInitializationException: could not initialize proxy - no Session这个异常好像跟使用哪个方法没太大的关系,因为他是在得到查询的结果集后,打印这个结果集的时候出现的,且映射关系默认使用FetchType.EAGER,实体的toString方法仅对实体自己的属性做打印,不处理映射关系,莫非是因为得到的一个代理对象,不能映射成实体?还希望路过的高人指点一二。 最后使用findOne,也能准确的查到结果,有趣的是打印了三条查询语句。 翻开官方的API,找找这几个磨人的方法都在哪: getOne来自JpaReposiroty接口,对于传入的标识则返回一个实体的引用;且取决于该方法的实现,可能会出现EntityNotFoundException,并会拒绝一些无效的标识; findById来自CrudRepository接口,通过它的id返回一个实体; findOne来自QueryByExampleExecutor接口,返回一个通过Example匹配的实体或者null; 那他们的区别也就是: getOne返回一个实体的引用,无结果会抛出异常;findById返回一个Optional对象;findOne返回一个Optional对象,可以实现动态查询;注:文中使用的版本为Spring Data JPA 2.1.8.RELEASE。

June 23, 2019 · 1 min · jiezi

工作日志跨域和缓存的冲突问题

记录和分享一篇工作中遇到的奇难杂症。一个前后端分离的项目,前端件图片上传到服务器上,存在跨域的问题。后端将图片返回给前端,并希望前端能对图片进行缓存。这是一个很常见的跨越和缓存的问题。可偏偏就能擦出意想不到的火花(据说和前端使用的框架有关)。 跨域问题首先要解决跨域的问题。方法很简单,重写addCorsMappings方法即可。前端反馈跨域的问题虽然解决,但是静态资源返回的响应头是Cache-Control: no-cache ,导致资源文件加载速度较慢。 处理跨域的代码 override fun addCorsMappings(registry: CorsRegistry) { super.addCorsMappings(registry) registry.addMapping("/**") .allowedHeaders("*") .allowedMethods("POST","GET","DELETE","PUT") .allowedOrigins("*") .maxAge(3600)}处理后的响应头 Access-Control-Allow-Headers: authorizationAccess-Control-Allow-Methods: POST,GET,DELETE,PUTAccess-Control-Allow-Origin: *Access-Control-Max-Age: 3600Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCHCache-Control: no-cache, no-store, max-age=0, must-revalidate静态资源配置然后再处理静态资源缓存的问题。方法也很简单,在资源映射的方法上加上.setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)) 代码 。前端反馈缓存的问题虽然解决,但是静态资源跨域的问题又双叒叕出现了。 处理静态资源代码 override fun addResourceHandlers(registry: ResourceHandlerRegistry) { registry.addResourceHandler("/attachment/**") .addResourceLocations("file:$attachmentPath") .setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS))}经过反复的调试后发现,是setCacheControl方法导致静态资源的跨域配置失效。至于什么原因,看了源码,翻了资料都没有找到(可能是我找的不够认真)。更让人匪夷所思的是,火狐浏览器竟然是可以正常使用的。这让我排查问题的方向更乱了。但我也不能甩锅给浏览器啊!就在我快要下定决心甩锅给浏览器的时候,再次验证了“船到桥头自然直”的真谛。我抱着试一试的心态加了一个拦截器! 解决方法到现在我还是不能很好地接受这个解决方法,我相信更好、更优雅地解决方法。目前的解决思路:既然返回的图片存在跨域和缓存的问题,那是否可以自定义拦截器,针对图片地址添加跨域和缓存的响应头呢? 拦截器代码 import org.springframework.stereotype.Componentimport javax.servlet.*import javax.servlet.http.HttpServletRequestimport javax.servlet.http.HttpServletResponseimport java.io.IOException@Componentclass CorsCacheFilter : Filter { @Throws(IOException::class, ServletException::class) override fun doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) { val response = res as HttpServletResponse val request = req as HttpServletRequest if (request.requestURL.contains("/attachment/")) { response.addHeader("Access-Control-Allow-Origin", "*") response.addHeader("Access-Control-Allow-Credentials", "true") response.addHeader("Access-Control-Allow-Methods", "GET") response.addHeader("Access-Control-Allow-Headers", "*") response.addHeader("Access-Control-Max-Age", "3600") response.addHeader("Cache-Control", "max-age=86400") } chain.doFilter(req, res) } override fun init(filterConfig: FilterConfig) {} override fun destroy() {}}MVC配置代码 ...

June 22, 2019 · 1 min · jiezi

前后端分离ssm配置swagger接口文档

之前配置过springboot,相比ssm要简单很多,现在记录一下ssm的配置 在pom.xml中加入依赖<!--swagger本身不支持spring mvc的,springfox把swagger包装了一下,让他可以支持springmvc--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency>添加配置类SwaggerConfig.java@WebAppConfiguration@EnableSwagger2@EnableWebMvc@ComponentScan(basePackages = "com.maxcore.controller")public class SwaggerConfig { @Bean public Docket customDocket() { // return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .build() .apiInfo(apiInfo()); } private ApiInfo apiInfo() { Contact contact = new Contact("娜", "https://www.baidu.me", "baidu_666@icloud.com"); return new ApiInfo("仿简书前台API接口",//大标题 title "Swagger测试demo",//小标题 "0.0.1",//版本 "www.baidu.com",//termsOfServiceUrl contact,//作者 "Blog",//链接显示文字 "https://www.baidu.me"//网站链接 ); }}在dispatcher-servlet.xml(springmvc的配置文件)中加入如下配置 <bean class="com.maxcore.config.SwaggerConfig" /> <mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/" /> <mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/" />要在controller层添加注解 最后启动项目,访问swagger接口文档的路径一定要对,不然一直报404,你以为你没配置对,其实是你路径不对,笔者在这里表示有很痛的领悟笔者的本地的访问路径是 http://localhost/jianShuSSM_w... 一般都是http://ip地址:端口(默认80,不显示)/项目名/swagger-ui.html ...

June 22, 2019 · 1 min · jiezi

前后端分离ssm配置跨域

前后端分离开发需要跨域,之前只会pringboot跨域,只需要一个配置类即可,下面记录一下ssm的配置 三个文件需要添加配置 SimpleCORSFilter.java实现Filterpublic class SimpleCORSFilter implements Filter { private boolean isCross = false; @Override public void destroy() { isCross = false; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (isCross) { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; System.out.println("拦截请求: " + httpServletRequest.getServletPath()); httpServletResponse.setHeader("Access-Control-Allow-Origin", "*"); httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); httpServletResponse.setHeader("Access-Control-Max-Age", "0"); httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token"); httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpServletResponse.setHeader("XDomainRequestAllowed", "1"); } chain.doFilter(request, response); } @Override public void init(FilterConfig filterConfig) throws ServletException { String isCrossStr = filterConfig.getInitParameter("IsCross"); isCross = isCrossStr.equals("true") ? true : false; System.out.println(isCrossStr); }dispatcher-servlet.xml(springMVC的配置文件)<!-- 接口跨域配置--> <mvc:cors> <mvc:mapping path="/**" allowed-origins="*" allowed-methods="POST, GET, OPTIONS, DELETE, PUT" allowed-headers="Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With" allow-credentials="true" /> </mvc:cors> ## web.xml ...

June 22, 2019 · 1 min · jiezi

关于Spring-Boot你不得不知道的事Spring-Boot的基本操作

1 Pom文件1.1 spring-boot-starter-parent表示当前pom文件从spring-boot-starter-parent继承下来,在spring-boot-starter-parent中提供了很多默认配置,可以简化我们的开发。 <parent> <groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.4.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent>Java版本和编码方式<properties> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version><resource.delimiter>@</resource.delimiter><maven.compiler.source>${java.version}</maven.compiler.source><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.target>${java.version}</maven.compiler.target></properties>依赖管理spring-boot-dependencies<properties> <activemq.version>5.15.9</activemq.version><antlr2.version>2.7.7</antlr2.version><appengine-sdk.version>1.9.73</appengine-sdk.version><artemis.version>2.6.4</artemis.version>...</properties>这样比如使用starter-web的时候就不需要指定版本号 <dependency> <groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.1.4.RELEASE</version></dependency>使用自己的parent项目这时候将依赖管理的问题放到dependencyManagement中。 官网说明文档见:13.2.2 Using Spring Boot without the Parent POM <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.4.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency></dependencies></dependencyManagement>1.2 打包管理使用mvn package打包的plugin。 <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin></plugins></build>1.3 Starters官网见:13.5 Starters Starters are a set of convenient dependency descriptors that you can include in your application. You get a one-stop shop for all the Spring and related technologies that you need without having to hunt through sample code and copy-paste loads of dependency descriptors. For example, if you want to get started using Spring and JPA for database access, include the spring-boot-starter-data-jpa dependency in your project.官方starter命名spring-boot-starter-* ...

June 21, 2019 · 4 min · jiezi

如何重新加载Spring-Boot上的更改而无需重新启动服务器

这可以使用DEV工具来实现。通过这种依赖关系,您可以节省任何更改,嵌入式tomcat将重新启动。 Spring Boot有一个开发工具(DevTools)模块,它有助于提高开发人员的生产力。Java开发人员面临的一个主要挑战是将文件更改自动部署到服务器并自动重启服务器。 开发人员可以重新加载Spring Boot上的更改,而无需重新启动服务器。这将消除每次手动部署更改的需要。Spring Boot在发布它的第一个版本时没有这个功能。 这是开发人员最需要的功能。DevTools模块完全满足开发人员的需求。该模块将在生产环境中被禁用。它还提供H2数据库控制台以更好地测试应用程序。

June 21, 2019 · 1 min · jiezi

Spring-Boot的学习之路02和你一起阅读Spring-Boot官网

官网是我们学习的第一手资料,我们不能忽视它。却往往因为是英文版的,我们选择了逃避它,打开了又关闭。我们平常开发学习中,很少去官网上看。也许学完以后,我们连官网长什么样子,都不是很清楚。所以,我们在开始去学习之前,我们先拜读一下Spring Boot官网,对其有一个大体上的了解。我们在后续的讲解中, 有可能会引用到官网上的知识。 如果要建立完整的知识体系,我的个人看法是了解官网这个环节是少不了的。我在写《Spring Boot的学习之路》这个系列时,增加了这样一篇文章,来体现其重要性。 那接下来,我们一起来阅读一下Spring Boot官网。我们从中可以得到哪些有价值的知识。 一. 访问Spring Boot网站,看看网站有哪些板块Spring Boot地址:https://spring.io/projects/spring-boot通过上面地址,我们可以看到如下图显示页面。 ① Projects 项目左边展示的所有的项目列表,Spring Boot就排在第一个,说明还是很重要的呢。不流行都不行 ② Overview 概述Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run".中文意思:Spring Boot使创建独立的、生产级的、基于Spring的应用程序变得容易,您可以“只运行”。 We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need very little Spring configuration.中文意思:我们对Spring平台和第三方库有一个独到的观点,这样你就可以以最少的麻烦开始了。大多数Spring Boot应用程序只需要很少的Spring配置。 ...

June 20, 2019 · 1 min · jiezi

动手搭建后端框架Velocity模板引擎的应用

为了提高开发效率,通常会想办法把一些模式固定的重复性的劳动抽取出来,以后再使用的时候,拿来主义就可以了。这样既可以提高开发效率,又降低了出错的风险。 这一思想在我们的日常工作中可以说随处可见,我们完成一项复杂的工程,并不需要面面俱到什么都自己写,我们完全可以利用第三方的jar包让我们达到事半功倍的效果,比如经常使用的apche的commons-lang3包。再比如java中的继承、我们自己封装的工具类等等。 另外一方面,对于源码文件,如果公司有成熟的框架,我们的开发都是遵循着框架制定的约定来进行开发的,我们在创建某一个业务的控制层、业务层、持久层的时候,实际上有相当一部分的工作是重复的。 那么对于源码文件的编写我们能否偷偷懒呢?答案肯定是可以的,我们可以利用模板引擎技术,将不变的部分写在模板文件中,将可变的部分作为变量传递到模板引擎的上下文中,最终生成我们想要的源码文件。 模板引擎的产品有很多,比如前端模板artTemplate、后端模板Velocity、FreeMarker等 本文以Velocity为例,总结一下它在实战中的应用 1.基础知识搭建过程涉及到的基础知识包括:Maven、Velocity、工厂模式、建造者模式、单元测试对于基础不熟悉的同学,建议看一下下面的两篇文章Velocity基础Velocity语法摘要 2.搭建工程2.1模块目录代码生成功能,在我设计的后台框架中,作为一个独立的模块存在,使用Maven构建。builder目录:建造者模式应用。由于代表表结构的Table实体稍显复杂,因此使用了建造者模式构建Table对象。其实不用也可以,因为Table不是很复杂,只是为了复习一下所学过的设计模式知识factory目录:工厂模式应用。在构建源码文件的时候,由于涉及到了Controller、Service、Dao、Domain这几种类型的文件,因此针对不同类型的文件,要使用其对应的处理类,因此使用了工厂模式handler目录:生成源文件的核心代码model目录:在生成domain的时候,由于字段需要从数据库中的表中读取,因此构造了与表对应的实体类方便处理utils目录:工具类Generator.java:程序主文件,调用入口test目录:单元测试 .├── generator.iml├── pom.xml└── src ├── main │   ├── java │   │   └── com │   │   └── wt │   │   └── master │   │   └── generator │   │   ├── Generator.java │   │   ├── builder │   │   │   ├── MySqlTableBuilder.java │   │   │   └── TableBuilder.java │   │   ├── factory │   │   │   └── GeneratorFactory.java │   │   ├── handler │   │   │   ├── BaseGenerator.java │   │   │   ├── ControllerGeneratorHandler.java │   │   │   ├── DomainGeneratorHandler.java │   │   │   ├── MapperGeneratorHandler.java │   │   │   └── ServiceGeneratorHandler.java │   │   ├── model │   │   │   └── Table.java │   │   └── util │   │   ├── JdbcUtils.java │   │   ├── SpringContextUtils.java │   │   ├── TableColumnUtils.java │   │   └── TableInfoUtils.java │   └── resources │   ├── config │   │   ├── applicationContext.xml │   │   └── db.properties │   └── template │   ├── controller.java.vm │   ├── dao.java.vm │   ├── domain.java.vm │   ├── service.java.vm │   └── serviceimpl.java.vm └── test └── com.wt.master.generator └── GeneratorTest.java2.2引入依赖<?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"> <parent> <artifactId>j2ee</artifactId> <groupId>com.wt.master</groupId> <version>1.0-SNAPSHOT</version> <relativePath>../version/</relativePath> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>generator</artifactId> <dependencies> <!-- 模板引擎 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.7</version> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>com.wt.master</groupId> <artifactId>core</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.4</version> </dependency> </dependencies></project>3.核心代码3.1模板文件的定义以controller层生成模板为例将不变的部分直接写到.vm文件中将模板文件中,有可能发生变化的部分,抽取为变量,变量的值从VelocityContext中获取在Velocity架构中,有一个上下文的定义,通过上下文,程序将变量放入上下文对象中。而模板从上下文中获取对应变量的值,获取的方式是${变量名},关于Velocity模板文件中的语法,参见上文提到的两篇文章 ...

June 19, 2019 · 5 min · jiezi

应用量化时代-微服务架构的服务治理之路

技术随业务而生,业务载技术而行。 近些年来,伴随数字经济的发展,在众多企业的数字化转型之路上,云原生、DevOps、微服务、服务治理等成为行业内不断被探讨的新话题。人们在理解和接受这些新型概念的同时,也不断地思考其可能的落地形态。需求是创造发生的原动力,于是一批代表性的开源技术或者框架涌现而出:Kubernetes,Spring Cloud,Service Mesh,Serverless…… 它们炙手可热,大放异彩。然而在具体落地过程中却步履维艰,磕磕绊绊。 本文试图结合企业业务的核心诉求,以应用形态发展历程为背景,帮助企业梳理应用面向云原生、微服务转型中涉及的各种服务治理问题,以及服务治理的发展趋势。 什么是服务治理? 服务治理(SOA governance),按照Anne Thomas Manes的定义是:企业为了确保事情顺利完成而实施的过程,包括最佳实践、架构原则、治理规程、规律以及其他决定性的因素。服务治理指的是用来管理SOA的采用和实现的过程。 由定义可知,服务治理关键因素在于:应用形态、数据采集、信息分析、管控策略和协议规范五个方面。用户群体只有从这五个层次出发,才能构建出符合企业规范与要求的服务治理平台,从而进一步为企业创造商业价值。 01 “微观”塑形,服务一小再小 世界上唯一不变的是变化本身。----By 斯宾塞.约翰逊 万理同此,纵观应用形态发展历程,从单机到网络、从单体到服务化、到微服务、到Serverless,再到未来,应用的形态随着业务驱动和技术演化,一直在不断变化。随之而来的是业务需求的复杂化与多样化,企业IT面临着大规模、高并发、应用快速创新等新难题,弹性与敏捷成为企业IT的迫切需求。 在IT行业内有两个“不成熟”的理论:第一,每增加一行代码就会带来N种风险;第二,任何问题都可以采取增加一层抽象的方式解决。因此面对企业IT复杂的环境,“小而精”逐渐取代“大而全”,成为构建企业服务的首选方式,这也导致软件设计原则中的“高内聚,低耦合”又开始成为不断被高调吟诵的主角,微服务理念因此大行其道。 微服务架构为业务单元可独立开发和独立部署,使服务具备灵活的动态处理机能,同时依赖高度抽象化的组件工具和多元化的通信机制,向用户屏蔽所有服务之间的通信细节的这种思想提供了最佳落地实践。微服务的出现有效地缩短了服务上线周期,并且允许企业快速响应客户反馈,为客户提供所期望的可靠服务。 然而随着企业业务的发展与扩张与微服务的深入,服务数量向不可控的规模增长,服务数量的爆发式增长,为服务管理以及线上治理带来了极大的挑战。服务治理应运而生,成为构建微服务架构系统的必备“良药”。 02 “量化”管控,服务无可遁形 数字永远不会说谎。 如今,微服务已经成为软件架构的实际指导思想,而以Docker和Kubernetes为代表的容器技术的延伸,也有效解决了微服务架构下多个服务单元的编排部署问题。然而,微服务架构下也隐藏着容易被忽视的风险:面临规模巨大的服务单元,如何对其进行有效合理的管控与治理? 服务治理领域开始被行业与用户所重视,期望能够获得有效的思维方式和技术手段,应对由于不断激增的服务单元带来的服务治理挑战。关于服务治理,我们看到的更多的是其功能集合:服务注册发现、服务配置、服务熔断、网关、负载均衡、服务跟踪、日志采集、监控平台等。但当我们抛开这些名词解释,重新审视服务治理的时候,这些名词并没有完整的解释我们的困惑:如何设置负载均衡策略?采集日志格式是什么?服务配置如何生效?服务跟踪如何进行精确定位? 显然单单通过这些功能名词无法满足我们构建服务治理平台的需求,但从这些功能中我们总结出一些规律与方法,我们将从功能场景的横向切面和技术手段的纵深层次,进行如何构建一个有效的服务治理平台的分析探讨。 首先,我们从服务治理功能场景的横向切面来看,其可以抽象为四个层面:量化,追踪,管控,规范。 量化量化包括服务数据采集、数据过滤和数据聚合三个层次。数据采集进一步细分为业务数据和性能数据,业务数据主要包括方法响应周期、服务内资源消耗规模、业务异常检测、方法调用次数、服务运行日志等;性能数据包括服务间响应时长、服务整体资源消耗等。 服务本身需要依赖不同的特性,构建不同的agent,来搜集服务运行时产生的数据。数据过滤针对采集的数据按照一定的格式规范进一步加工处理,例如基于kafka对原始的日志数据进行标准化处理后,导入日志系统。 数据聚合需要对独立的服务数据进行聚合操作,例如服务调用链呈现。 通过服务量化能够清晰的记录服务运行时产生的所有数据,为服务跟踪呈现和服务管控策略制定并提供强有力的数据支撑。 追踪追踪能够有效量化服务调用链路上发生的事情,具体来讲,可以划分为:服务间的链路跟踪和服务内部的方法调用链路跟踪。追踪的本质,不仅仅是为了呈现服务链路及服务路由信息,更重要的是呈现服务间请求,以及服务内部请求的响应延迟,异常反馈,能够快速定位服务以及服务内在代码存在的问题。 管控管控依赖于量化采集的聚合数据。管控允许运维人员聚焦某个服务单元的运行时状态,为服务设定一定的控制策略,从而保证服务稳定可靠的运行。例如熔断策略,负载策略,流量控制,权限控制等。 规范规范更多针对服务通信而言,例如通信协议规范,无论针对哪种协议,例如http,tcp,rpc等都能够提供相应的检测手段。与此同时,规范也能够清晰定义服务名称和管控策略,使得服务在不同环境之间进行迁移的时候,依旧平稳可靠。 综上所述,在服务单元遵循一定规范标准的前提下,基于服务单元数据量化、服务调用跟踪以及服务策略管控的方式,才能构建出符合要求的服务治理平台。 接下来,我们从纵深的角度考虑构建服务治理平台过程中涉及的技术理论基础。服务治理之所以困难,原因在于构建业务系统采用的技术栈成多元化的方式存在。从目前行业内采用的技术而言可以划分为三大学派:代码集成、agent探针、流量劫持。 代码集成代码集成往往需要业务开发人员的支持,在业务系统中嵌入数据采集代码,用来采集服务运行时服务产生的各种业务指标及性能指标,并将数据传输到云端治理平台。平台依据数据信息,通过配置动态下发,从而影响业务响应动态,完成服务治理功能。 优点:治理深入,端到端监控缺点:维护繁琐,语言版本众多,影响业务性能 Agent探针Agent探针是对代码集成的进一步提炼。Agent探针将需要集成的监控代码,高度提取、抽象、封装成可以独立集成的SDK,并且以“弱旁路”的方式与代码集成在一起,从而完成数据采集工作。云端治理平台,同样以采集的数据信息作为治理策略制定的依据,下发各种治理策略,从而达到服务治理功能。 优点:治理深入,端到端监控缺点:语言版本众多,影响业务性能 流量劫持流量劫持与前两者相比,与代码集成不同。它从网络通信作为切入点,以proxy的方式,代理业务单元所有的IN/OUT流量,并且proxy内部可以对请求数据进行一定的策略控制。从而完成服务通信的治理功能。 优点:无关语言差异性,维护简单缺点:治理略浅,影响业务性能 综上所述,目前服务治理的技术栈或多或少都存在一些缺陷,在构建服务治理平台时往往需要采用结合的方式,才能做到物尽其才。 03 “百家争鸣”,成就未来 竞争成就未来。 从目前行业发展来看,微服务奠定了服务构建的基础方式,容器引擎以及编排技术解决了服务编排上线的困惑,下一个“兵家必争”的场景必将在服务治理。那目前行业内又有哪些项目聚焦在服务治理领域? SpringCloudSpringCloud作为Spring社区的重要布局之一,在微服务落地伊始就逐渐发力,当下已经成为Java体系下微服务框架的代名词,SpringCloud 以 Netfilx 全家桶作为初始化基础,为开发人员提供业务单元服务支撑框架的同时,也开发出一系列的服务治理SDK,供开发人员选用。在微服务发展背景下,SpringCloud可谓如日中天。 DubboDubbo原为阿里巴巴开源的 rpc 远程调用框架,初始设计初衷在于解决以 rpc 协议为标准的远程服务调用问题,随着阿里巴巴重启Dubbo,其也开始在服务治理领域发力,成为很多以rpc协议作为通信基础系统平台的首选。粗略而言,Dubbo和SpringCloud已成为Java体系下的服务治理“双枪”。 gRPCgRPC与Dubbo类似,最初是由Google开源的一款远程服务调用框架。gRPC凭借HTTP/2和 RrotoBuf 服务定义方式以及多语言支持的特性,加之其易于定制与开发,能够方面开发人员进行快速扩展和灵活发挥,从而也成为众多用户的选择之一。 Service MeshService Mesh的出现不在于它实现了多少功能,而是它彻底把业务单元与业务支撑体系分离,完整贯彻了“术业有专攻”的思想理念。它允许业务人员聚焦业务实现,不再关心服务治理相关的内容。通过与容器技术结合,下沉至基础设施,从通信协议的角度彻底接管业务通信交互过程,可谓微服务治理领域的后起之秀。 总而言之,服务治理的本质是针对业务与应用产生价值的收敛与反馈,只有不断地反馈和复盘才能构建出稳定、高效的应用形态

June 19, 2019 · 1 min · jiezi

spring-statemachine的企业可用级开发指南4多种状态机共存

在上一章的例子中,我们实现了多个状态机并存执行,不同的订单有各自的状态机运行,但只有一种状态机,这显然不能满足实际业务的要求,比如我就遇到了订单流程和公文审批流程在同一个项目的情况,所以我们这一章讲怎么让多种状态机共存。 我们先把上一章的例子状态机再复习一下,这是个订单状态机,流程图如下:![](https://oscimg.oschina.net/oscnet/e60cfa4b1956ed1863632b34b7f0d7a60ff.jpg)定义这个状态机我们用到了OrderEvents,OrderStates来表达状态(states)和事件(events),用OrderStateMachineBuilder来描述初始状态和状态变化流程,用OrderEventConfig来描述这个流程和状态变化过程中需要做的业务。 现在我们再弄一个新的状态机流程,表单状态机,流程图如下: 为此,我们同样配套了和订单状态机一样的表单四件套,events,states,StateMachineBuilder和eventConfig。 public enum FormStates { BLANK_FORM, // 空白表单FULL_FORM, // 填写完表单CONFIRM_FORM, // 校验表单SUCCESS_FORM// 成功表单} public enum FormEvents { WRITE, // 填写CONFIRM, // 校验SUBMIT // 提交} import java.util.EnumSet; import org.springframework.beans.factory.BeanFactory;import org.springframework.statemachine.StateMachine;import org.springframework.statemachine.config.StateMachineBuilder;import org.springframework.stereotype.Component; /** 订单状态机构建器*/@Componentpublic class FormStateMachineBuilder { private final static String MACHINEID = "formMachine"; /** * 构建状态机 * * @param beanFactory * @return * @throws Exception */public StateMachine<FormStates, FormEvents> build(BeanFactory beanFactory) throws Exception { StateMachineBuilder.Builder<FormStates, FormEvents> builder = StateMachineBuilder.builder(); System.out.println("构建表单状态机"); builder.configureConfiguration() .withConfiguration() .machineId(MACHINEID) .beanFactory(beanFactory); builder.configureStates() .withStates() .initial(FormStates.BLANK_FORM) .states(EnumSet.allOf(FormStates.class)); builder.configureTransitions() .withExternal() .source(FormStates.BLANK_FORM).target(FormStates.FULL_FORM) .event(FormEvents.WRITE) .and() .withExternal() .source(FormStates.FULL_FORM).target(FormStates.CONFIRM_FORM) .event(FormEvents.CONFIRM) .and() .withExternal() .source(FormStates.CONFIRM_FORM).target(FormStates.SUCCESS_FORM) .event(FormEvents.SUBMIT); return builder.build(); }} ...

June 18, 2019 · 2 min · jiezi

从0手写springCloud项目组件搭建

写在前面一直在写springCloud项目,每次都是新建项目然后从零开始写配置,现在写一个尽量通用的项目,方便后续搭建框架的时候直接拿过去使用。 需要搭建的组件(模块)有: eureka(认证),zuul(网关),auth(认证),config(配置中心),user(用户),order(订单),pay(支付),feign...这边主要想涉及到的框架技术有:springcloud,springboot2,oauth2,springSecurity,liquibase,lcn(5.0.2),mybatisplus,logback,redis,mysql,swagger2,poi需要搭建、支持的技术 github,jenkins(自动发布),maven私服,nginx,redis,mysql5.7,jdk1.8,swagger2,rabbitmq一 需要搭建的组件需要搭建的组件主要有7个模块(feign会集成到具体模块),这边我回详细记录eureka,zuul,auth,config,user.因为前四者是springCloud的配置。需要详细介绍,而具体的业务逻辑代码会在具体模块,这里我将以user模块为例子详细介绍. eureka我们知道,在为服务里面,所有模块需要被注册到一个注册中心,持续的向注册中心发送心跳以保证连接的存活。而springcloud的注册中心有consul和eureka,这里我选用的是eureka.eureka的代码很简单,只需要在配置文件里面配置好注册地址与密码(可不设置,生产上强烈建议设置),并标识好自己不向自己注册,不被自己发现即可。 maven坐标: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--我是用的springboot2.1.3如果是springboot1.5.x请不用这个--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies>主类,不用做任何配置 @SpringBootApplication@EnableEurekaServerpublic class CrawlerEurekaApplication { public static void main(String[] args) { SpringApplication.run(CrawlerEurekaApplication.class, args); }}yml配置文件: spring: application: name: crawler-eurekaserver: host: http://localhost port: 9990eureka: client: fetch-registry: false register-with-eureka: false service-url: defaultZone: ${server.host}:${server.port}/eureka/ instance: prefer-ip-address: truezuul上面我们把注册中心搭建好了,访问localhost:9990就可以看到eureka的控制台。但是我们看不到一个服务注册上去了。现在我们搭建一个网关,因为在实际项目中,我们会有很多个微服务模块,而服务器只会向外暴露一个端口,其他的通过相对路径转发。这样也是为了安全和方便管理,有点nginx的感觉。网关的配置也不复杂:pom坐标: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>主类除了标识为eureka-client,还标识是网关 ...

June 18, 2019 · 1 min · jiezi

SpringBoot系列教程之基础篇一白话我的学习经历

有人说,Spring Boot的出现,让Java迎来了又一春,它是Java应用开发的颠覆者,彻底改变了Java应用开发的模式。2017年,SpringBoot闯入我的生活, 也让我迎来了又一春我开始接触SpringBoot的时候,是在2017年,是公司同事在开始学。我也网上查找了些资料,发现SpringBoot相比传统SpringMVC在xml配置上有很大的一部分优势:无繁琐的xml配置,各个组件依赖配置都自动加入等。我便也跟着疯狂地学起来。不得不发表一下心得体会:用起来很爽,很舒服。 学习过程,痛并快乐着我是一个天生爱折腾的主儿。喜欢学习新的技术和实现方案,挑战难点。然后把学习到的知识转化为自己的,并记录下来,整理输出。有很多也跟我一样吧。 学习springboot的时间虽过了很久,但我仍旧感觉得到当初学习SpringBoot的那股劲。 现在学习技术的途径有很多,可以看视屏教程,看博客,看码云和Github都可以呢。 学习过程中,我也总是会遇到各种问题,或者不明白的知识点,也就是知识盲区,我会怎么做呢?根据这个知识点,提炼出“关键字”去百度或者谷歌搜索,对知识点先有一个大体的了解。但是要从浩瀚的资料中,筛选出有用的资料,那还真得有一双敏感的慧眼吧。我在搜索资料的时候,心里面就会去评估这份资料: 第一层:看到不符合的,直接pass掉。第二层:比较符合的。收集下来,再寻找相似的,方便后面做对比。第三层:直接符合的。那就是实践。看是否能正确解决。并做好记录。不断试错是一种态度。也正是这样的思考方式,解决了我遇到的很多问题。 在学习SpringBoot的过程中,除了基础知识点的积累,我少不了去找许多开源项目案例研究学习,折腾各种环境部署,并从中找到我需要的那部分,然后运用到自己的项目中。我很感谢有前辈们的探索和分享。 我当时的目标很简单,就一个----学好SpringBoot,然后能成功整合各个项目,并简单的用起来。 只是学会用的话,如果有SpringMVC的使用经验的话,上手是分分钟的。 当时,我的目的也很单纯,就是学会用,其他也没多想。我开始简单地搭建了三层架构,然后慢慢开始整合相关组件,实现功能需求。 就这么简单的目的,我什么都不想去实现它就可以了。然而会有很多人,还没开始去做,就开始打退堂鼓。从心里面就已经告知自己:“我不会,我不行,没有大佬带。”,就这样,每天活在痛苦和焦虑中。 有些路必须得自己去走,才能知道沿途的风景是多么的迷人。我曾经也很想会有大佬带,学什么会什么。但对于我们这样的无名小卒,菜鸟小白,谁又会去关心呢。只能啃书,啃视频。有问题也不知道该如何解决。 痛苦在所难免,但如果有我陪着,你是否不会感觉到孤单。我是一天不写代码就剁手的程序猿。遇到的问题,也尽可能的去一起解决,减轻学习上的痛苦。 我当时大概学习了一两周,就开始上手,整合项目,直接开干。在项目中去夯实基础。 学习完了,就得实战。不说了,直接干它一个商城!其过程可谓是艰辛痛苦,那可谓是网上搜罗各种资料博客,github上找Demo项目学习,也算是很艰辛的一段学习历程。 我在的公司是个小公司,但我当时主要负责聚合支付类项目的开发,一想到互联网的项目,应该使用的是比较新的技术开发,终于可以涨姿势了。当时就我一个人接手,我还很高兴,终于能挺起腰杆,撸起袖子,大干一场。但当我真正去接手的时候,我不敢相信自己的眼睛,项目是用servlet+jsp实现,还是几个研究生实习开发的。现在都二十一世纪了好吧,还是互联网项目。咋就没看出一点互联网项目的气息。 收了,吐槽结束。 也许正因为是在这样的环境下,让我有机会去把所学的给施展开来。当时,我一边用原来的技术开发着原有的功能,一边在谋划用SpringBoot新框架的搭建和实现。 我很喜欢当时带我项目的老师说的一句话:用你最熟悉的语言开发。 我深信不疑。 后来,整个的搭建思路,前后端的整合过程,百分之90是我独立完成的。我也很成功的将原有的旧项目V1.0,迁移到我新搭建的SpringBoot项目中,并按照规范开发,就基本上完成SpringBoot单体应用V2.0整合。后面,由于有其他需求,又进行了SpringBoot+Dubbo的微服务搭建V3.0。 正因为有了这样的经历,我知道这样的经验很宝贵,也很来之不易。当然也有我很多没有考虑到的,还需继续学习。 遗憾的是,当初没有做好笔记,光顾着自己爽了。现在也只能靠着自己残缺的记忆。 不遗憾的是,我依然还有心去做一件我值得去做的事----那就是将零散的知识点,躺过的坑,能总结分享,有机地形成一个个系列。 这也是我现在准备去做的事情。 曾经的我也开始过,但后来没有写下去,因为自己的口才和知识面不够,没有多少的落地经验,自然也写不出,即使写得出,也写不好,写不清楚。 相比之前的我,现在的我,年岁长了,经验长了,学到的和看到的多了。也写了几万的文字。也更有底气去做这件事情,相信可以写得更好。 于你,可以跟我一起,学习SpringBoot,并能真正的从基础入门到独自搭建属于自己的框架,为自己增添技术实力, 而且掌握大小公司里的开发技巧,工作习惯。 于我,可以在写教程中,反思自我,争取做得更好。也可能会有更好,更有趣的想法在其中产生。

June 18, 2019 · 1 min · jiezi

全栈之路JAVA基础课程七AJAX原理剖析20190617v10

欢迎进入JAVA基础课程 博客地址:https://blog.csdn.net/houjiyu...本系列文章将主要针对JAVA一些基础知识点进行讲解,为平时归纳所总结,不管是刚接触JAVA开发菜鸟还是业界资深人士,都希望对广大同行带来一些帮助。若有问题请及时留言或加QQ:243042162。 寄语:"不深思而得者,其得易失",这句话告诫我们在学习原理的同时,要在深入思考上下功夫,要在融会贯通上下功夫,要做到既知其言又知其义、既知其然有知其所以然,要在知行合一上下功夫,要做到学以致用、用有所成。概述本文借鉴网络上各技术博客,抽取出一些非常实用的解析图,便于读者融会贯通。借鉴图片仅用于学习分享,请尊重原创作者,勿用于商用。 什么是AJAXAsynchronous JavaScript and XML(异步的 JavaScript 和 XML)Ajax是一种用于创建快速动态网页的技术与服务器进行少量数据交换,实现网页异步和局部刷新原理剖析Ajax 的原理简单来说通过 XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后用 javascript 来操作 DOM 而更新页面。这其中最关键的一步就是从服务器获得请求数据。 基本步骤: (1) 创建XMLHttpRequest对象,也就是创建一个异步调用对象。 (2) 创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息。 (3) 设置响应HTTP请求状态变化的函数。 (4) 发送HTTP请求。 (5) 获取异步调用返回的数据。 (6) 使用JavaScript和DOM实现局部刷新。原理图 Ajax核心:XMLHttpRequest对象属性readyState:请求状态,开始请求时值为0直到请求完成这个值增长到4responseText:目前为止接收到的响应体,readyState<3此属性为空字符串,=3为当前响应体,=4则为完整响应体responseXML:服务器端相应,解析为xml并作为Document对象返回status:服务器端返回的状态码,=200成功,=404表示“Not Found”statusText:用名称表示的服务器端返回状态,对于“OK”为200,“Not Found”为400 方法 setRequestHeader():向一个打开但是未发生的请求设置头信息open():初始化请求参数但是不发送send():发送Http请求abort():取消当前相应getAllResponseHeaders():把http相应头作为未解析的字符串返回getResponseHeader():返回http相应头的值 事件句柄onreadystatechange:每次readyState改变时调用该事件句柄,但是当readyState=3有可能调用多次 状态图 状态码 1. readyState0:初始化,XMLHttpRequest对象还没有完成初始化1:载入,XMLHttpRequest对象开始发送请求2:载入完成,XMLHttpRequest对象的请求发送完成3:解析,XMLHttpRequest对象开始读取服务器的响应4:完成,XMLHttpRequest对象读取服务器响应结束2. status1xx:信息响应类,表示接收到请求并且继续处理2xx:处理成功响应类,表示动作被成功接收、理解和接受3xx:重定向响应类,为了完成指定的动作,必须接受进一步处理4xx:客户端错误,客户请求包含语法错误或者是不能正确执行5xx:服务端错误,服务器不能正确执行一个正确的请求100——客户必须继续发出请求101——客户要求服务器根据请求转换HTTP协议版本200——交易成功201——提示知道新文件的URL202——接受和处理、但处理未完成203——返回信息不确定或不完整204——请求收到,但返回信息为空205——服务器完成了请求,用户代理必须复位当前已经浏览过的文件206——服务器已经完成了部分用户的GET请求300——请求的资源可在多处得到301——删除请求数据302——在其他地址发现了请求数据303——建议客户访问其他URL或访问方式304——客户端已经执行了GET,但文件未变化305——请求的资源必须从服务器指定的地址得到306——前一版本HTTP中使用的代码,现行版本中不再使用307——申明请求的资源临时性删除400——错误请求,如语法错误401——请求授权失败402——保留有效ChargeTo头响应403——请求不允许404——没有发现文件、查询或URl405——用户在Request-Line字段定义的方法不允许406——根据用户发送的Accept拖,请求资源不可访问407——类似401,用户必须首先在代理服务器上得到授权408——客户端没有在用户指定的饿时间内完成请求409——对当前资源状态,请求不能完成410——服务器上不再有此资源且无进一步的参考地址411——服务器拒绝用户定义的Content-Length属性请求412——一个或多个请求头字段在当前请求中错误413——请求的资源大于服务器允许的大小414——请求的资源URL长于服务器允许的长度415——请求资源不支持请求项目格式416——请求中包含Range请求头字段,在当前请求资源范围内没有range指示值,请求也不包含If-Range请求头字段417——服务器不满足请求Expect头字段指定的期望值,如果是代理服务器,可能是下一级服务器不能满足请求500——服务器产生内部错误501——服务器不支持请求的函数502——服务器暂时不可用,有时是为了防止发生系统过载503——服务器过载或暂停维修504——关口过载,服务器使用另一个关口或服务来响应用户,等待时间设定值较长505——服务器不支持或拒绝支请求头中指定的HTTP版本 代码实现前台代码 var xmlHttp = new XMLHttpRequest();xmlHttp.open("post","/loginTest",true);xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;");xmlHttp.send("username=rain&password=123456");xmlHttp.onreadystatechange = function (){ var state = xmlHttp.readyState; var status = xmlHttp.status; if(state == 4 && status == 200){ var data=xmlHttp.responseText; console.log("1."+data); }}xmlHttp.onload=function () { console.log("2."+xmlHttp.responseText);}后台代码 ...

June 17, 2019 · 1 min · jiezi

ssl品牌证书那个好

首先要挑选一个靠谱的代理商,由于现在市场上许多公司都在做这块,不管是价格仍是质量都比较紊乱,所以挑选一家好的代理商非常重要。合信ssl证书是咱们运用以后,性价比最高的一款产品。合信ssl证书,是国内有名的ssl证书代理商,各种国内外品牌都有协作,如:OCKERT、GLOBALSIGN、DIGICERT、GEOTRUST等国内外知名品牌。证书类型也是能够满足多种需求。dvssl证书、ovssl证书、以及evssl证书都有。 合信SSL为您供给多种廉价的SSL证书,更供给免费服务器证书请求,咱们的HTTPS证书能够兼容各大干流浏览器,是您值得信赖伙伴。 现在ssl关于网络安全的重要性,大家都是越来越了解了,那么该怎样获取这样的ssl证书呢?合信ssl证书 。 你能够自己生成自签的证书,自签名SSL证书不被浏览器信赖,并且存在安全危险,所以不常被承受。所以还是挑选向ca机构请求的证书会稳定些。

June 17, 2019 · 1 min · jiezi

听过了API咱们来看看SPI是什么

引语平时API倒是听得很多?SPI又是啥.别急我们来先看看面向接口编程的调用关系,来了解一下,API和SPI的相似和不同之处。 SPI理解先来一段官话的介绍:SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.(听了一脸懵逼)好的,我们结合图片来理解一下。    简单的来说分为调用方,接口,服务方.接口就是协议,契约,可以调用方定义,也可以由服务方定义,也就是接口是可以位于调用方的包或者服务方的包.1.接口的定义和实现都在服务方的时候,仅暴露出接口给调用方使用的时候,我们称为API;2.接口的定义在调用方的时候(实现在服务方),我们给它也取个名字--SPI。应该还比较好理解吧? SPI的使用场景SPI在框架中其实有很多广泛的应用,这里列举几个例子:1.Mysql驱动的选择driverManager根据配置来确定要使用的驱动; 2.dubbo框架中的扩展机制(dubbo官网链接) 使用实例看完上面的简介和SPI在框架中的应用,想必对SPI在读者的大脑中已经产生了一个雏形,talk is cheap!show me the code.说了这么多,我们具体写一个简单的例子来看看效果,验证一下SPI. 1.首先定义一个接口,忍者服务接口 public interface NinjaService { void performTask();}2.接下来写两个实现类,ForbearanceServiceImpl(上忍),ShinobuServiceImpl(下忍) public class ForbearanceServiceImpl implements NinjaService { @Override public void performTask() { System.out.println("上忍在执行A级任务"); }}public class ShinobuServiceImpl implements NinjaService { @Override public void performTask() { System.out.println("下忍在执行D级任务"); }}3.接下来我们在main/resources/下创建META-INF/services目录,并且在services目录下创建一个com.scott.java.task.spi.NinjaService(忍者服务类的全限定名)的文件. 4.创建一个Client场景类来调用看看结果很完美的调用了两个实现类的performTask()方法. 5.最后贴一下目录结构附上一波代码例子的地址,在spi里面,git链接; SPI源码简单分析1.先看下核心类ServiceLoader的定义和属性 // 继承了Iterable类 遍历的时候使用public final class ServiceLoader<S> implements Iterable<S>{ // 这就是为啥需要在META-INF/services/目录下创建服务类的文件 private static final String PREFIX = "META-INF/services/"; // 被加载的服务 private final Class<S> service; // 类加载器 private final ClassLoader loader; // 访问控制类 private final AccessControlContext acc; // 实现类的缓存 根据初始化的顺序 也就是在/services/文件中的定义顺序来定义的加载顺序 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 懒加载iterator private LazyIterator lookupIterator;2.然后从client开始,然后依次debug进去 ...

June 16, 2019 · 4 min · jiezi

缘起-Dubbo-讲讲-Spring-XML-Schema-扩展机制

背景在 Dubbo 中,可以使用 XML 配置相关信息,也可以用来引入服务或者导出服务。配置完成,启动工程,Spring 会读取配置文件,生成注入 相关 Bean。那 Dubbo 如何实现自定义 XML 被 Spring 加载读取? Spring XML Schema 扩展机制。从 Spring 2.0 开始,Spring 开始提供了一种基于 XML Schema 格式扩展机制,用于定义和配置 bean。 Spring XML Schema 扩展机制实现 Spring XML Schema 扩展,其实非常简单,只需要完成下面四步。 创建 XML Schema 文件,由于该文件后缀名为 xsd,下面称为 XSD 文件。编写实现一个或多个 BeanDefinitionParser 。编写NamespaceHandler实现类。注册 NamespaceHandler 以及 XSD 文件。我们按照以上步骤,最终完整 Spring 解析如下配置。 <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:demo="http://www.test.com/demo" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.test.com/demo http://www.test.com/demo/demo.xsd"> <demo:application name="test" id="test"/></beans>创建 XSD 文件XSD 文件,主要用来定义 XML 格式,用来验证 XML 合法性。在 IDE 中,导入 XSD 文件,编辑 XML 文件可以获得相关提示。 ...

June 16, 2019 · 2 min · jiezi

6如何获取配置中心的配置

在《配置中心》这一篇博文里学习了如何git获取配置文件。大概的流程可以用下图来概括。 《配置中心》这篇博文说的是Config Server,本篇将和大家看看如何编写一个Config Client从Config Server获取配置。1、 先在仓库中创建如下配置文件(具体参考下面地址) https://gitee.com/hjj520/spring-cloud-2.x/tree/master/config-repos/sc-config-client2、 创建maven项目sc-config-client,对应的pom.xml如下 <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>spring-cloud</groupId> <artifactId>sc-config-client</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sc-config-client</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> <version>2.0.1.RELEASE</version> </dependency> <!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> <version>2.0.1.RELEASE</version> </dependency> --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.0.1.RELEASE</version> </dependency> </dependencies></project>其中:spring-cloud-starter-config与spring-cloud-config-client可以二选一,但是根据选择的依赖不同对应的配置文件有些许不一样。spring-cloud-starter-config已经包含spring-cloud-config-client,所以选择依赖spring-cloud-starter-config。 3、 创建配置文件bootstrap.yml #服务端口server: port: 8200eureka: client: serviceUrl: defaultZone: http://localhost:5001/eureka/ spring: application: name: sc-config-client cloud: config: label: master # 配置文件所在分支 #uri: http://127.0.0.1:8100/ #配置服务中心 profile: dev # dev根据具体情况来修改 discovery: serviceId: sc-config-server #配置服务实例名称 enabled: true #开启配置服务发现备注:sc-config-server为配置服务实例名称,对应sc-config-server项目的bootstrap.yml配置文件的如下配置项 ...

June 16, 2019 · 1 min · jiezi

7服务发现服务消费者Ribbon

在《服务注册&服务提供者》这一篇可能学习了这么开发一个服务提供者,在生成上服务提供者通常是部署在内网上,即是服务提供者所在的服务器是与互联网完全隔离的。这篇说下服务发现(服务消费者),通常服务消费者是部署在与互联网联通的服务器上,提供restful接口给H5和App调用。 服务消费者 :Service Consumer本质上也是一个Eureka Client。它启动后会从Eureka Server上获取所有实例的注册信息,包括IP地址、端口等,并缓存到本地。这些信息默认每30秒更新一次。如果与Eureka Server通信中断或者Eureka Server宕机,Service Consumer仍然可以通过本地缓存与Service Provider通信。1、 新建项目sc-eureka-client-consumer-ribbon,对用的pom.xml文件 <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>spring-cloud</groupId> <artifactId>sc-eureka-client-consumer-ribbon</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sc-eureka-client-consumer-ribbon</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.0.1.RELEASE</version> </dependency> <!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> <version>1.4.5.RELEASE</version> </dependency> --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> <version>2.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.0.4.RELEASE</version> </dependency> </dependencies></project>备注:spring cloud 2.x推荐使用spring-cloud-starter-netflix-ribbon,而且spring-cloud-starter-ribbon已经被标志为过期 ...

June 16, 2019 · 1 min · jiezi

Spring-里那么多种-CORS-的配置方式到底有什么区别

作为一个后端开发,我们经常遇到的一个问题就是需要配置 CORS,好让我们的前端能够访问到我们的 API,并且不让其他人访问。而在 Spring 中,我们见过很多种 CORS 的配置,很多资料都只是告诉我们可以这样配置、可以那样配置,但是这些配置有什么区别? CORS 是什么首先我们要明确,CORS 是什么,以及规范是如何要求的。这里只是梳理一下流程,具体的规范请看 这里。 CORS 全称是 Cross-Origin Resource Sharing,直译过来就是跨域资源共享。要理解这个概念就需要知道域、资源和同源策略这三个概念。 域,指的是一个站点,由 protocal、host 和 port 三部分组成,其中 host 可以是域名,也可以是 ip ;port 如果没有指明,则是使用 protocal 的默认端口资源,是指一个 URL 对应的内容,可以是一张图片、一种字体、一段 HTML 代码、一份 JSON 数据等等任何形式的任何内容同源策略,指的是为了防止 XSS,浏览器、客户端应该仅请求与当前页面来自同一个域的资源,请求其他域的资源需要通过验证。了解了这三个概念,我们就能理解为什么有 CORS 规范了:从站点 A 请求站点 B 的资源的时候,由于浏览器的同源策略的影响,这样的跨域请求将被禁止发送;为了让跨域请求能够正常发送,我们需要一套机制在不破坏同源策略的安全性的情况下、允许跨域请求正常发送,这样的机制就是 CORS。 预检请求在 CORS 中,定义了一种预检请求,即 preflight request,当实际请求不是一个 简单请求 时,会发起一次预检请求。预检请求是针对实际请求的 URL 发起一次 OPTIONS 请求,并带上下面三个 headers : Origin:值为当前页面所在的域,用于告诉服务器当前请求的域。如果没有这个 header,服务器将不会进行 CORS 验证。Access-Control-Request-Method:值为实际请求将会使用的方法Access-Control-Request-Headers:值为实际请求将会使用的 header 集合如果服务器端 CORS 验证失败,则会返回客户端错误,即 4xx 的状态码。 否则,将会请求成功,返回 200 的状态码,并带上下面这些 headers: ...

June 15, 2019 · 6 min · jiezi

SpringBoot-中的Flyway配置

在我们的Springboot项目——studentsystem中使用flyway进行数据库版本控制。我们的springboot项目采用gradle管理。 studentsystem项目地址:https://github.com/zjgirl/Spr...flyway配置参考地址:https://blog.waterstrong.me/f... 配置过程很简单,只需要在build.gradle中添加配置即可: //引入配件plugins { id"org.flywaydb.flyway"version"4.0.3"}//配置flyway propertiesflyway { url = jdbc:h2:./.tmp/testdb user = sa password =}//添加mysql依赖dependencies { compilegroup:'mysql',name:'mysql-connector-java',version:'8.0.11'}flyway默认执行的sql脚本路径是resources/db/migration,.sql脚本以Vx__xxx_xxx_xxx.sql的方式命名。配置完成后,执行./gradlew tasks可以看到可用的命令,执行./gradlew flywayMigrate可以执行sql脚本。 注意:按理来说,build项目应该会自动执行flyway,但是我们这里竟然不能自动执行!!!不知道什么原因。。。。。还有,它无法在非空数据库中迁移表,即使在application.properties中设置了spring.flyway.baseline-on-migrate=true。很奇怪!!! 另外,在配置过程中遇到了一些奇葩的错: 1、mysql数据库的密码设置的有问题,报错caching_sha2_password;原因是在mysql8之前的版本使用的密码加密规则是mysql_native_password,但是在mysql8则是caching_sha2_password,可以重设密码解决: create user 'root'@'localhost' identified with mysql_native_password by 'your password';FLUSH PRIVILEGES;2、‘query_cache_size’的错误:这个是由于依赖的mysql版本太老了,mysql-connector-java的版本还是6.0.6,需要升级版本到8.0.11 ,这个报错就不存在了。

June 15, 2019 · 1 min · jiezi

springbootquartzjsoupkafka

最近在学习springboot,光看官方文档比较枯燥,于是想用一个项目把各种框架和技术串联起来,思来想去觉得爬虫是一个不错的idea。 大概思路是这样:固定频率去爬取新浪财经的头条新闻,爬到的标题和链接以json方式推到kafka的topic中,再通过ELK消费,在kibana中查看。 首先通过Spring Initializr下载一个demo工程,选择我们需要的依赖包,jsoup的包需要额外添加。 引入到idea中,修改pom文件,加入jsoup依赖,由于习惯了使用jetty作为web容器,所以把tomcat踢掉,引入jetty的依赖,为了方便处理json,引入fastjson依赖。 quartz的使用可以参考官网文档,这里我们通过mysql来持久化定时任务相关信息,涉及到mysql,顺便把mybatis和druid依赖也一起引入。 quartz相关表的sql在官网的demo里可以找到,这里就略过,假设表已建好。springboot提供了很好的quartz支持,自动配置了一个Scheduler,直接Autowired就可以使用,我们新建一个Service,在系统启动的时候启动爬取新闻的定时任务,代码如下: 假设每30分钟爬取一次,我们还需要一个Job实现类,来完成具体的爬取任务,也可以通过不同的job来分别爬取,这里就不展开了。Job实现类如下: 在爬网页之前先看一下每个页面的结构,以新浪财经为例,地址:https://finance.sina.com.cn/,查看页面结构可以发现,我们需要的头条新闻都在“m-hdline”这个class的a标签下,jsoup的使用比较简单,根据需要查找对应的文档就可以了,直接上代码: 接下来需要将获取到的数据发到kafka的topic中,我的win10是家庭版,天生不带docker,我又懒得折腾toolbox,于是搞了个自带的ubuntu虚拟机,直接下载kafka安装,然后创建一个topic:financenews。这时候可以将kafka的信息配置在我们的工程中,如下: springboot也贴心的为我们准备了KafkaTemplate,Autowired即可。这里我们还没有搭建好elk,可以使用直接监听定时任务发送到的topic中的消息是否正常。 最后在job中添加发送消息到kafka的处理: 代码到这里基本差不多了,下面我们启动应用看看效果: 成功。

June 14, 2019 · 1 min · jiezi

spring-statemachine的企业可用级开发指南3多个状态机共存

1、多个状态机的搞法在实际的企业应用中,基本不可能只有一个状态机流程在跑,比如订单,肯定是很多个订单在运行,每个订单都有自己的订单状态机流程,但上一章的例子,大家可以试一下,当执行到一个状态时,再次刷新页面,不会有任何日志出现,当一个状态流程执行到某个状态,再次执行这个状态,是不会有任何输出的,因为状态机的机制是只有在状态切换的时候才会事件(event)触发,所以我们这一章讲多个状态机的并行执行。 首先,靠上一章例子里面的手打定制一个StateMachineConfig的做法,就只能是有一个状态机流程制霸整个项目,这种霸道的做法肯定是不行啦,要想多个状态机流程并行,那么就要请builder出场了,看代码: private final static String MACHINEID = "orderMachine";public StateMachine<OrderStates, OrderEvents> build(BeanFactory beanFactory) throws Exception { StateMachineBuilder.Builder<OrderStates, OrderEvents> builder = StateMachineBuilder.builder(); System.out.println("构建订单状态机"); builder.configureConfiguration() .withConfiguration() .machineId(MACHINEID) .beanFactory(beanFactory); builder.configureStates() .withStates() .initial(OrderStates.UNPAID) .states(EnumSet.allOf(OrderStates.class)); builder.configureTransitions() .withExternal() .source(OrderStates.UNPAID).target(OrderStates.WAITING_FOR_RECEIVE) .event(OrderEvents.PAY).action(action()) .and() .withExternal() .source(OrderStates.WAITING_FOR_RECEIVE).target(OrderStates.DONE) .event(OrderEvents.RECEIVE); return builder.build(); }有没有似曾相识的感觉,里面描述订单状态机的初始状态,状态机的流程代码和StateMachineConfig几乎是一样的,但是都配置在StateMachineBuilder里面 StateMachineBuilder.Builder<OrderStates, OrderEvents> builder = StateMachineBuilder.builder();这是完整的builder类代码: import java.util.EnumSet;import org.springframework.beans.factory.BeanFactory;import org.springframework.context.annotation.Bean;import org.springframework.statemachine.StateContext;import org.springframework.statemachine.StateMachine;import org.springframework.statemachine.action.Action;import org.springframework.statemachine.config.StateMachineBuilder;import org.springframework.stereotype.Component;@Componentpublic class OrderStateMachineBuilder { private final static String MACHINEID = "orderMachine"; /** * 构建状态机 * * @param beanFactory * @return * @throws Exception */ public StateMachine<OrderStates, OrderEvents> build(BeanFactory beanFactory) throws Exception { StateMachineBuilder.Builder<OrderStates, OrderEvents> builder = StateMachineBuilder.builder(); System.out.println("构建订单状态机"); builder.configureConfiguration() .withConfiguration() .machineId(MACHINEID) .beanFactory(beanFactory); builder.configureStates() .withStates() .initial(OrderStates.UNPAID) .states(EnumSet.allOf(OrderStates.class)); builder.configureTransitions() .withExternal() .source(OrderStates.UNPAID).target(OrderStates.WAITING_FOR_RECEIVE) .event(OrderEvents.PAY).action(action()) .and() .withExternal() .source(OrderStates.WAITING_FOR_RECEIVE).target(OrderStates.DONE) .event(OrderEvents.RECEIVE); return builder.build(); } @Bean public Action<OrderStates, OrderEvents> action() { return new Action<OrderStates, OrderEvents>() { @Override public void execute(StateContext<OrderStates, OrderEvents> context) { System.out.println(context); } }; } }在完整的代码里面我们看到有个东西没讲,那就是MACHINEID,在builder的配置代码里面,有这么一段 ...

June 13, 2019 · 2 min · jiezi

spring-statemachine的企业可用级开发指南1说些废话

1、背景在我打算学习spring statemachine的时候,我几乎看过了所有网上的中文教程,基本上都处于浅尝辄止的阶段,有几篇讲的比较深入的,都只是堆代码,具体用在什么地方,都语焉不详,我打算把我一路摸索的过程记录下来,方便大家能继续前行。 2、spring statemachine是干啥用的spirng statemachine是干啥用的,这个其实是个问题来的,但很多的教程的问题也在这,一上来就是告诉你有这么个玩意,然后就是开始怎么安装,怎么运行,功能有哪些,特性列起来,但就不告诉你这个东西能干啥,所以我打算在把spring statemachine跑起来之前,先说下spring statemachin能干啥。让我们先看下状态机的概念。 有限状态机(英语:finite-state machine,缩写:FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。应用FSM模型可以帮助对象生命周期的状态的顺序以及导致状态变化的事件进行管理。将状态和事件控制从不同的业务Service方法的if else中抽离出来。FSM的应用范围很广,对于有复杂状态流,扩展性要求比较高的场景都可以使用该模型。下面是状态机模型中的4个要素,即现态、条件、动作、次态。 现态:是指当前所处的状态。条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。 这是状态机的定义,这个我不打算多说,因为概念性的东西,诸位请自行搜索,那么在实际应用中,状态机会应用在什么地方呢?不知道大家怎么想,我在接触到状态机的时候,第一时间想到的就是订单和审批公文,这是我在工作中实际遇到的场景,所以我学习spring statemachine的主要动力也是为了能处理这两种情况。因为订单和审批公文都有很多的流程,每个流程都会产生状态的变化,而且流程是这种业务的主轴,其他都是围绕这个流程和状态变化来考虑的,所以看起来蛮适合用状态机来做。 那是不是一定要用状态机来做呢,这可不一定,因为在没有状态机之前的订单流程大家也能实现,这世界缺了谁都正常运转,所以状态机不是必须的,在可以用状态机解决这种流程和状态变化的场景下,为什么要用,这是另外一个问题,因为可以用和决定用之间,还有个问题,那就是用这个有啥好处呢? 3、spring statemachine的好处在做软件项目的时候,我们会以各种各样的角度看一个项目,比如OOP,万物皆对象,一个订单就是一个对象,所谓的状态变化,无非就是订单这个对象的变量在不停的变化,变化的过程就是对象的方法;再比如数据库,万物无非CRUD的组合,订单不过就是增删改查,数据库原子操作的组合罢了。这些角度对吗?可以说都对,因为用这些角度都有人做出了项目,但有些角度对项目的理解会比较方便,有些角度就比较费劲,比如订单,如果是订单的生成查看,用CRUD就是很适合,因为这个角度很契合;如果是订单和商品的关系,订单和其他业务的关联,OOP的角度就很适合,因为用封装、多态的视角比较容易看清楚这类业务和他们之间的边界,方便划分各自的功能。而状态机的角度,就是以状态变化和流程运转为角度切入看问题,所以对于流程性为主轴的项目就很适合,所以是不是用spring statemachine,衡量的标准就是,这个项目的主轴,或者场景是不是一个流程性、状态变化为主的项目,如果是,就可以考虑用spring statemachine了。 这个问题其实还没回答完,上面说的其实是在什么情况下用spring statemachine,但还没回答好处在哪里。要回答这个问题,要从软件项目的一些特质说起。上面说了,软件项目其实有很多的办法完成,手段很多,完成的情况也各有优劣,那怎么判断用什么办法好呢,我觉得答案就是看项目主要解决的问题是什么。软件项目既不是程序员练手的工具,也不是老板用来蒙钱的门面,它是用来解决问题的,所以能清晰的表达问题,解决问题的手段就是好的技术手段,所以,如果一种技术手段很清晰的表达了软件项目的问题和解决方法,那么这就是这个技术最大的好处,而状态机对于流程性、状态变化的场景,它就是一个清晰的表达方式,这就是它的好处。 这样说可能有些朋友会不以为然,一个技术框架的好处难道不是功能强大,使用方便,性能卓越这些东西吗。我的理解是上面说的这几点都很重要,但都是次一级的问题,能清晰的表达问题和解决问题的,才是核心的好处。在《人月神话》的《没有银弹》这篇文章里面就提到过软件特性的根本困难和次要困难,而对问题的表达和解决其实就是对需求的准确描述和实现,这就是软件开发的根本困难,所以能够清晰表达这个的技术框架,就是解决根本困难的好工具,项目技术选型最重要的好处考量。至于功能、性能这些,都是次要问题,虽然spirng statemachine在次要问题上也并没有什么缺陷。 好了,废话时间结束,下一章,我们正式开始第一个例子,运行一个demo,请大家期待,因为这个例子真的没啥用:) 源代码地址

June 11, 2019 · 1 min · jiezi

跨域问题与SpringBoot解决方案

什么是跨域?定义:浏览器从一个域名的网页取请求另一个域名下的东西。通俗点说,浏览器直接从A域访问B域中的资源是不被允许的,如果想要访问,就需要进行一步操作,这操作就叫“跨域”。例如,你从百度的页面,点击一个按钮,请求了新浪的一个接口,这就进行了跨域。不单单只有域名不同就是跨域,域名、端口、协议其一不同就是不同的域,请求资源需要跨域。 为什么要跨域?为什么需要跨域,而不直接访问其他域下的资源呢?这是浏览器的限制,专业点说叫浏览器同源策略限制。主要是为了安全考虑。现在的安全框架,一般请求的时候header中不是都存个token嘛,你要是用这个token去正常访问A域下的东西是没问题的,然后又去访问了B域,结果阴差阳错的还带着这个token,那么B域,或者说B网站是不是就可以拿着你的token去A域下做点什么呢,这就相当危险了。所以浏览器加上了所谓的浏览器同源策略限制。但是为了我们真的需要从A域下访问B的资源(正常访问),就需要用到跨域,跨越这个限制了。 SpringBoot解决跨域问题SpringBoot可以基于Cors解决跨域问题,Cors是一种机制,告诉我们的后台,哪边(origin )来的请求可以访问服务器的数据。全局配置配置实例如下: @Configurationpublic class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowCredentials(true) .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .maxAge(3600); }}首先实现了WebMvcConfigurer 接口,WebMvcConfigurer 这个接口十分强大,里面还有很多可用的方法,在SpringBoot2.0里面可以解决WebMvcConfigurerAdapter曾经的部分任务。其中一个方法就是addCorsMappings(),是专门为开发人员解决跨域而诞生的接口。其中构造参数为CorsRegistry。 看下CorsRegistry源码,十分简单: public class CorsRegistry { private final List<CorsRegistration> registrations = new ArrayList<>(); public CorsRegistration addMapping(String pathPattern) { CorsRegistration registration = new CorsRegistration(pathPattern); this.registrations.add(registration); return registration; } protected Map<String, CorsConfiguration> getCorsConfigurations() { Map<String, CorsConfiguration> configs = new LinkedHashMap<>(this.registrations.size()); for (CorsRegistration registration : this.registrations) { configs.put(registration.getPathPattern(), registration.getCorsConfiguration()); } return configs; }} 可以看出CorsRegistry 有个属性registrations ,按道理可以根据不同的项目路径进行定制访问行为,但是我们示例直接将pathPattern 设置为 /**,也就是说已覆盖项目所有路径,只需要创建一个CorsRegistration就好。getCorsConfigurations(),这个方法是获取所有CorsConfiguration的Map集合,key值为传入路径pathPattern。 ...

June 10, 2019 · 2 min · jiezi

3配置中心

1、 当一个系统中的配置文件发生改变的时候,经常的做法是重新启动该服务,才能使得新的配置文件生效,spring cloud config可以实现微服务中的所有系统的配置文件的统一管理,而且还可以实现当配置文件发生变化的时候,系统会自动更新获取新的配置。 将配置文件放入git或者svn等服务中,通过一个Config Server服务来获取git或者svn中的配置数据,二其他服务需要配置数据时在通过Config Client从Config Server获取。2、 在git仓库新建如下图目录 具体内容查看:https://gitee.com/hjj520/spri...3、 新建maven项目sc-config-server,对应pom.xml <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>spring-cloud</groupId> <artifactId>sc-config-server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sc-config-server</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> <version>2.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <version>2.0.1.RELEASE</version> </dependency> </dependencies></project>4、 新建类ConfigServerApplication.java package sc.config.server;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.config.server.EnableConfigServer;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@SpringBootApplication@EnableConfigServer@EnableEurekaClientpublic class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); }}5、 创建bootstrap.yml文件 ...

June 9, 2019 · 1 min · jiezi

4服务注册服务提供者

1、 什么是服务提供者 服务提供者(Service Provider):是指服务的被调用方(即:为其它服务提供服务的服务);服务提供者,作为一个Eureka Client,向Eureka Server做服务注册、续约和下线等操作,注册的主要数据包括服务名、机器ip、端口号、域名等等。 从图中可以到Eureka 有两种服务实例,分别为Eureka Server和Eureka Client;而且Eureka Client又分为两种类型:Service Provider(服务提供者)和Service Consumer(服务消费者),如果学过dubbo发现这个图跟dubbo的调用关系图比较类似。2、 新建meven项目 <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>spring-cloud</groupId> <artifactId>sc-eureka-client-provider</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sc-eureka-client-provider</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!-- 说明是一个 eureka client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.0.1.RELEASE</version> </dependency> <!-- spring boot实现Java Web服务 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 把tomcat-jdbc连接池排除掉,这样spring-boot就会寻找是否有HikariCP可用 --> <exclusions> <exclusion> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jdbc</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.3</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> </dependencies></project> ...

June 9, 2019 · 1 min · jiezi

2eureka注册中心集群

1、 Eureka作为spring cloud的服务发现与注册中心,在整个的微服务体系中,处于核心位置。单机模式下的eureka服务,显然不能满足高可用的实际生产环境,这就要求配置一个能够应对各种突发情况,具有较强容灾能力的eureka服务。Eureka通过“伙伴机制”实现高可用。每一台Eureka都需要在配置中指定另外两个Eureka的地址伙伴,Eureka启动时会向自己的伙伴节点获取当前已经存在的注册表,这样在向Eureka集群中新加机器时就不需要担心注册表的不完整。2、 新建三个maven项目,分别为sc-eureka-cluster-server-node1,sc-eureka-cluster-server-node2,sc-eureka-cluster-server-node3。项目结构如下: 备注:pom.xml和EurekaServerApplication.java的内容给单机模式是一模一样的,请参考上篇文章。3、 主要看下bootstap.yml或者application.yml文件的改动sc-eureka-cluster-server-node1 spring: application: name: sc-eureka-cluster-server-node1 server: port: 5001 eureka: instance: hostname: sc-eureka-cluster-server-node1 lease-renewal-interval-in-seconds: 30 lease-expiration-duration-in-seconds: 30 prefer-ip-address: true client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://sc-eureka-cluster-server-node2:5002/eureka/,http://sc-eureka-cluster-server-node3:5003/eureka/sc-eureka-cluster-server-node2 spring: application: name: sc-eureka-cluster-server-node2 server: port: 5002 eureka: instance: hostname: sc-eureka-cluster-server-node2 lease-renewal-interval-in-seconds: 30 lease-expiration-duration-in-seconds: 30 prefer-ip-address: true client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://sc-eureka-cluster-server-node1:5001/eureka/,http://sc-eureka-cluster-server-node3:5003/eureka/sc-eureka-cluster-server-node3 spring: application: name: sc-eureka-cluster-server-node3 server: port: 5003 eureka: instance: hostname: sc-eureka-cluster-server-node3 lease-renewal-interval-in-seconds: 30 lease-expiration-duration-in-seconds: 30 prefer-ip-address: true client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://sc-eureka-cluster-server-node1:5001/eureka/,http://sc-eureka-cluster-server-node2:5001/eureka/4、 配置host文件C:WindowsSystem32driversetchosts ...

June 9, 2019 · 1 min · jiezi

Springboot2快速集成minidao持久层

Springboot2 快速集成minidao持久层这里采用springboot版本号: 2.0.4.RELEASEminidao已经提供自定义starter,集成非常简单,直接pom引入minidao-spring-boot-starter依赖即可集成步骤: 第一步: pom引入starter依赖包<dependency> <groupId>org.jeecgframework</groupId> <artifactId>minidao-spring-boot-starter</artifactId> <version>1.6.7.RELEASE</version></dependency>此starterd对应的minidao最新版本 1.6.7 默认提供了mysql的依赖。 第二步:配置minidao的配置参数 (application.properties 或者 application.yml) minidao: base-package: com.springBoot.* db-type: mysql show-sql: true第三步: 配置springjdbc所需数据源 ( application.yml) spring: datasource: url: jdbc:mysql://localhost:3306/minidao-pe username: root password: root driver-class-name: com.mysql.jdbc.Driver通过以上三步,minidao集成完毕。 参考源码下载: https://download.csdn.net/dow...技术交流群:325978980 Minidao常见配置参数说明:参数名用途默认值base-packageminidao扫描路径*db-type数据库类型,常用配置: mysql/postgresql/oracle/sqlservermysqlshow-sql是否打印sqltrueempty-interceptorminidao拦截器的bean名字空keyType是使用什么字母做关键字Map的关键字 默认值origin 即和sql保持一致,lower小写(推荐),upper 大写origin

June 7, 2019 · 1 min · jiezi

Java面试技术学习总结最后一站

一直在找这样的项目,准备作为个人项目,找工作时候的谈资:贯穿真个Java知识点的,用到ssh,或者ssm框架,这样就可以让自己对java有一个整体的、清晰的认识。 不管是OA人事管理系统,或者ERP系统,都不能很好地满足我,直到看到这样一个项目,一个很用心的自学网站,分为一个个小的学习模块,有视频教学,还有题目和答案,可以边动手边学习。天猫整站(J2EE、SSH、SSM、SpringBoot等技术实现) 天猫网站实现技术路径及效果

June 6, 2019 · 1 min · jiezi

1eureka注册中心单机

一、简介 Spring Cloud Eureka是Spring Cloud Netflix项目下的服务治理模块。而Spring Cloud Netflix项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),客户端负载均衡(Ribbon)等。 1、 新建一个maven项目:sc-eureka-server,其pom.xml配置如下: <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>spring-cloud</groupId> <artifactId>sc-eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sc-eureka-server</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> <version>1.4.5.RELEASE</version> </dependency> --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <version>2.0.1.RELEASE</version> </dependency> </dependencies></project>备注: 主要引入eureka server所需的starter<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency>Spring Cloud 1.x之前的eureka server的starter为 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> <version>1.4.5.RELEASE</version></dependency>在http://mvnrepository.com中央...,推荐使用spring-cloud-starter-netflix-eureka-server ...

June 6, 2019 · 1 min · jiezi

聊聊Spring-MVC中-handlerMapping和handlerAdapter设计思想

先上一段Spring MVC核心类DispatcherServlet中最重要的方法doDispatch源码 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // 根据当前请求获取对应的处理器映射器 mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // 根据handler类型获取对应的处理器适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }}关注代码中打中文注释的两个地方,一个获取对应handler的处理器映射器,一个获取对应handler的处理器适配器。那为什么需要这两个东西,我们直接在handler中写映射逻辑,直接通过handler来执行处理器方法难道不行吗?答案是否定的,但Spring为什么要这样做?有以下几个好处 ...

June 6, 2019 · 2 min · jiezi

WebSocket-前后端实时消息推送

要做一个通信监测方面的事情,需要实时进行前后端的的消息推送,这里不分析Ajax轮询和WebSocket的区别,网上讲的挺多的,下图是两者的通信示意图,这里只写怎么用。下图是我的一个页面简单展示 上代码前端js链接:https://pan.baidu.com/s/1gkdj... 提取码:c0q5 从上述连接下载必须的jssockjs.min.jsstomp.min.js <script src="dist/js/sockjs.min.js"></script><script src="dist/js/stomp.min.js"></script><script type="text/javascript">function connect() { var socket = new SockJS("http://127.0.0.1:7070/myWebSocket");//如果前后端分离项目需要拼接具体地址 stompClient = Stomp.over(socket); stompClient.connect({}, function(frame) { setMessageInnerHTML("连接成功!" + "\n") console.log(frame); stompClient.subscribe('/topic/ip', function(body) {//'/topic/ip'是自己定义的一个地址,可根据自己业务定 //收到后台推送的消息后进行的业务处理,根据自己的情况写 alert("来自后台的消息:"+body.body); }); }); }</script>后端使用pom.xml配置 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>配置类 import org.springframework.context.annotation.Configuration;import org.springframework.messaging.simp.config.MessageBrokerRegistry;import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;import org.springframework.web.socket.config.annotation.StompEndpointRegistry;import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;//springBoot2.0版本后使用 实现WebSocketMessageBrokerConfigurer接口;//2.0以下版本继承AbstractWebSocketMessageBrokerConfigurer 类;@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //注册一个Stomp 协议的endpoint指定URL为myWebSocket,并用.withSockJS()指定 SockJS协议。.setAllowedOrigins("*")设置跨域 registry.addEndpoint("/myWebSocket").setAllowedOrigins("*").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry config) { //配置消息代理(message broker) //将消息传回给以‘/topic’开头的客户端 config.enableSimpleBroker("/topic"); }}private SimpMessagingTemplate simpMessage;使用的时候直接用 ...

June 6, 2019 · 1 min · jiezi

15个经典的Spring面试常见问题

我自己总结的Java学习的系统知识点以及面试问题,已经开源,目前已经 41k+ Star。会一直完善下去,欢迎建议和指导,同时也欢迎Star: https://github.com/Snailclimb...这篇文章主要是想通过一些问题,加深大家对于 Spring 的理解,所以不会涉及太多的代码!这篇文章整理了挺长时间,下面的很多问题我自己在使用 Spring 的过程中也并没有注意,自己也是临时查阅了很多资料和书籍补上的。网上也有一些很多关于 Spring 常见问题/面试题整理的文章,我感觉大部分都是互相 copy,而且很多问题也不是很汗,有些回答也存在问题。所以,自己花了一周的业余时间整理了一下,希望对大家有帮助。 什么是 Spring 框架?Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。Spring 官网:https://spring.io/。 我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是Spring 所有组件的核心,Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。 Spring 官网列出的 Spring 的 6 个特征: 核心技术 :依赖注入(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL。测试 :模拟对象,TestContext框架,Spring MVC 测试,WebTestClient。数据访问 :事务,DAO支持,JDBC,ORM,编组XML。Web支持 : Spring MVC和Spring WebFlux Web框架。集成 :远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。语言 :Kotlin,Groovy,动态语言。列举一些重要的Spring模块?下图对应的是 Spring4.x 版本。目前最新的5.x版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。 Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IOC 依赖注入功能。Spring Aspects : 该模块为与AspectJ的集成提供支持。Spring AOP :提供了面向方面的编程实现。Spring JDBC : Java数据库连接。Spring JMS :Java消息服务。Spring ORM : 用于支持Hibernate等ORM工具。Spring Web : 为创建Web应用程序提供支持。Spring Test : 提供了对 JUnit 和 TestNG 测试的支持。谈谈自己对于 Spring IoC 和 AOP 的理解IoCIoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spirng 特有。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。 ...

June 5, 2019 · 3 min · jiezi

千万不要这样使用-ArraysasList

使用Arrays.asList()的原因无非是想将数组或一些元素转为集合,而你得到的集合并不一定是你想要的那个集合。 而一开始asList()的设计时用于打印数组而设计的,但jdk1.5开始,有了另一个比较更方便的打印函数Arrays.toString(),于是打印不再使用asList(),而asList()恰巧可用于将数组转为集合。 错误用法如果你这样使用过,那你要注意下了。 错误一将基本类型数组作为asList的参数 int[] arr = {1,2,3};List list = Arrays.asList(arr);System.out.println(list.size());猜一下输出结果? 错误二将数组作为asList参数后,修改数组或List String[] arr = {"欢迎","关注","Java"};List list = Arrays.asList(arr); arr[1] = "爱上";list.set(2,"我"); System.out.println(Arrays.toString(arr));System.out.println(list.toString());猜一下输出结果? 错误三数组转换为集合后,进行增删元素 String[] arr = {"欢迎","关注","Java"};List list = Arrays.asList(arr); list.add("新增");list.remove("关注");猜一下输出结果? 你是不是以为上面????那个list是 java.util.ArrayList ? 答案很确定:NO! 探索真理我们通过asList()源码可发现,但为了更直观,我们通过IDEA debug来看看结果。 List<String> asList = Arrays.asList("欢迎","关注","码上实战");ArrayList<String> aList = new ArrayList<>(asList); 其实它返回的是 java.util.Arrays.ArrayList ,这个家伙是谁呢? 请看下源码: public class Arrays { //省略其他方法 public static <T> List<T> asList(T... a) { return new ArrayList<>(a); } //就是这个家伙 ???? private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable{ private final E[] a; ArrayList(E[] array) { a = Objects.requireNonNull(array); } @Override public int size() { return a.length; } //省略其他方法 }}但它和ArrayList貌似很像唉!有什么不同吗? ...

June 5, 2019 · 1 min · jiezi

springbootmybatismybatisplus分页查询简单实现

最近在研究mybatis,然后就去找简化mybatis开发的工具,发现就有通用Mapper和mybatis-plus两个比较好的可是使用,可是经过对比发现还是mybatis-plus比较好,个人觉得,勿喷。。。集成还是非常简单的,然后就在研究怎么分页,开始研究通用mapper时发现有个pagehelper的分页工具可以和它搭配。然后反过来看是不是也可以和mybatis-plus搭配使用呢?发现mybatis-plus之前是可以支持的,升级成3.X之后就不再支持了。然后就研究mybatis-plus自带的分页工具吧!今天就简单的写个例子吧! 首先我们肯定要先建一个springboot的项目,这里我就不再多说怎么创建了昂,不会的话请参考Spring Boot 的简单教程(一) Spring Boot 项目的创建创建好项目之后我们就需要配置maven依赖了,这里附上我的pom.xml文件了。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 这是mybatis-plus依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.1</version> </dependency> <!-- 这是mybatis-plus的代码自动生成器 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.1.1</version> </dependency> <!-- 这是模板引擎依赖 --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.28</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>配置好pom.xml文件之后,理所当然的就需要配置application.yml了。 #端口号 server: port: 8888 #数据库的配置信息 spring: datasource: url: jdbc:mysql://localhost:3306/blog #自己的数据库名称 username: root password: 123456 mybatis: #开启驼峰命名法 configuration: map-underscore-to-camel-case: true mybatis-plus: # xml地址 mapper-locations: classpath:mapper/*Mapper.xml # 实体扫描,多个package用逗号或者分号分隔 type-aliases-package: com.zhouzhaodong.pagination.entity #自己的实体类地址 configuration: # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl接下来就需要配置mybatis-plus的代码生成器了。/** * <p> * 读取控制台内容 * </p> */ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输入" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotEmpty(ipt)) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("jobob"); gc.setOpen(false); // gc.setSwagger2(true); 实体属性 Swagger2 注解 mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/blog?useUnicode=true&useSSL=false&characterEncoding=utf8"); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); //这里有个模块名的配置,可以注释掉不用。// pc.setModuleName(scanner("模块名"));// pc.setParent("com.zhouxiaoxi.www"); pc.setParent(scanner("模块地址")); mpg.setPackageInfo(pc); // 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 如果模板引擎是 freemarker String templatePath = "/templates/mapper.xml.ftl"; // 如果模板引擎是 velocity// String templatePath = "/templates/mapper.xml.vm"; // 自定义输出配置 List<FileOutConfig> focList = new ArrayList<>(); // 自定义配置会被优先输出 focList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! return projectPath + "/src/main/resources/mapper/"// + + pc.getModuleName() + 如果放开上面的模块名,这里就有一个模块名了 + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); /* cfg.setFileCreate(new IFileCreate() { @Override public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) { // 判断自定义文件夹是否需要创建 checkDir("调用默认方法创建的目录"); return false; } }); */ cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); // 配置模板 TemplateConfig templateConfig = new TemplateConfig(); // 配置自定义输出模板 //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 // templateConfig.setEntity("templates/entity2.java"); // templateConfig.setService(); // templateConfig.setController(); templateConfig.setXml(null); mpg.setTemplate(templateConfig); // 策略配置 StrategyConfig strategy = new StrategyConfig(); //数据库表映射到实体的明明策略 strategy.setNaming(NamingStrategy.underline_to_camel); //数据库表字段映射到实体的命名策略, 未指定按照 naming 执行 strategy.setColumnNaming(NamingStrategy.underline_to_camel); //自定义继承的Entity类全称,带包名// strategy.setSuperEntityClass("***"); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); //自定义继承的Controller类全称,带包名// strategy.setSuperControllerClass("***"); strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); //自定义基础的Entity类,公共字段(可添加更多)// strategy.setSuperEntityColumns("id"); //驼峰转连字符 strategy.setControllerMappingHyphenStyle(true); //表前缀// strategy.setTablePrefix(pc.getModuleName() + "_"); mpg.setStrategy(strategy); mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); }运行之后就会出现所有需要的文件了。 ...

June 5, 2019 · 3 min · jiezi

软件测试进阶指南-MTSC2019-测试开发大会日程-V10-版公布有奖投票你最关注的议题

投票选出你最关注的 MTSC2019 测试开发大会议题,抽奖领取大会门票和 TesterHome 社区其他福利!参与方式见文末!2019,最前沿热门的测试技术和质量管理 QA 最佳实践,尽在 MTSC2019 测试开发大会! MTSC2019 测试开发大会日程 V1.0 重磅发布MTSC2019 第五届中国移动互联网测试开发大会邀请到60+ 来自 Google、BAT、TMD 等一线互联网企业的测试大咖分享精彩议题,涵盖移动自动化测试、服务端测试、质量保障 QA、高新领域测试(AI+、大数据测试、IOT 测试),游戏测试,工程效能提升等 6 大专题方向,预计有 2000+ 测试同行会在现场交流 。 目前 MTSC2019 大会日程 V1.0 版正式公布(Tips:日程后续可能还会有微调,请以官网最新信息为准)。 MTSC2019 8 折门票倒计时截止到 5 月 31 日,有意向者请抓紧抢票! 大会官网:http://2019.test-china.org/报名地址:https://www.bagevent.com/even...MTSC2019 议题亮点解析MTSC2019 大会在移动测试专题,有阿里巴巴手机淘宝的“双十一”客户端质量保障负责人分享全链路验收实践,有蚂蚁金服移动测试 2.0+ 以及代码染色精准化测试探索,以及腾讯微信小程序质量体系构建。 在游戏测试专题,MTSC2019 联合腾讯 WeTest 邀请到腾讯互娱的质量天团(天美、光子、图灵三大工作室测试总监及核心团队)首次公开揭秘腾讯海量用户大型游戏(王者荣耀、绝地求生等)背后的游戏测试和质量保障黑科技。 在今年备受关注的工程效能提升方向,有来自百度搜索质量平台部的资深测试专家组团深度分享百度持续集成交付平台从 0 到 1 构建在每个阶段的踏坑经验,以及从自动化向智能化迭代(数据驱动+AI 测试)的实践心得,有蚂蚁金服质效破局探索,也有美团持续交付工具链分享以及国内知名持续交付专家乔梁老师对持续交付 2.0 的讲解。 在服务端测试和质量保障 QA 领域,既有 VIPKID 千万级系统压测案例和苏宁亿级用户平台性能调优实践,也有优酷视频和转转电商背后的质量保障体系建设案例分享,以及 DevOps 和 TestOps 背景下的微医多维一体化监控平台实践总结,360 和安居客测试团队应对变化的转型升级经验参考,还有酷家乐对混沌工程的探索实践。 而关于火热的 AI+ 测试技术,更有来自美团、小米(小爱产品)、京东、ANKER、Intel、百度、腾讯的多个 AI+ 测试落地案例。 ...

June 3, 2019 · 1 min · jiezi

springcloud-基于feign的服务接口的统一hystrix降级处理

springcloud开发微服务时,基于feign来做声明式服务接口,当启用hystrix服务熔断降级时,项目服务众多,每个Feign服务接口都得写一些重复问的服务降级处理代码,势必显得枯燥无味: Feign服务接口: @FeignClient(name="springcloud-nacos-producer", qualifier="productApiService", contextId="productApiService", fallback=ProductFallbackApiService.class)public interface ProductApiService { /** * 创建商品 * @param product */ @PostMapping(value="/api/product/add", produces=APPLICATION_JSON, consumes=APPLICATION_JSON) public Result<Long> createProduct(@RequestBody Product product); /** * 修改商品 * @param product */ @PutMapping(value="/api/product/update", produces=APPLICATION_JSON, consumes=APPLICATION_JSON) public Result<Object> updateProduct(@RequestBody Product product); /** * 删除商品 * @param productId */ @DeleteMapping(value="/api/product/delete/{productId}", produces=APPLICATION_JSON) public Result<Object> deleteProductById(@PathVariable("productId") Long productId); /** * 根据productId获取商品信息 * @param productId * @return */ @GetMapping(value="/api/product/{productId}", produces=APPLICATION_JSON) public Result<Product> getProductById(@PathVariable("productId") Long productId); /** * 根据条件查询商品列表(分页、排序) * @param condition * @param page * @param sort * @return */ @GetMapping(value="/api/product/list1", produces=APPLICATION_JSON) public PageResult<List<Product>> getProductListByPage(@RequestParam Product condition, @RequestParam Page page, @RequestParam Sort sort);}对应的熔断降级处理类: ...

June 3, 2019 · 1 min · jiezi

Spring源码原理篇一

Spring源码原理篇--容器初始化&Bean后置处理器本篇主要是讲解IOC容器初始化过程中大体进行了哪一些工作,以及Bean后置处理器的工作原理和BeanPostProcessor在底层的使用。环境准备编译器IDEAmaven依赖spring-context version:4.3.12.RELEASEmaven依赖junit version:4.11BeanPostProcessor工作原理实现BeanPostProcessor接口的组件,并且在两个方法体内打上断点: public class BeanPostProcessorDefinition implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object o, String s) throws BeansException { System.out.println("postProcessBeforeInitialization -->"+s+" = "+o); return o; } @Override public Object postProcessAfterInitialization(Object o, String s) throws BeansException { System.out.println("postProcessorAfterInitialization -->"+s+"="+o); return o; }}调试后查看方法调用栈如下(如图1): 在方法调用栈中的initializeBean(初始化Bean)方法中,有下面一段类似的伪代码: initializeBean(param){wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);...invokeInitMethods(beanName, wrappedBean, mbd);...wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}这段伪代码的大致意思就是先执行bean初始化之前的方法,然后执行bean初始化方法,最后执行初始化后的方法。applyBeanPostProcessorsBeforeInitialization也是属于方法调用栈的一环,进去有类似一段伪代码: applyBeanPostProcessorsBeforeInitialization(param) throws BeansException { for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { result = beanProcessor.postProcessBeforeInitialization(result, beanName); if (result == null) { return result; } } return result; }这段代码通过遍历得到所有的BeanPostProcessor,然后挨个执行重写的postProcessBeforeInitialization方法,倘若有一个方法返回的bean为null,那么循环就会跳出,意味着下面的postProcessBeforeInitialization方法不会被执行。在初始化方法后执行的applyBeanPostProcessorsAfterInitialization同理也是一样的。大致总结后置处理器处理Bean初始化的过程(如图2): ...

June 3, 2019 · 2 min · jiezi

基于springsecurityoauth2实现单点登录持续更新

基于spring-security-实现数据库版文章代码地址:链接描述可以下载直接运行,基于springboot2.1.5,springcloud Greenwich版本实现。前面两篇写了认证oauth2通过内存 还有jdbc实现认证中心。接下来我们采用oauth2实现管理系统的单点登录。 说到这里,需要介绍几个注解: @EnableAuthorizationServer 该注解用来开启认证服务,使用该注解表明自己是一个认证服务。 @EnableResourceServer 该注解要用来开启资源保护,表明自己是资源服务器受认证服务保护。 @EnableOAuth2Sso 该注解表示自己是oauth2客户端,也即单点登录客户端 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) spring-security默 认禁用注解,使用该注解来判断用户对某个控制层的方法是否具有访问权限 好来,注解介绍完了,闲话少说。我们开始今天的主题“单点登录”。 (1)创建sso-client项目,修改maven依赖: 因为,是web项目需要添加maven依赖。 (2)在启动类加上@EnableOAuth2Sso注解,表明自己是客户端 (3)下面进行最重要的,设置配置文件 因为,前面几个配置在之前章节介绍过,这里只介绍server.servlet.session.cookie.name=OAUTH2SESSION这个配置。 这是个坑,我在没加这个配置之前,授权成功后,还是跳转授权登录页码。认证服务器和浏览器控制台也没有报错信息。只好debug一点点差错。 这里简单介绍下如何查阅源码,首先全局搜索自己的配置 security.oauth2.client.user-authorization-uri=http://localhost:9001/oauth/authorize因为这个地址是认证服务器请求授权的,所以,请求认证的过滤器肯定包含他。搜索的结果如下: 两个结果,一个是我们自己配置的忽略,点开另外一个: ok我们在源码中找到这个类,一直向上找,可以找到OAuth2RestTemplate 同样的,我们可以搜索这个地址,查找在认证服务器中是如何认证的。 跑偏了,还是介绍下这个配置吧,通过这个配置session和认证服务器不一样结局。也可以设置上下文路径 server.servlet.context-path=/sso-client (4)调回来,下来我们创建一个controller文件,用来获取授权用户信息: 在template下创建index.html欢迎页面: (5)启动客户端服务: (6)因为,我们需要请求认证服务器,校验token,因此认证服务器需要开启/oauth/token路径,修改WebSecurityConfig文件添加: (7)启动认证服务,访问客户端首页: http://localhost:9005 如下: 自动跳转到认证服务器登录地址,输入用户名: admin 密码: 123456 登录 你可以把项目修改端口启动试试,登录一个另一个不在需要登录。 未完待续,下一篇介绍资源服务器和认证服务器的集成。 有问题,请留言。

June 2, 2019 · 1 min · jiezi

Halo-v10-正式版发布一款惊艳的动态博客系统

前言Halo 从去年 5 月开源以来,广受小伙伴们的喜爱,在此非常感谢使用 Halo 发表博客的小伙伴们。今年,在 @JohnNiang 的帮助下,我们几乎完全重写了 Halo,然后 1.0 正式版就发布了。在此,非常感谢 @JohnNiang 的加入以及他做出的贡献。再到后面,我们公开了 admin api 之后,@雨季不再来 使用了 Flutter 为 Halo 开发了管理端的 APP。相信以后越来越多人加入之后,Halo 会变得更好。希望大家会喜欢。 主要特性拥有使用 Vue 开发的后台管理,体验升级,但是并不需要独立部署,启动 Halo 即可。拥有 Restful 风格的 Content api,你可以用于开发单页面主题,微信小程序等。拥有 Restful 风格的 Admin api,你可以用于开发桌面管理客户端,管理 App(已有) 等。拥有使用 Flutter 开发的管理端 App,支持 Android 和 iOS,随时随地发表你的想法!感谢@雨季不再来。拥有独立的评论插件,使用 Vue 开发,只需在页面引入构建好的 JS 文件即可,完美地和主题相结合。支持多主题。另外,还支持在线下载主题以及更新主题。支持在线修改主题文件内容,无需在本地修改然后上传。十分友好的主题开发体验,支持自定义配置。(主题开发文档正在开发中)。功能强大的附件管理,同时支持本地上传,又拍云/七牛云/阿里云等云存储,另外,还支持 SM.MS 图床(非常感谢 SM.MS,请大家善用该服务哦)。自带友情链接管理,图库管理(给爱摄影的小伙伴们)。支持自定义页面。支持 Markdown 文档导入,顺带解析 FrontMatter。支持日志功能,类似于 QQ 空间的说说,亦或者微博。同时支持微信发布日志(后续计划)。还有…相关链接Halo 开源地址:https://github.com/halo-dev/haloWeb 管理端:https://github.com/halo-dev/h...管理端 APP:https://github.com/halo-dev/h...独立评论插件:https://github.com/halo-dev/h...主题仓库:https://halo.run/theme交流论坛:https://bbs.halo.run有喜欢的同学可以点个 star 哦。有任何问题可以去 Github issues 或者 https://bbs.halo.run 。预览图 ...

June 2, 2019 · 1 min · jiezi

SpringBootSpringSecurityjwt整合及初体验

原来一直使用shiro做安全框架,配置起来相当方便,正好有机会接触下SpringSecurity,学习下这个。顺道结合下jwt,把安全信息管理的问题扔给客户端,准备首先用的是SpringBoot,省去写各种xml的时间。然后把依赖加入一下 <!--安全--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency><!--jwt--><dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version></dependency>application.yml加上一点配置信息,后面会用 jwt: secret: secret expiration: 7200000 token: Authorization可能用到代码,目录结构放出来一下 配置SecurityConfig配置首先是配置SecurityConfig,代码如下 @Configuration@EnableWebSecurity// 这个注解必须加,开启Security@EnableGlobalMethodSecurity(prePostEnabled = true)//保证post之前的注解可以使用public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired JwtUserDetailsService jwtUserDetailsService; @Autowired JwtAuthorizationTokenFilter authenticationTokenFilter; //先来这里认证一下 @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoderBean()); } //拦截在这配 @Override protected void configure(HttpSecurity http) throws Exception { http .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint) .and() .authorizeRequests() .antMatchers("/login").permitAll() .antMatchers("/haha").permitAll() .antMatchers("/sysUser/test").permitAll() .antMatchers(HttpMethod.OPTIONS, "/**").anonymous() .anyRequest().authenticated() // 剩下所有的验证都需要验证 .and() .csrf().disable() // 禁用 Spring Security 自带的跨域处理 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 定制我们自己的 session 策略:调整为让 Spring Security 不创建和使用 session http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } @Bean public PasswordEncoder passwordEncoderBean() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }}ok,下面娓娓道来。首先我们这个配置类继承了WebSecurityConfigurerAdapter,这里面有三个重要的方法需要我们重写一下: ...

June 1, 2019 · 4 min · jiezi

3分钟干货之Spring-是如何快速创建产品就绪应用程序的

Spring Boot 致力于快速产品就绪应用程序。为此,它提供了一些譬如高速缓存,日志记录,监控和嵌入式服务器等开箱即用的非功能性特征。 spring-boot-starter-actuator - 使用一些如监控和跟踪应用的高级功能 spring-boot-starter-undertow, spring-boot-starter-jetty, spring-boot-starter-tomcat - 选择您的特定嵌入式 Servlet 容器 spring-boot-starter-logging - 使用 logback 进行日志记录 spring-boot-starter-cache - 启用 Spring Framework 的缓存支持 Spring2 和 Spring5 所需要的最低 Java 版本是什么?Spring Boot 2.0 需要 Java8 或者更新的版本。Java6 和 Java7 已经不再支持。

May 31, 2019 · 1 min · jiezi

马蜂窝大交通业务监控报警系统架构设计与实现

部门的业务线越来越多,任何一个线上运行的应用,都可能因为各种各样的原因出现问题:比如业务层面,订单量比上周减少了,流量突然下降了;技术层面的问题,系统出现 ERROR ,接口响应变慢了。拿大交通业务来说,一个明显的特点是依赖很多供应商的服务,所以我们还需要关注调用供应商接口是否出现异常等等。 为了让大交通下的各业务线都能够通过报警尽早发现问题、解决问题,进而提升业务系统的服务质量,我们决定构建统一的监控报警系统。一方面在第一时间发现已经出现的系统异常,及时解决;另一方面尽早发现一些潜在的问题,比如某个系统目前来看没有影响业务逻辑的正常运转,但是一些操作耗时已经比较长等,这类问题如果不及时处理,将来就很可能影响业务的发展。 本文主要介绍马蜂窝大交通业务监控报警系统的定位、整体架构设计,以及我们在落地实践过程中的一些踩坑经验。 架构设计与实现我们希望监控报警系统主要具备以下三个能力: 1. 常用组件自动报警:对于各业务系统常用的框架组件(如 RPC ,HTTP 等)创建默认报警规则,来方便框架层面的统一监控。 2. 业务自定义报警:业务指标由业务开发自定义埋点字段,来记录每个业务和系统模块的特殊运行状况。 3. 快速定位问题:发现问题并不是目的,解决才是关键。我们希望在完成报警消息发送后,可以让开发者一目了然地发现问题出现在什么地方,从而快速解决。 在这样的前提下,报警中心的整体架构图和关键流程如下图所示: 纵向来看,Kafka 左侧是报警中心,右侧是业务系统。 报警中心的架构共分为三层,最上层是 WEB 后台管理页面,主要完成报警规则的维护和报警记录的查询;中间层是报警中心的核心;最下面一层是数据层。业务系统通过一个叫做 mes-client-starter 的 jar 包完成报警中心的接入。 我们可以将报警中心的工作划分为五个模块: 1. 数据收集我们采用指标采集上报的方式来发现系统问题,就是将系统运行过程中我们关注的一些指标进行记录和上传。上传的方式可以是日志、 UDP 等等。 首先数据收集模块我们没有重复造轮子,可是直接基于 MES (马蜂窝内部的大数据分析工具)来实现,主要考虑下面几个方面的原因:一来数据分析和报警在数据来源上是相似的;二来可以节省很多开发成本;同时也方便报警的接入。 那具体应该采集哪些指标呢?以大交通业务场景下用户的一次下单请求为例,整个链路可能包括 HTTP 请求、Dubbo 调用、SQL 操作,中间可能还包括校验、转换、赋值等环节。一整套调用下来,会涉及到很多类和方法,我们不可能对每个类、每个方法调用都做采集,既耗时也没有意义。 为了以最小的成本来尽可能多地发现问题,我们选取了一些系统常用的框架组件自动打点,比如 HTTP、SQL、我们使用的 RPC 框架 Dubbo ,实现框架层面的统一监控。 而对于业务来说,每个业务系统关注的指标都不一样。对于不同业务开发人员需要关注的不同指标,比如支付成功订单数量等,开发人员可以通过系统提供的 API 进行手动埋点,自己定义不同业务和系统模块需要关注的指标。 2. 数据存储对于采集上来的动态指标数据,我们选择使用 Elasticsearch 来存储,主要基于两点原因: 一是动态字段存储。每个业务系统关注的指标可能都不一样,每个中间件的关注点也不同,所以埋哪些字段、每个字段的类型都无法预知,这就需要一个可以动态添加字段的数据库来存储埋点。Elasticsearch 不需要预先定义字段和类型,埋点数据插入的时候可以自动添加。 二是能够经得起海量数据的考验。每个用户请求进过每个监控组件都会产生多条埋点,这个数据量是非常庞大的。Elasticsearch 可以支持大数据量的存储,具有良好的水平扩展性。 此外,Elasticsearch 还支持聚合计算,方便快速执行 count , sum , avg 等任务。  3. 报警规则有了埋点数据,下一步就需要定义一套报警规则,把我们关注的问题量化为具体的数据来进行检查,验证是否超出了预设的阈值。这是整个报警中心最复杂的问题,也最为核心。 ...

May 31, 2019 · 2 min · jiezi

让springcloud-feignclient-完全支持springmvc的RequestParam注解的特性

1、要解决的问题在springcloud微服务中,使用feign来做声明式微服务调用的client时,经常会遇到springmvc的原生注解@RequestParam不支持自定义POJO对象的问题,例如: 服务的API接口: @FeignClient(name="springcloud-nacos-producer", qualifier="productApiService")public interface ProductApiService { @GetMapping(value="/api/product/list", produces=APPLICATION_JSON) public PageResult<List<Product>> getProductListByPage(@RequestParam Product condition, @RequestParam Page page, @RequestParam Sort sort);}public class Page implements DtoModel { private static final long serialVersionUID = 1L; private Integer currentPage = 1; private Integer pageSize = 10; private Integer totalRowCount = 0; //get/set...}public class Sort implements DtoModel { private static final long serialVersionUID = 1L; private List<Order> orders; Sort() { super(); } Sort(List<Order> orders) { super(); this.orders = orders; } public static Sort by(Order... orders) { return new Sort(Arrays.asList(orders)); } public List<Order> getOrders() { return orders; } public void setOrders(List<Order> orders) { this.orders = orders; } public Order first() { if(orders != null && orders.size() > 0) { return orders.get(0); } return null; } public static class Order { public static final String DIRECTION_ASC = "asc"; public static final String DIRECTION_DESC = "desc"; private String property; private String direction; Order() { super(); } Order(String property, String direction) { super(); if(direction != null) { direction = direction.toLowerCase(); direction = DIRECTION_DESC.equals(direction) ? DIRECTION_DESC : DIRECTION_ASC; } else { direction = DIRECTION_ASC; } this.property = property; this.direction = direction; } public static Order by(String property, String direction) { return new Order(property, direction); } public static Order asc(String property) { return new Order(property, DIRECTION_ASC); } public static Order desc(String property) { return new Order(property, DIRECTION_DESC); } public String getProperty() { return property; } public void setProperty(String property) { this.property = property; } public String getDirection() { return direction; } public void setDirection(String direction) { this.direction = direction; } /** * Used by SpringMVC @RequestParam and JAX-RS @QueryParam * @param order * @return */ public static Order valueOf(String order) { if(order != null) { String[] orders = order.trim().split(":"); String prop = null, dir = null; if(orders.length == 1) { prop = orders[0] == null ? null : orders[0].trim(); if(prop != null && prop.length() > 0) { return Order.asc(prop); } } else if (orders.length == 2) { prop = orders[0] == null ? null : orders[0].trim(); dir = orders[1] == null ? null : orders[1].trim(); if(prop != null && prop.length() > 0) { return Order.by(prop, dir); } } } return null; } @Override public String toString() { return property + ":" + direction; } } @Override public String toString() { return "Sort " + orders + ""; }}服务的提供者(Provider): ...

May 31, 2019 · 6 min · jiezi

基于springsecurityoauth2实现oauth2数据库版持续更新

基于spring-security-oauth2实现oauth2数据库版文章代码地址:链接描述可以下载直接运行,基于springboot2.1.5,springcloud Greenwich版本实现 该系列分为两个部分:分为内存实现,数据库实现。其中数据库实现采用RBAC权限角色管理。 上一篇,介绍了oauth2的内存实现,也就是认证服务把客户端和用户信息都存储在内存中,这样不利于拓展,不适合于生产环境。下面,我们开始基于mysql数据库的oauth2实现。 首先,我们创建oauth2数据库,注意编码选择utf-8mb4格式,utf-8是不规范的,mysql也没有进行更改。 好了,现在我们初始化表,sql如下: Drop table if exists oauth_client_details;create table oauth_client_details ( client_id VARCHAR(255) PRIMARY KEY, resource_ids VARCHAR(255), client_secret VARCHAR(255), scope VARCHAR(255), authorized_grant_types VARCHAR(255), web_server_redirect_uri VARCHAR(255), authorities VARCHAR(255), access_token_validity INTEGER, refresh_token_validity INTEGER, additional_information TEXT, autoapprove VARCHAR (255) default 'false') ENGINE=InnoDB DEFAULT CHARSET=utf8; Drop table if exists oauth_access_token;create table oauth_access_token ( token_id VARCHAR(255), token BLOB, authentication_id VARCHAR(255), user_name VARCHAR(255), client_id VARCHAR(255), authentication BLOB, refresh_token VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=utf8; ...

May 31, 2019 · 4 min · jiezi

基于springsecurityoauth2实现oauth2持续更新

基于spring-security-oauth2实现oauth2文章代码地址:链接描述可以下载直接运行,基于springboot2.1.5,springcloud Greenwich版本实现 该系列分为两个部分:分为内存实现,数据库实现。其中数据库实现采用RBAC权限角色管理。 首先声明oauth2是一种协议规范,spring-security-oauth2是对他的一种实现。其次,还有shiro实现,自己根据规范编写代码的实现方式。主流的qq,微信等第三方授权登录方式都是基于oauth2实现的。 oauth2的认证方式有授权码,简单,账户密码,客户端等方式,具体请自行百度不做过多的阐述。 本文基于授权码方式实现 oauth生态设计的范围很大,可以说是一种解决方案,它有“第三方客户端(web服务,APP服务)”、“用户”、“认证服务器”、“资源服务器”等部分。认证流程如下图: (A)用户打开客户端以后,客户端要求用户给予授权。(B)用户同意给予客户端授权。 (C)客户端使用上一步获得的授权,向认证服务器申请令牌。 (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。 (E)客户端使用令牌,向资源服务器申请获取资源。 (F)资源服务器确认令牌无误,同意向客户端开放资源。 好了,简单介绍后,现在开始实现基于内存的认证服务编写: (1)使用idea在nacos-test项目中创建authserver-memory模块。 目录如下图: (2)创建好module之后,我们开始配置pom文件加载依赖。注意:springcloud的版本1.x和2.x差别很大,有很多不兼容,例如jpa1.x的findOne方法在2.x版本中不能使用。因为我们需要先配置依赖管理 我把spring-cloud-Alibaba一起配置了。好了,现在添加oauth2的依赖,因为我们使用springcloud,并且springcloud-security为我们封装好了oauth2, 因次我们只添加这个依赖就可以 (3)依赖添加完成,下面我们开始写代码,创建config包,因为,我们认证之前需要先校验用户的账户密码是否正确,所以我们先配置WebSecurityConfig拦截: 在config方法里,我们在内存中,配置了两个用户,这里注意密码用了BCryptPasswordEncoder进行加密,在springboot2.x中不加密会报错的。 (4)到这里,用户验证已经完成,我们创建AuthConfig配置认证拦截处理: 需要添加 @EnableAuthorizationServer注解开启认证服务,注入加密用的BCryptPasswordEncoder实例。然后,配置需要认证的客户端, 这里需要细说一下,首先是client_id代表是哪个客户端也就是哪个APP或者web服务需要认证的,然后是客户端的secret秘钥需要加密, authorizedGrantTypes授权方式指的是授权码,简单,客户端,账户密码等,这里使用的是授权码(authorization_code),然后是scopes范围, redirectUris重定向地址,就是你的登录地址,授权后跳转的地址。 (5)配置application.properties文件: 很简单,不在多说,现在,我们启动应用: 成功,我们用这个地址进行授权访问: http://localhost:9000/oauth/authorize?client_id=client&response_type=code 成功后,跳转到登录页面: 输入账户:admin 密码: 123456 点登录 选择approve点击Authorize认证 这个code就是授权码 我们打开postman用post方式获取access_token 这个client就是配置的client_id,secret就是配置的secret,返回access_token ok,基于内存的oauth2实现完成,下一篇基于数据库的实现。有问题请留言。

May 30, 2019 · 1 min · jiezi

关于SpringCloudSpringBoot-的详细解释

什么是Spring Boot 用我的话来理解,Spring Boot就是整合了框架的框架,它让一切依赖都变得有序简单,你不用操心A.jar是什么版本,又依赖哪些版本的jar,它默认配置了很多框架的使用方式,就像 maven整合了所有的jar包,Spring Boot整合了所有的框架,第三方库的功能你拿着就能用。Spring Boot的核心思想就是约定大于配置,一切由内定的约束来自动完成。采用 Spring Boot可以大大的简化你的开发模式,节省大部分照搬照抄的成本,通过少量的代码就能创建一个独立的,它都有对应的组件支持。 它是由 Pivotal团队提供的全新框架,其设计目的是用来简化新 Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。 什么是Spring Cloud Spring Cloud是一套分布式服务治理的框架,既然它是一套服务治理的框架,那么它本身不会提供具体功能性的操作,更专注于服务之间的通讯、熔断、监控等。因此就需要很多的组件来支持一套功能。微服务是可以独立部署、水平扩展、独立访问(或者有独立的数据库)的服务单元, Spring Cloud就是这些微服务的大管家,采用了微服务这种架构之后,项目的数量会非常多, Spring Cloud做为大管家就需要提供各种方案来维护整个生态。 SpringColoud的小弟们小弟们可是非常多,就像梁山108好汉那样 默默无闻服务融合在每个微服务中、依赖其它组件并为其提供服务。 Ribbon,客户端负载均衡,特性有区域亲和、重试机制。 Hystrix,客户端容错保护,特性有服务降级、服务熔断、请求缓存、请求合并、依赖隔离。 Feign,声明式服务调用,本质上就是Ribbon+Hystrix Stream,消息驱动,有Sink、Source、Processor三种通道,特性有订阅发布、消费组、消息分区。 Bus,消息总线,配合Config仓库修改的一种Stream实现, Sleuth,分布式服务追踪,需要搞清楚TraceID和SpanID以及抽样,如何与ELK整合。 利刃独挑大梁独自启动不需要依赖其它组件,单枪匹马都能干。 Eureka,服务注册中心,特性有失效剔除、服务保护。 Dashboard,Hystrix仪表盘,监控集群模式和单点模式,其中集群模式需要收集器Turbine配合。 Zuul,API服务网关,功能有路由分发和过滤。 Config,分布式配置中心,支持本地仓库、SVN、Git、Jar包内配置等模式, 各司其职 每个组件都不是平白无故的产生的,是为了解决某一特定的问题而存在。 Eureka和Ribbon,是最基础的组件,一个注册服务,一个消费服务。Hystrix为了优化Ribbon、防止整个微服务架构因为某个服务节点的问题导致崩溃,是个保险丝的作用。Dashboard给Hystrix统计和展示用的,而且监控服务节点的整体压力和健康情况。Turbine是集群收集器,服务于Dashboard的。Feign是方便我们程序员些更优美的代码的。Zuul是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点IP端口信息,加强安全保护的。Config是为了解决所有微服务各自维护各自的配置,设置一个同意的配置中心,方便修改配置的。Bus是因为config修改完配置后各个结点都要refresh才能生效实在太麻烦,所以交给bus来通知服务节点刷新配置的。Stream是为了简化研发人员对MQ使用的复杂度,弱化MQ的差异性,达到程序和MQ松耦合。Sleuth是因为单次请求在微服务节点中跳转无法追溯,解决任务链日志追踪问题的。特殊成员Zipkin,之所以特殊是因为从jar包和包名来看它不属于Spring Cloud的一员,但是它与Spring Cloud Sleuth的抽样日志结合的天衣无缝。乍一看它与Hystrix的Dashboard作用有重叠的部分,但是他们的侧重点完全不同。Dashboard侧重的是单个服务的统计和是否可用,Zipkin侧重的监控环节时长。简言之,Dashboard侧重故障诊断,Ziokin侧重性能优化。 与SpringCloud区别 通俗的说,SpringBoot是构建单个服务的快速架构,比如它是全家桶中的1个汉堡,SpringCloud是关注全局的微服务协调整理治理框架,类似于组成多个服务的全家桶,桶里面不光有汉堡,还有薯条,还有番茄酱,那现在我要给汉堡加点番茄酱,它就更好吃了,意思就是SpringBoot可以配合全家桶中的这些工具组成一个强大的微服务体系,有点类似于Collection和Collections。 Spring boot是Spring的一套快速配置脚手架,可以基于Spring Boot快速开发单个微服务;Spring Cloud是一个基于Spring Boot实现的云应用开发工具Spring Boot专注于快速、方便集成的单个个体,Spring Cloud是关注全局的服务治理框架;Spring boot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,Spring Cloud很大的一部分是基于Spring Boot来实现。Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。 来自:https://www.cnblogs.com/itmsb...

May 30, 2019 · 1 min · jiezi

谈谈spring如何自定义标签

干货点了解如何基于spring自定义标签,这是自定义组件的第一步。而最重要的是了解了这个过程后也可以大致了解spring自身部分组件是怎么相互工作和触发的,如spring-aop,组件可以通过反调AopNamespaceHandler了解大致面貌。 系列文描述书写该系列文的初衷是因为最近正在负责一个组件的开发,于是打算将接触和学习到的知识写进博客里。这第一篇,记录基于spring如何自定义标签。 自定义标签的作用自定义标签可以说是spring为了给类似你我这样的开发人员扩展组件使用的,因为它提供了一个标准的公共可插拔的接口;目前我们都知道spring非常强大,不过实际上除了spring-core和spring-beans外,其他都是通过自定义标签扩展实现的,其次还有一些开源组件也是,如dubbo等。所以,对于想扩展spring组件的小伙伴来说,了解如何自定义标签和相应的原理是必须走的第一步。 那么如何自定义标签自定义标签可以简单分为四个步骤,分别是 编写.schemas文件,通知spring容器我们定义的xsd文件在哪里;编写.xsd文件,定义配置时可以使用的属性限制或者说支持的那些属性配置;编写.handlers 文件,扩展NamespaceHandler命名空间注册器和定义解析器;在xml文件中使用自定义标签下面我将以目前开发组件中的代码做例子,从在xml文件中使用开始一步一步逆推,复盘整个自定义标签的过程。 首先,先看下目录情况 common是我自定义的一个组件组,其中包含的resource组件便是这次使用了自定义标签的主体,可以从截图中看出部分相关文件的存放位置。 test-demo是为了测试这次组件中的自定义标签是否有作用而存在,test-demo只是导入了common组件组而已,再从中调用resource组件。好了,目录结构描述完了,接下来进入正题。 看下xml文件如何使用自定义标签 在第4行这里引入了resource对应的命名空间,spring会从本地扫.handlers,从中找到对应的Key值和Value值,如 spring容器会将Key值对应的具体命名空间注册注册入容器,至于这个空间注册器是怎么样的,后面再表,继续描述xml文件。在之后,我们可以在xmlns:schemaLocation中找到类似的Key&Value的配置,这次的配置是告诉spring容器从哪里查找XSD文件,这点可以从第六行找到,对应的XSD文件地址是:http://www.nuofankj.com/resou...细心的话不难发现,这是一个网络地址,是的,确实如此,不过spring的容器却是先在本地扫.schemas文件,并且读取其中的键值对关系,从中找到本地的文件地址,如果找不到,才会从网络中读取。如spring.schemas文件: 该文件以一种键值对的形式表明了文件在本地的地址,那就是resource.xsd,之后spring容器便会找到resource.xsd文件做校验。如 众所周知,XSD文件的作用是定义配置时可以使用的属性限制或者说支持那些属性配置。我们可以直接看applicationContext.xml中的配置 走到这一步就说明配置文件配置好了,接下来便是如何解析的问题了。也就是上文提到com.nuofankj.resource.schema.NamespaceHandler。 那么NamespaceHandler类是什么样的 该类扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器中。其中以SchemaNames.CONFIG_ELEMENT为名注册了一个类ConfigDefinitionParser,SchemaNames.CONFIG_ELEMENT对应的变量就是config字符串,目的就是为了解析 显然,ConfigDefinitionParser就是作为解析器存在的。 接下来看看该解析器是什么样的 该解析器继承了AbstractBeanDefinitionParser类,并且重写parseInternal方法,其中的参数element携带了resource:config中的所有配置,我们可以将自身的解析业务放在该函数中。以我自定义的组件为例: 我这边的业务是将SchemaNames.PACKAGE_ELEMENT包下的所有类扫出来并且放入list中保存,已经读取出type、suffix等相关配置。 到这一步,自定义标签的过程就全部理清楚了。相关源码地址:https://github.com/wiatingpub... Java源码分析、go语言应用、微服务,更多干货欢迎关注公众号:

May 29, 2019 · 1 min · jiezi

不你不了解Spring实例化bean的时候做了什么

Spring加载bean的时候构造函数什么时候调用、@PostConstruct什么时候调用、实现了BeanPostProcessor接口的bean中的postProcessAfterInitialization和postProcessBeforeInitialization什么时候调用?你是否清楚呢?如果清楚的话可以直接忽略该篇文章!!!最近来了几个新人,被问了一个和bean生命周期相关的一个知识点,解决新人的问题后自己再写了一个demo,目的是为了清晰的描述整个bean的生命周期。 注意注意,以下demo有五个类,可能会引起部分人不适,建议可以直接跳到最后看最终总结,或者自己下载源码运行下。demo地址:https://github.com/wiatingpub...给出一个demo首先给出一个实现了BeanFactoryPostProcessor的类,目的是为了比较清晰的看出postProcessBeanFactory接口被调用的时间点。 /** * BeanFactoryPostProcessor是bean工厂的处理器 */@Componentpublic class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { public MyBeanFactoryPostProcessor() { super(); System.out.println("【BeanFactoryPostProcessor】实现类postProcessBeanFactory的构造函数"); } // 允许我们在工厂里所有的bean被加载进来后但是还没初始化前,对所有bean的属性进行修改也可以add属性值,该操作在对应bean的构造函数执行前 @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0) throws BeansException { System.out .println("【BeanFactoryPostProcessor.postProcessBeanFactory】,来自MyBeanFactoryPostProcessor"); //获取到Spring中所有的beanName String[] beanStr = arg0.getBeanDefinitionNames(); //循环打印 for (String beanName : beanStr) { System.out.print("bean name:" + beanName + ";"); } System.out.println(); }}这里给出一个实现了BeanPostProcessor的类,目的是为了看出postProcessAfterInitialization、postProcessBeforeInitialization调用的时间点。/** * 完成bean实例化、配置以及其他初始化方法前后要添加一些自己逻辑处理则要实现接口BeanPostProcessor */@Componentpublic class MyBeanPostProcessor implements BeanPostProcessor { public MyBeanPostProcessor() { super(); System.out.println("【MyBeanPostProcessor】BeanPostProcessor实现类的构造函数"); } // 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的业务 @Override public Object postProcessAfterInitialization(Object arg0, String arg1) throws BeansException { if (arg0.getClass() == TestBeanA.class || arg0.getClass() == TestBeanB.class) { System.out .println("【BeanPostProcessor.postProcessAfterInitialization】来自MyBeanPostProcessor,beanName:" + arg1); } return arg0; } // 实例化、依赖注入、初始化后完成一些定制的业务 @Override public Object postProcessBeforeInitialization(Object arg0, String arg1) throws BeansException { if (arg0.getClass() == TestBeanA.class || arg0.getClass() == TestBeanB.class) { System.out .println("【BeanPostProcessor.postProcessBeforeInitialization】来自MyBeanPostProcessor,beanName:" + arg1); } return arg0; }}这里给出继承了InstantiationAwareBeanPostProcessorAdapter的子类,目的是为了看出postProcessBeforeInitialization、postProcessAfterInitialization、postProcessPropertyValues被调用的时间点。 ...

May 29, 2019 · 3 min · jiezi

Spring-Framework系列教程汇总

最近整理出了之前学习Spring Framework系列,记载了一些学习笔记,特地在此罗列出来。大家也可直接关注笔者github(Spring Framework)获取最新资讯。 TutorialSpring-framework-4.x1. Spring Annotation驱动编程2. Java Beans内省机制以及在Spring中的应用3. Java资源管理以及在Spring中的应用4. Spring自定义XML配置扩展Spring-framework-5.x1. 深入Java之国际化2. JSP在Spring中的应用(Annotation版)3. JSP在Spring中的应用(XML版)4. Java Reactive Web设计与实现5. Servlet在Spring中的应用6. Spring5新特性之测试7. Spring5新特性之Web Flux8. Spring Web自动装配(Annotation)Spring-framework-common1. Spring Aware接口应用

May 28, 2019 · 1 min · jiezi

FortuneCommons正式开源啦

Fortune Commons 是笔者在工作以来的一些技术积累,虽然是很[低端],但是对于Java入门或者初入Java开发的工作者来说,也是一个不错的学习资源,今天特地整合出来。Github地址:https://github.com/landy8530/... 版本说明版本约束Spring:5.x+JDK:1.8+版本计划为适应目前国内各个行业不同的技术分布,目前计划三个大版本的计划,说明如下: 版本功能说明1.x数据缓存处理和excel/pdf导出组件,集成Spring MVC2.x数据缓存处理和excel/pdf导出组件,集成Restful API3.x数据缓存处理和excel/pdf导出组件,集成Restful API,并且计划加入Spring Boot/Spring Cloud等注意:以上各个版本都可以增加其他组件。 工程说明目前最新版本为v1.0.x,含有以下子工程(子模块),分别说明如下(也可参考wiki): commons-core主要是本项目所需的一些核心功能实现,比如BeanCopier工具封装,读取yml文件工具,Freemarker解析实现,ApplicationContext工具类,Spring容器初始化后统一操作的listener实现以及其他一些工具类支持。 commons-datacache本模块动态实现了各主流缓存中间件的实现,可以自由切换,依赖于commons-nosql模块。目前实现了以下几种: 本地内存(Memory)MongodbMemcachedRedis(即将实现)commons-nosqlNoSql模拟关系型数据库的CRUD操作,目前有Mongodb实现。 commons-export实现了excel和pdf导出组件 commons-web封装了web端常见的一些配置操作 commons-web-springmvc封装了spring mvc的一些配置操作,依赖于commons-web子模块。 fortune-commons-example本项目的演示模块,主要是用于测试用途。 如何对该开源项目进行贡献代码大多是手敲,所以难免会有错误,你可以帮我Bug,提交issues或者PR。很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充或者加入其他的组件。为了使项目更加的透明化,便利化,也可以参与wiki的编写工作。为什么要做这个开源组件?初始想法源于自己工作中遇到的各种坑,主要目的是为了通过这个开源平台来帮助一些在学习 Java 或者直接在自己公司中使用或者扩展自己的项目。 Git操作说明切换分支fork本工程后可以按照如下操作即可, 切换到master分支,并且更新最新远程库中的代码 git checkout mastergit pull/git fetch创建分支创建自己的本地分支,以master为源创建 git checkout -b fortune-commons-export查看是否创建成功 git branch fortune-commons-beanutils* fortune-commons-export fortune-commons-memcached master星号(*)表示当前所在分支。现在的状态是成功创建的新的分支并且已经切换到新分支上。 同步分支把新建的本地分支push到远程服务器,远程分支与本地分支同名(当然可以随意起名) git push origin fortune-commons-export:fortune-commons-export创建标签git tag -a v1.0.1 -m "fortune commons v1.0.1"git push origin v1.0.1

May 26, 2019 · 1 min · jiezi

Java四种引用简介

引语:    我们知道java相比C,C++中没有令人头痛的指针,但是却有和指针作用相似的引用对象(Reference),就是常说的引用,比如,Object obj = new Object();这个obj就是引用,它指向的是真正的对象Object的地址,不过今天要说的是java中的四种引用。有人可能比较懵逼,四种引用?是的,从JDK1.2之后,java对引用这块的概念进行了扩充,按照引用的强度分为了四种引用:强引用,软引用,弱引用,虚引用。下面就让我们来看看这四种引用都具体的情况吧。 1.强引用1.1介绍:我们平时代码中使用得最多的引用,对象的类是:StrongReference。就比如上面说的Object obj = new Object();我们再熟悉不过了,作为最强的引用,只要引用还存在着,垃圾收集器就不会将该引用给回收,即使会出现OOM(内存溢出)。就是说这种引用只要引用还一直指向的对象,垃圾收集器是不会去管它的,所以它被称为强引用。不过如果 Object obj = new Object();obj = null;obj被赋值为了null,该引用就断了,垃圾收集器会在合适的时候回收改引用的内存。还有一种情况就是obj是成员变量,方法执行完了,obj随着被栈帧被回收了,obj引用也是一起被回收了。强引用的使用就不介绍了,地球人都知道。 2.软引用2.1介绍:软引用是用来描述一些有用但是非必须的对象。对应的类是SoftReference,它被回收的时机是系统内存不足的时候,如果内存足够,它不会被回收,内存不足了,可能会发生OOM了,软引用的对象就会被回收。这样的特性是不是就像缓存?是的,软引用可以用来存放缓存的数据,内存足够的时候一直可以访问,内存不足的时候,需要重新创建或者访问原对象。 2.2使用:其实不管是软引用,弱引用,还是虚引用,代码中使用方式都是像下面这样,使用对应的Reference将对象放入到构造函数当中,然后使用的地方reference.get()来调用具体对象。 Object obj = new Object();SoftReference<Object> softReference = new SoftReference<>(obj);softReference.get();同时可以使用ReferenceQueue来把引用和引用队列给关联起来: Object obj = new Object();ReferenceQueue<Object> refQueue = new ReferenceQueue<>();SoftReference<Object> softReference = new SoftReference<>(obj, refQueue);__所谓关联起来,其实就是当引用被回收的时候,会被添加到ReferenceQueue中,使用ReferenceQueue.poll()方法可以返回当前可用的引用,并从队列冲删除__。简单来说就是引用和引用队列关联起来(引用的构造函数传入队列),然后引用被回收的时候会被添加到队列中,然后使用poll()方法可以返回引用。 3.弱引用3.1介绍:虚引用比上面两个引用就更菜了,只要垃圾收集器扫描到了它,被弱引用关联的对象就会被回收。被弱引用关联对象的生命周期其实就是从对象创建到下一次垃圾回收。对应的类是WeakReference。 3.2使用:public static void main(String[] args) throws InterruptedException { Object obj = new Object(); ReferenceQueue<Object> refQueue = new ReferenceQueue<>(); WeakReference<Object> weakRef = new WeakReference<>(obj, refQueue); System.out.println("引用:" + weakRef.get()); System.out.println("队列中的东西:" + refQueue.poll()); // 清除强引用, 触发GC obj = null; System.gc(); Thread.sleep(200); System.out.println("引用:" + weakRef.get()); System.out.println("引用加入队列了吗? " + weakRef.isEnqueued()); System.out.println("队列中的东西:" + refQueue.poll()); /** * 输出结果 * 引用:java.lang.Object@7bb11784 * 队列中的东西:null * 引用:null * 引用加入队列了吗? true * 队列中的东西:java.lang.ref.WeakReference@33a10788 */ }可以看到当强引用被清除,手动触发GC后,弱引用回收,被加入到队列中了。 ...

May 26, 2019 · 1 min · jiezi

小说搜索站快速搭建2内容页解析

三方框架 JSOUPokhttp解析要素 翻章:上一章翻章:下一章目录内容 表设计 /** * 内容 */ private String content; @Field("content_title") private String contentTitle; @Field("chapter_url") private String chapterUrl; @Field("next_chapter_url") private String nextChapterUrl; @Field("last_chapter_url") private String lastChapterUrl;解析代码 public BookChapter content(String url) { BookChapter bookChapter = new BookChapter(); BookSite bookSite = getSite(url); try { Document document = download(url); Element titleElement = document.selectFirst(bookSite.getContentTitle()); if (titleElement != null) { bookChapter.setName(titleElement.text()); } Element chapterElement = document.selectFirst(bookSite.getChapterUrl()); if (chapterElement != null) { bookChapter.setChapterUrl(chapterElement.absUrl("href")); } Element nextElement = document.selectFirst(bookSite.getNextChapterUrl()); if (nextElement != null) { bookChapter.setNextChapterUrl(nextElement.absUrl("href")); } Element lastElement = document.selectFirst(bookSite.getLastChapterUrl()); if (lastElement != null) { bookChapter.setLastChapterUrl(lastElement.absUrl("href")); } Element contentElement = document.selectFirst(bookSite.getContent()); if (contentElement != null) { contentElement.select("a").remove(); contentElement.select("script").remove(); contentElement.select("style").remove(); bookChapter.setContent(contentElement.html()); } } catch (IOException e) { log.error(e.getMessage(), e); } return bookChapter; }最终结果 ...

May 26, 2019 · 1 min · jiezi

全限定名完全相同的两个类转换出现类型转换错误分析

描述 最近在公司开发一个新的需求:同事写好了一个工具类并且通过@Component的方式交给Spring容器进行管理,然后我在代码中按名字从Spring容器中获取这个对象。在获取的时候出现了以下的错误: com.utils.A cannot not be cast to com.utils.A问题解决思路 百度搜索之后发现全限定名相同的类进行转换出现错误可能是类加载器不相同而导致的。 接下来我使用Class.getClassLoader()验证是否类加载器不一致。 需要提醒的是:放在Spring中对象此时不能再通过按类型取出,因为本身类型已经不一致了,此时可以通过按名字获取。 getClassLoader() == null时,是指类加载器为BootStrap ClassLoader

May 26, 2019 · 1 min · jiezi

Java-Reactive-Web设计与实现

注: 本文是由读者观看小马哥公开课视频过程中的笔记整理而成。更多Spring Framework文章可参看笔者个人github: spring-framework-lesson 。0. 编程模型与并发模型Spring 5实现了一部分Reactive Spring WebFlux: Reactive Web(non-blocking servers in general) Spring Web MVC:传统Servlet Web(servlet applications in general) 0.1 编程模型编程模型:阻塞、非阻塞 NIO:同步+非阻塞,基于事件非阻塞 基本上采用Callback方式当时不阻塞,后续再输出(再回调)0.2 并发模型并发模型: 同步(Sync)异步(Async)0.3 比较同步+非阻塞:线程不会改变,不会切换[线程:main] Observable 添加观察者! [线程:main] 通知所有观察者! [线程:main] 3. 收到数据更新:Hello World [线程:main] 2. 收到数据更新:Hello World [线程:main] 1. 收到数据更新:Hello World 异步+非阻塞:线程会被切换[线程:main] 启动一个JFrame窗口! [线程:AWT-EventQueue-0] 销毁当前窗口! [线程:AWT-EventQueue-0] 窗口被关闭,退出程序!使用Jconsole查看改异步非阻塞程序 等待总数一直在增加,说明异步程序一直在等待。NIO就是无限地在处理,无限地在等待。 1. Reactive概念Reactive Programming:响应式编程,异步非阻塞就是响应式编程(Reactive Programming),与之相对应的是命令式编程。 Reactive并不是一种新的技术,不用Reactive照样可以实现非阻塞(同步、异步均可,推拉模式的结合),比如利用观察者模式实现(比如Java Swing GUI技术)。 Reactive的另外一种实现方式就是消息队列。 1.1 标准概念1.1.1 维基百科讲法https://en.wikipedia.org/wiki... In computing, reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change. With this paradigm it is possible to express static (e.g., arrays) or dynamic (e.g., event emitters) data streams with ease, and also communicate that an inferred dependency within the associated execution model exists, which facilitates the automatic propagation of the changed data flow关键点: ...

May 25, 2019 · 8 min · jiezi

Spring-Boot-Aop

spring-boot-aop什么是aop面向切面的程序设计(Aspect-oriented programming,AOP,又译作面向方面的程序设计、剖面导向程序设计)是计算机科学中的一种程序设计思想,旨在将横切关注点与业务主体进行进一步分离,以提高程序代码的模块化程度。通过在现有代码基础上增加额外的通知(Advice)机制,能够对被声明为“切点(Pointcut)”的代码块进行统一管理与装饰,如“对所有方法名以‘set*’开头的方法添加后台日志”。该思想使得开发人员能够将与代码核心业务逻辑关系不那么密切的功能(如日志功能)添加至程序中,同时又不降低业务代码的可读性。面向切面的程序设计思想也是面向切面软件开发的基础。 面向切面的程序设计将代码逻辑切分为不同的模块(即关注点(Concern),一段特定的逻辑功能)。几乎所有的编程思想都涉及代码功能的分类,将各个关注点封装成独立的抽象模块(如函数、过程、模块、类以及方法等),后者又可供进一步实现、封装和重写。部分关注点“横切”程序代码中的数个模块,即在多个模块中都有出现,它们即被称作“横切关注点(Cross-cutting concerns, Horizontal concerns)”。 日志功能即是横切关注点的一个典型案例,因为日志功能往往横跨系统中的每个业务模块,即“横切”所有有日志需求的类及方法体。而对于一个信用卡应用程序来说,存款、取款、帐单管理是它的核心关注点,日志和持久化将成为横切整个对象结构的横切关注点。 切面的概念源于对面向对象的程序设计的改进,但并不只限于此,它还可以用来改进传统的函数。与切面相关的编程概念还包括元对象协议、主题(Subject)、混入(Mixin)和委托(Delegate)。 AOP中的相关概念看过了上面解释,想必大家对aop已经有个大致的雏形了,但是又对上面提到的切面之类的术语有一些模糊的地方,接下来就来讲解一下AOP中的相关概念,了解了AOP中的概念,才能真正的掌握AOP的精髓。 Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。Target(目标对象):织入 Advice 的目标对象.。Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程 spring aopSpring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器,它在运行期通过代理方式向目标类织入增强代码。在Spring中可以无缝地将Spring AOP、IoC和AspectJ整合在一起。Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。在Java中动态代理有两种方式:JDK动态代理和CGLib动态代理 jdk proxyjava动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。 import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/** * <p> * * @author leone * @since 2018-11-09 **/public class JdkProxy { interface IUserService { Integer delete(Integer userId); } static class UserServiceImpl implements IUserService { @Override public Integer delete(Integer userId) { // 业务 System.out.println("delete user"); return userId; } } // 自定义InvocationHandler static class UserServiceProxy implements InvocationHandler { // 目标对象 private Object target; public UserServiceProxy(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("------方法调用前---------"); //执行相应的目标方法 Object result = method.invoke(target, args); System.out.println("------方法调用后---------"); return result; } } public static void main(String[] args) { IUserService userService = new UserServiceImpl(); // 创建调用处理类 UserServiceProxy handler = new UserServiceProxy(userService); // 得到代理类实例 IUserService proxy = (IUserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), new Class[]{IUserService.class}, handler); // 调用代理类的方法 Integer userId = proxy.delete(3); System.out.println(userId); }}cglib proxy而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。 ...

May 25, 2019 · 4 min · jiezi

Spring注解专题系类二

Spring注解应用篇--IOC容器Bean生命周期这是Spring注解专题系类文章,本系类文章适合Spring入门者或者原理入门者,小编会在本系类文章下进行企业级应用实战讲解以及spring源码跟进。本文来自公众号:B一下爪哇环境准备编译器IDEAmaven依赖spring-context version:4.3.12.RELEASEmaven依赖junit version:4.11Bean注解来指定bean初始化和销毁方法前面一章提过,在配置类中通过@Bean来将组件注入到容器中,在容器中,Bean的生命周期大抵上可以分为创建--初始化--销毁的过程,容器管理着组件的全部生命周期。Bean注解源码里面包含initMethod和destroyMethod两个属性,可以分别来自定义bean的初始化方法和销毁方法。自定义格式: @Bean(initMethod=,destoryMethod=)测试:先在Bean中自定义初始化和销毁方法: public class ExampleBean { private String name; private int age;.....public void init(){ System.out.println("ExampleB init..."); } public void destory(){ System.out.println("Example destory..."); }}配置类: @Configurationpublic class BaseConfig { @Bean(value="beanIdDefinition",initMethod = "init",destroyMethod = "destory") public ExampleBean exampleBean(){ return new ExampleBean(); }}测试类: @Test public void shouldAnswerWithTrue() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BaseConfig.class); System.out.println("IOC容器创建完成..."); String[] beanDefinitionNames = ctx.getBeanDefinitionNames(); ctx.close(); }output: exampleBean constructor......ExampleB init...IOC容器创建完成...Example destory...由此可知,在容器初始化过程中已经完成对bean的初始化工作,并且在容器关闭途中,调用bean的销毁方法。(下一章分析容器初始化大致做了哪一些工作) InitializingBean和DisposableBean接口指定Bean初始化和销毁方法InitializingBean的afterPropertiesSet在设置提供Bean的属性值后由BeanFactory调用进行方法调用。DisposableBean的destory在Bean单例被破坏时由BeanFactory进行方法调用。定义Bean并实现这两个接口 public class LifeCycleBean implements InitializingBean, DisposableBean{ @Override public void afterPropertiesSet() throws Exception { System.out.println("LifeCycleBean afterPropertieSet...."); } @Override public void destroy() throws Exception { System.out.println("LifeCycleBean destory...."); }}output: ...

May 25, 2019 · 1 min · jiezi