关于springboot:SpringBoot整合达梦数据库

装置配置软件装置下载地址:https://www.dameng.com/view_6... 本文以x86 win64 DM8为例 装置结束后关上DM数据库配置助手创立数据库,设置字符集utf8,去除字符大小写敏感 创立表空间及用户,最好是一个库对应一个用户一个表空间,创立用户时须要指定对应表空间 须要对用户调配DBA操作权限 数据表迁徙针对现有我的项目或框架库须要同步迁徙达到梦数据库,本文以mysql5.7为例,关上DM数据迁徙工具,留神放弃对象名大小写,抉择表时全副取出再全选,迁徙的表名和字段名就与原数据库保持一致 maven援用 <dependency> <groupId>com.dameng</groupId> <artifactId>DmJdbcDriver18</artifactId> <version>8.1.1.193</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.0</version> </dependency> 数据库配置应用druid治理连接池,去除wall的配置否则会报错 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: dm.jdbc.driver.DmDriver url: jdbc:dm://localhost:5236/ROOT?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8 username: ROOT password: abcd@1234 filters: stat,slf4j兼容代码映射成LinkHashMap数据会在达梦的数据库驱动中强制大写,这对某些接口的数据返回给前端数据大小写呈现问题,影响范畴较大 JdbcTemplate解决咱们能够通过混合应用jdbcTemplate进行查问的通用操作,调用query办法,传入自定义的ResultSetExtractor,失去jdbc原生的ResultSet对象,取出ResultSetMetaData转换成DmdbResultSetMetaData,其中的columns对象为公有对象且无办法拜访,通过反射取出即可,通过columns获取到数据库理论的列名 public List<LinkedHashMap<String, Object>> findListByParam(String sqlText, Map<String, Object> map) { List<LinkedHashMap<String, Object>> result = new ArrayList<>(); List<Object> paramList = new ArrayList<>(); //解析sqlText中的占位符#{xxxx} String regex = "\\#\\{(?<RegxName>[\\w.]*)\\}"; String sqlTextCopy = sqlText; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(sqlTextCopy); while (matcher.find()) { String paramNameSymbol = matcher.group(0); sqlText = sqlText.replace(paramNameSymbol, " ? "); } logger.debug("【sqlText】:" + sqlText); //参数赋值 matcher = pattern.matcher(sqlTextCopy); while (matcher.find()) { String paramNameSymbol = matcher.group(0); String paramName = paramNameSymbol.replace("#", "").replace("{", "").replace("}", ""); Object paramValue = map.get(paramName); logger.debug("【paramName】:" + paramName); logger.debug("【paramValue】:" + paramValue); paramList.add(paramValue); } jdbcTemplate.query(sqlText, paramList.toArray(), new ResultSetExtractor<Object>() { @Override public Object extractData(ResultSet rs) throws SQLException, DataAccessException { try { ResultSetMetaData rsMetaData = rs.getMetaData(); Column[] dm_columns = null; if (dataBaseInfoUtil.getUsingDataBaseType() == GlobalEnum.DataBaseType.DM) { ResultSetMetaDataProxyImpl resultSetMetaDataProxy = (ResultSetMetaDataProxyImpl) rsMetaData; DmdbResultSetMetaData dmdbResultSetMetaData = (DmdbResultSetMetaData) resultSetMetaDataProxy.getRawObject(); Class dataClass = DmdbResultSetMetaData.class; Field field = dataClass.getDeclaredField("columns"); field.setAccessible(true); dm_columns = (Column[]) field.get(dmdbResultSetMetaData); } while (rs.next()) { LinkedHashMap<String, Object> resultitem = new LinkedHashMap<>(); for (int i = 1; i <= rsMetaData.getColumnCount(); i++) { String columnName = ""; if (dataBaseInfoUtil.getUsingDataBaseType() == GlobalEnum.DataBaseType.DM) { columnName = dm_columns[i - 1].name; } else { columnName = rsMetaData.getColumnName(i); ; } Object columnValue = rs.getObject(columnName); resultitem.put(columnName, columnValue); } result.add(resultitem); } } catch (Exception e) { e.printStackTrace(); } finally { return null; } } }); return result; }与mybaits对立数据源在应用事务时,因为查问操作通过jdbcTemplate,更新操作通过myabtis,在某些隔离级别下会查问不到未提交的数据,所以须要对立数据源都为druid治理的datasource,这里的dynamicDataSource为我自定义的数据源解决对象,继承自spring的AbstractRoutingDataSource,为了解决多数据源状况 ...

January 8, 2022 · 2 min · jiezi

关于springboot:SpringBoot居然这么耗内存你知道吗

Spring Boot总体来说,搭建还是比拟容易的,特地是Spring Cloud全家桶,简称亲民微服务,但在发展趋势中,容器化技术曾经成熟,面对巨耗内存的Spring Boot,小公司示意用不起。现在,很多刚诞生的JAVA微服务框架大多主打“轻量级”,次要还是因为Spring Boot太重。 No1-Spring Cloud介绍有Spring大靠山在,更新、稳定性、成熟度的问题基本不须要思考。在JAVA系混的技术人员大概都据说过Spring的小名吧,所以不缺程序员……,而且这动手的难度非常低,齐全能够省去一个架构师。 然而,你必然在服务器上付出: 至多一台“服务发现 ”的服务器;可能有一个对立的网关Gateway;可能须要一个用于“分布式配置管理”的配置核心;可能进行“服务追踪”,晓得我的申请从哪里来,到哪里去;可能须要“集群监控”;我的项目上线后发现,咱们须要好多服务器,每次在集群中减少服务器时,都感觉疼爱;压测30秒压测前的内存占用 如图,内存占用304M。 压测时的内存占用 如图,内存占用1520M(1.5G),CPU回升到321% 概览 总结一个Spring Boot的简略利用,起码1G内存,一个业务点比拟少的微服务编译后的JAR会大概50M;而Spring Cloud引入的组件会绝对多一些,耗费的资源也会绝对更多一些。 启动工夫大概10秒左右: Started Application in 10.153 seconds (JVM running for 10.915) JAVA系响应式编程的工具包Vert.x介绍背靠Eclipse的Eclipse Vert.x是一个用于在JVM上构建响应式应用程序的工具包。定位上与Spring Boot不抵触,甚至能够将Vert.x联合Spring Boot应用。泛滥Vert.x模块提供了大量微服务的组件,在很多人眼里是一种微服务架构的抉择。 华为微服务框架Apache ServiceComb就是以Vert.x为底层框架实现的,在"基准测试网站TechEmpower"中,Vert.x的体现也非常亮眼。 压测30秒压测前的内存占用 如图,内存占用65M。 压测时的内存占用 如图,内存占139M,CPU占2.1%,给人的感觉仿佛并没有进行压测。 概览 总结Vert.x单个服务打包实现后大概7M左右的JAR,不依赖Tomcat、Jetty之类的容器,间接在JVM上跑。 Vert.x耗费的资源很低,感觉一个1核2G的服务器曾经可能部署许多个Vert.x服务。除去编码方面的问题,真心合乎小我的项目和小模块。git市场上曾经呈现了基于Vert.x实现的开源网关- VX-API-Gateway帮忙文档 https://duhua.gitee.io/vx-api... 对多语言反对,很适宜小型我的项目疾速上线。 启动工夫不到1秒:Started Vert.x in 0.274 seconds (JVM running for 0.274) JAVA系其余微服务框架SparkJava jar比拟小,大概10M占内存小,大概30~60MB;性能还能够,与Spring Boot相仿;Micronaut Grails团队新宠;能够用 Java、Groovy 和 Kotlin 编写的基于微服务的应用程序;相比Spring Boot曾经比拟全面;性能较优,编码方式与Spring Boot比拟相似;启动工夫和内存耗费方面比其余框架更高效;多语言;依赖注入;内置多种云本地性能;很新,刚公布1.0.0Javalin 上手极为容易;灵便,能够兼容同步和异步两种编程思路;JAR小,4~5M;多语言;有KOA的影子;只有大概2000行源代码,源代码足够简略,能够了解和修复;合乎当今趋势;多语言;嵌入式服务器Jetty;Quarkus ...

January 7, 2022 · 1 min · jiezi

关于springboot:Spring-Boot-集成-Swagger2构建强大的-API-文档

前言不论你是从事前端还是后端开发,置信都不免被接口文档折磨过。如果你是一个前端开发者,可能你会常常发现后端给的接口文档跟理论代码有所出入。而假如你是一个后端开发者,你可能又会感觉本人开发后端接口曾经够烦的了,还要破费大量精力去编写和保护接口文档,所以不免有时候会更新不及时。这就可能造成了前后端互相不了解,最初甚至吵起来,哈哈哈 。 这时候咱们就会想,有没有一款工具,能让咱们疾速实现编写接口文档。这个工具既能保障咱们的接口文档实时更新,也能保障咱们不必花过多工夫去保护,就像写正文那么简略。 既然这是大多数前后端程序员的一大痛点,那必须得有一个解决方案吧。而这个计划应用的人多了,缓缓就成了一种标准,大家都默认应用这个计划,从而解决前后端接口文档不同步的问题,而这就是咱们明天的配角 - Swagger 的由来。 通过应用 Swagger,咱们只须要依照它所给定的一系列标准去定义接口以及接口的相干信息,而后它就能帮咱们主动生成各种格局的接口文档,不便前后端开发者进行前后端联调。同时,如果咱们的代码接口有所变动,只须要更新 Swagger 的形容,它就能进行实时更新,做到理论代码和接口文档的一致性。 Swagger 简介Swagger 是什么Swagger 是一种接口描述语言,次要用于生成、形容、调用以及可视化 RESTful 格调的 Web 服务接口文档。以前的我的项目可能更多的是前后端未离开同时进行开发,所以接口文档可能不是那么重要。但当初支流的我的项目根本都是前后端拆散,如果前后端没有沟通好,就有可能导致接口文档更新不及时,造成一些不必要的麻烦。而艰深地讲,Swagger 就是帮咱们写接口文档的。它不仅能主动生成实时接口文档,还能生成测试用例,不便咱们进行测试。 Swagger 次要提供了如下几种开源工具: Swagger EditorSwagger 所提供的的编辑器,次要用于编辑 Swagger 形容文件,反对实时预览形容文件更新后的成果,相似于咱们的 Markdown 编辑器,右边编写源码,左边就能够进行实时预览。该编辑器不仅提供在线应用,还反对本地部署。 Swagger UI提供可视化的 UI 页面,用于展现 Swagger 的形容文件。接口的调用方、测试等都能够通过该页面查阅接口的相干信息,并且进行简略的接口申请测试。 Swagger Codegen通过应用该工具,能够将 Swagger 的形容文件生成 HTML 和 CWIKI 模式的接口文档,而且还能生成针对多种不同语言的服务端和客户端的代码。 Swagger UI平时和咱们打交道最多的,可能就是 Swagger UI 这个工具了,它次要用于显示接口文档。依据咱们代码中依照 Swagger 标准所设置的形容,主动生成接口阐明文档。一个简略的示例如下: Spring Boot 集成 Swagger创立 Spring Boot 我的项目通过以上对 Swagger 简略的介绍之后,咱们来看看如何在 Spring Boot 我的项目中应用 Swagger。 首先须要创立一个简略的 Spring Boot 我的项目,如果你还不晓得如何创立,能够参考我之前的一篇文章 创立 Spring Boot 我的项目的 3 种形式。 ...

January 6, 2022 · 2 min · jiezi

关于springboot:SpringBoot-如何统一后端返回格式

SpringBoot 如何对立后端返回格局为什么要对SpringBoot返回对立的标准格局在默认情况下,SpringBoot的返回格局常见的有三种: 第一种:返回 String @GetMapping("/hello")public String getStr(){ return "hello,javadaily";}复制代码此时调用接口获取到的返回值是这样: hello,javadaily复制代码第二种:返回自定义对象 @GetMapping("/aniaml")public Aniaml getAniaml(){ Aniaml aniaml = new Aniaml(1,"pig"); return aniaml;}复制代码此时调用接口获取到的返回值是这样: { "id": 1, "name": "pig"}复制代码第三种:接口异样 @GetMapping("/error")public int error(){ int i = 9/0;return i;}复制代码此时调用接口获取到的返回值是这样: { "timestamp": "2021-07-08T08:05:15.423+00:00", "status": 500, "error": "Internal Server Error", "path": "/wrong"}复制代码基于以上种种情况,如果你和前端开发人员联调接口她们就会很懵逼,因为咱们没有给他一个对立的格局,前端人员不知道如何处理返回值。 还有甚者,有的同学比如小张喜爱对后果进行封装,他使用了Result对象,小王也喜爱对后果进行包装,然而他却使用的是Response对象,当出现这种情况时我相信前端人员肯定会抓狂的。 所以咱们我的项目中是需要定义一个对立的标准返回格局的。 定义返回标准格局一个标准的返回格局至多蕴含3部分: status 状态值:由后端对立定义各种返回后果的状态码message 描述:本次接口调用的后果描述data 数据:本次返回的数据。{ "status":"100", "message":"操作胜利", "data":"hello,javadaily"}复制代码当然也可能按需加入其余扩大值,比如咱们就在返回对象中增加了接口调用工夫 timestamp: 接口调用工夫定义返回对象@Datapublic class ResultData<T> { /* 后果状态 ,具体状态码参见ResultData.java/ private int status; private String message; private T data; private long timestamp ; public ResultData (){ ...

January 5, 2022 · 1 min · jiezi

关于springboot:SpringBoot整合Ehcache3

前言公司部门老我的项目要迁徙降级java版本,须要进行缓存相干操作,原框架未反对这部分,通过调研java相干缓存计划大抵分为ehcache和redis两种,redis的value最大值为500mb且超过1mb会对存取有性能影响,业务零碎须要反对列表查问缓存就不可避免的波及到大量的数据存取过滤,ehcache反对内存+磁盘缓存不必放心缓存容量问题,所以框架初步版本决定集成ehcache3,设计流程构造如下图所示 缓存配置maven援用 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>个性化配置 #缓存配置 cache: ehcache: heap: 1000 offheap: 100 disk: 500 diskDir: tempfiles/cache/@Component@ConfigurationProperties("frmae.cache.ehcache")public class EhcacheConfiguration { /** * ehcache heap大小 * jvm内存中缓存的key数量 */ private int heap; /** * ehcache offheap大小 * 堆外内存大小, 单位: MB */ private int offheap; /** * 磁盘长久化目录 */ private String diskDir; /** * ehcache disk * 长久化到磁盘的大小, 单位: MB * diskDir无效时才失效 */ private int disk; public EhcacheConfiguration(){ heap = 1000; offheap = 100; disk = 500; diskDir = "tempfiles/cache/"; }}代码注入配置因为springboot默认缓存优先注入redis配置,所以须要手动申明bean进行注入,同时ehcache的value值必须反对序列化接口,不能应用Object代替,这里申明一个缓存基类,所有缓存value对象必须继承该类 ...

January 3, 2022 · 4 min · jiezi

关于springboot:spring-boot-buildimage-开启代理

原文地址  因为大陆网络的起因拜访github极大可能会超时导致失败,个别都是挂着梯子拜访。梯子的原理有些不雷同次要看客户端实现,有的是在本地开启代理端口而后向零碎写入http_proxy 等变量,有的是拦挡所有流量而后代理客户端断定该不该走proxy。当然能第二种是最好的,然而不是所有代理客户端都是这么做的。开发软件读取代理变量也只是一个俗成的约定而不是一个强制的规定。    说完这个再说回来 spring-boot maven插件打包docker镜像失败的问题,始终排查不到起因,只有执行mvn spring-boot:build-image 工作就会陷入期待,从github下载所需的资源,等了之后再报连贯超时之类的音讯,零碎自身是开启代理的,然而没有走代理,第一工夫想到的是要配置idea代理,因为是在idea外面的终端执行的,配置之后仍然不行,又想到在maven配置中配置代理因为这是个maven插件,配置之后仍然不行,是在想不出方法google了一下找到了官网文档官网文档上写着的几个大志好像在讥笑我是个伞兵,无奈了折腾了一天的问题,世间文档上写的很明确,配置之后查看代理软件流量变动确实是走过去了,下载过程一瞬间就实现了。之后遇到这类文件还是多看看文档吧q.q <project> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.0.RELEASE</version> <configuration> <image> <env> <HTTP_PROXY>http://proxy.example.com</HTTP_PROXY> <HTTPS_PROXY>https://proxy.example.com</HTTPS_PROXY> </env> </image> </configuration> </plugin> </plugins> </build></project>

December 30, 2021 · 1 min · jiezi

关于springboot:Spring-Boot-项目鉴权的-4-种方式

转自:枕边书 链接:https://zhenbianshu.github.io/ 文章介绍了spring-boot中实现通用auth的四种形式,包含 传统AOP、拦截器、参数解析器和过滤器,并提供了对应的实例代码,最初简略总结了下他们的执行程序。 前言最近始终被无尽的业务需要吞没,没工夫喘息,终于接到一个能让我冲破代码舒服区的活儿,解决它的过程十分波折,一度让我狐疑人生,不过播种也很大,代码方面不显著,但感觉本人抹掉了 java、Tomcat、Spring 始终挡在我眼前的一层纱。对它们的了解上了一个新的档次。 好久没输入了,于是挑一个方面总结一下,心愿在梳理过程中再理解一些其余的货色。 因为 Java 凋敝的生态,上面每一个模块都有大量的文章专门讲述。所以我选了另外一个角度,从理论问题登程,将这些扩散的常识串联起来,各位能够作为一个综述来看。各个模块的极致具体介绍,大家能够去翻官网文档或看网络上的其余博客。 需要很简略清晰,跟产品们提的妖艳需要一点也不一样:在咱们的 web 框架里增加一个 通用的 appkey 白名单校验性能,心愿它的扩展性更好一些。 这个 web 框架是部门前驱者基于 spring-boot 实现的,介于业务和 Spring 框架之间,做一些偏差于业务的通用性性能,如 日志输入、性能开关、通用参数解析等。平时是对业务通明的,最近始终忙于把需要做好,代码写好,甚至从没留神过它的存在。 传统AOP对于这种需要,首先想到的当然是 Spring-boot 提供的 AOP 接口,只须要在 Controller 办法前增加切点,而后再对切点进行解决即可。 实现其应用步骤如下: 应用  @Aspect 申明一下切面类  WhitelistAspect;在切面类内增加一个切点  whitelistPointcut(),为了实现此切点灵便可拆卸的能力,这里不应用  execution 全副拦挡,而是增加一个注解  @Whitelist,被注解的办法才会校验白名单。在切面类中应用 spring 的 AOP 注解  @Before 申明一个告诉办法  checkWhitelist() 在 Controller 办法被执行之前校验白名单。切面类伪代码如下: @Aspectpublic class WhitelistAspect {    @Before(value = "whitelistPointcut() && @annotation(whitelist)") public void checkAppkeyWhitelist(JoinPoint joinPoint, Whitelist whitelist) {     checkWhitelist();     // 可应用 joinPoint.getArgs() 获取Controller办法的参数     // 能够应用 whitelist 变量获取注解参数 }       @Pointcut("@annotation(com.zhenbianshu.Whitelist)") public void whitelistPointCut() { }}在Controller办法上增加  @Whitelist 注解实现性能。 扩大本例中应用了 注解 来申明切点,并且我实现了通过注解参数来申明要校验的白名单,如果之后还须要增加其余白名单的话,如通过 UID 来校验,则能够为此注解增加  uid() 等办法,实现自定义校验。 此外,spring 的 AOP 还反对  execution(执行办法) 、bean(匹配特定名称的 Bean 对象的执行办法)等切点申明办法和  @Around(在指标函数执行中执行) 、@After(办法执行后) 等告诉办法。 如此,性能曾经实现了,但领导并不称心=\_=,起因是我的项目中 AOP 用得太多了,都用滥了,倡议我换一种形式。嗯,只好搞起。 InterceptorSpring 的 拦截器(Interceptor) 实现这个性能也十分适合。顾名思义,拦截器用于在 Controller 内 Action 被执行前通过一些参数判断是否要执行此办法,要实现一个拦截器,能够实现 Spring 的  HandlerInterceptor 接口。 ...

December 30, 2021 · 1 min · jiezi

关于springboot:2spring系列之404异常的捕获

回顾我在之前公布了一篇spring对立返回的文章,最初提到是无奈捕捉404异样的,这里咱们先来测试一下 @RestControllerpublic class TestController { @GetMapping("/test") public String insert22() { return "hello"; }}浏览器申请试一下 http://localhost:8080/xxx 报错 # Whitelabel Error PageThis application has no explicit mapping for /error, so you are seeing this as a fallback.Wed Dec 29 10:14:36 CST 2021There was an unexpected error (type=Not Found, status=404).springboot的解决形式springboot解决这个404的异样是在 BasicErrorController中解决的 @Controller@RequestMapping("${server.error.path:${error.path:/error}}")public class BasicErrorController extends AbstractErrorController { ........... @Override public String getErrorPath() { return null; } @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } // 蕴含申请头 "Accept": "application/json" 会往这里走 @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity<>(status); } Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity<>(body, status); } .............}只有申请门路/error就能够进去到errorHtml这个办法,在浏览器申请http://localhost:8080/error就能够进入这个办法 ...

December 29, 2021 · 3 min · jiezi

关于springboot:Spring-Boot-Serverless-实战系列架构篇-光速入门函数计算

作者 | 西流(阿里云函数计算专家) Spring Boot 是基于 Java Spring 框架的套件,它预装了 Spring 一系列的组件,开发者只须要很少的配置即可创立独立运行的应用程序。 在云原生体系中,有大量的平台都能够运行 Spring Boot 利用,例如虚拟机、容器等。但其中最有吸引力的,是以 Serverless 的形式运行 Spring Boot 利用。我将通过《Spring Boot Serverless 实战》系列文章,从架构,部署,监控、性能、平安等 5 个篇章来剖析 Serverless 平台运行 SpringBoot 利用的优劣。 为了让剖析更有代表性,我抉择了 Github 上 star 数超过 50k 的电商利用 Mall 作为示例。这是该系列文章的第一篇,本文会从架构角度对 Spring Boot 利用的 Serverless 化进行剖析。 Mall 架构简介 Mall 是一套电商零碎,包含前台商城零碎及后盾管理系统,基于 Spring Boot + MyBatis 实现。前台商城零碎蕴含首页门户、商品举荐、商品搜寻、商品展现、购物车、订单流程、会员中心、客户服务、帮忙核心等模块。后盾管理系统蕴含商品治理、订单治理、会员治理、促销治理、经营治理、内容治理、统计报表、财务管理、权限治理、设置等模块。 Mall 的架构如下图所示,分为网关层,应用层,数据存储层。申请首先通过网关达到 Spring Boot 应用服务。网关实现负载平衡,流量管制等性能。应用层蕴含 3 个 Spring Boot 利用和1个前端利用: mall-admin:后盾商城管理系统mall-portal:前台商城零碎mall-search:于Elasticsearch的商品搜寻零碎Mall-admin-web:mall-admin 的前端展现,基于 Vue+Element 实现Mall 应用了 MySQL,Redis,MongoDB,ElaisticSearch 等多种数据库。次要业务数据存储在 MySQL,缓存数据存储在 Redis,用户行为剖析数据存储在 MongoDB,搜寻数据存储在 ElasticSearch 中。Spring Boot 应用服务间应用 RabbitMQ 实现异步通信。 ...

December 28, 2021 · 1 min · jiezi

关于springboot:springboot实现万能文件在线预览已开源真香

举荐一个用Spring Boot搭建的文档在线预览解决方案: kkFileView,一款成熟且开源的文件文档在线预览我的项目解决方案,对标业内付费产品有【永中office】【office365】【idocv】等,收费! 我的项目个性反对 office, pdf, cad 等办公文档反对 txt, xml(渲染), md(渲染), java, php, py, js, css 等所有纯文本反对 zip, rar, jar, tar, gzip 等压缩包反对 jpg, jpeg, png, gif, tif, tiff 等图片预览(翻转,缩放,镜像)应用 spring-boot 开发,预览服务搭建部署十分简便rest 接口提供服务,跨语言、跨平台个性(java,php,python,go,php,....)都反对,利用接入简略不便形象预览服务接口,不便二次开发,十分不便增加其余类型文件预览反对最最重要 Apache 协定开源,代码 pull 下来想干嘛就干嘛官网及文档地址:https://kkfileview.keking.cn 在线体验会不定时停用地址:https://file.keking.cn 我的项目文档(Project documentation)具体wiki文档:https://gitee.com/kekingcn/fi...中文文档:https://gitee.com/kekingcn/fi...English document:https://gitee.com/kekingcn/fi...文档预览成果1. 文本预览反对所有类型的文本文档预览, 因为文本文档类型过多,无奈全副枚举,默认开启的类型如下  txt,html,htm,asp,jsp,xml,json,properties,md,gitignore,log,java,py,c,cpp,sql,sh,bat,m,bas,prg,cmd 文本预览成果如下 文本预览成果如下 2. 图片预览反对jpg,jpeg,png,gif等图片预览(翻转,缩放,镜像),预览成果如下 图片预览 3. word文档预览反对doc,docx文档预览,word预览有两种模式:一种是每页word转为图片预览,另一种是整个word文档转成pdf,再预览pdf。两种模式的实用场景如下 图片预览:word文件大,前台加载整个pdf过慢pdf预览:内网拜访,加载pdf快图片预览模式预览成果如下 word文档预览1 pdf预览模式预览成果如下 word文档预览2 4. ppt文档预览反对ppt,pptx文档预览,和word文档一样,有两种预览模式 图片预览模式预览成果如下 ppt文档预览1 pdf预览模式预览成果如下 ppt文档预览2 5. pdf文档预览反对pdf文档预览,和word文档一样,有两种预览模式 ...

December 28, 2021 · 1 min · jiezi

关于springboot:面试官什么是-YAML和-Spring-Boot-有什么关系

起源:blog.csdn.net/chenlixiao0071、什么是YAMLYAML是"YAML Ain’t a Markup Language"(YAML不是一种标记语言)的递归缩写。YAML的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。次要强度这种语音是以数据为核心,而不是以标记语言为重心,例如像xml语言就会应用大量的标记。 YAML是一个可读性高,易于了解,用来表白数据序列化的格局。它的语法和其余高级语言相似,并且能够简略表白清单(数组)、散列表,标量等数据状态。它应用空白符号缩进和大量依赖外观的特色,特地适宜用来表白或编辑数据结构、各种配置文件等。 YAML的配置文件后缀为 .yml,例如Springboot我的项目中应用到的配置文件 application.yml 。 2、根本语法YAML应用可打印的Unicode字符,可应用UTF-8或UTF-16。数据结构采纳键值对的模式,即 键名称: 值,留神冒号前面要有空格。每个清单(数组)成员以单行示意,并用短杠+空白(- )起始。或应用方括号([]),并用逗号+空白(, )离开成员。每个散列表的成员用冒号+空白(: )离开键值和内容。或应用大括号({ }),并用逗号+空白(, )离开。字符串值个别不应用引号,必要时可应用,应用双引号示意字符串时,会本义字符串中的特殊字符(例如\n)。应用单引号时不会本义字符串中的特殊字符。大小写敏感0d9f12e0ed43f02a97c0941c5d325d989c6af5fb0276dc7&scene=21#wechat_redirect)应用缩进示意层级关系,缩进不容许应用tab,只容许空格,因为有可能在不同零碎下tab长度不一样缩进的空格数能够任意,只有雷同层级的元素左对齐即可在繁多文件中,可用间断三个连字号(—)辨别多个文件。还有选择性的间断三个点号(…)用来示意文件结尾。'\#'示意正文,能够呈现在一行中的任何地位,单行正文在应用逗号及冒号时,前面都必须接一个空白字符,所以能够在字符串或数值中自在退出分隔符号(例如:5,280或http://www.wikipedia.org)而不须要应用引号。3、数据类型纯量(scalars):单个的、不可再分的值对象:键值对的汇合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)数组:一组按秩序排列的值,又称为序列(sequence) / 列表(list)标量 标量是最根底的数据类型,不可再分的值,他们个别用于示意单个的变量,有以下七种: [字符串[布尔值][整数][浮点数][Null][工夫][日期]# 字符串 string.value: Hello!我是陈皮! # 布尔值,true或false boolean.value: true boolean.value1: false # 整数 int.value: 10 int.value1: 0b1010_0111_0100_1010_1110 # 二进制 # 浮点数 float.value: 3.14159 float.value1: 314159e-5 # 迷信计数法 # Null,~代表null null.value: ~ # 工夫,工夫应用ISO 8601格局,工夫和日期之间应用T连贯,最初应用+代表时区 datetime.value: !!timestamp 2021-04-13T10:31:00+08:00 # 日期,日期必须应用ISO 8601格局,即yyyy-MM-dd date.value: !!timestamp 2021-04-13这样,咱们就能够在程序中引入了,如下: @RestController @RequestMapping("demo") public class PropConfig {     @Value("${string.value}")     private String stringValue;     @Value("${boolean.value}")     private boolean booleanValue;     @Value("${boolean.value1}")     private boolean booleanValue1;     @Value("${int.value}")     private int intValue;     @Value("${int.value1}")     private int intValue1;     @Value("${float.value}")     private float floatValue;     @Value("${float.value1}")     private float floatValue1;     @Value("${null.value}")     private String nullValue;     @Value("${datetime.value}")     private Date datetimeValue;     @Value("${date.value}")     private Date datevalue; }Spring Boot 根底就不介绍了,举荐下这个实战教程:https://github.com/javastacks... ...

December 27, 2021 · 1 min · jiezi

关于springboot:SpringCloud-服务默认OUTOFSERVICE

背景: 产品高速迭代,通常随同着高频次的版本公布。部署新版上线须要进行验证,不想让服务间接被客户端关注到(即服务默认注册到eureka,并且是UP状态)并进行调用。起因解析: 所有服务的状态保留在注册核心 Eureka Server 中。一个服务要想获取其余服务的实例列表和状态,须要定时从 Eureka Server 中获取并缓存下来,默认工夫30秒发动一次,缓存Server 状态申请由 Eureka Client 发动,而不是基于长连贯或者 Eureka Server 被动推送,所以无奈立刻晓得其余服务状态变更。解决方案: 1、上线状态置为 OUT-OF-SERVICE,验证通过后手动更改为UP(举荐)2、上线状态为UP,手动更改状态为 OUT-OF-SERVICE (不举荐)3、间接勾销服务主动向erueka注册(须要改源码,不举荐)这里咱们举荐第一种计划,具体操作如下: 1、批改配置文件,批改eureka.instance.initial-status:out-of-service ![image.png](/img/bVcWYv7)2、做验证,swagger 或者 mq 只生产一条生产等,形式不具体开展说了,这里不是本文重点3、验证通过,手动批改服务状态为UP,验证不通过,也不会有流量进来.上线形式如下: http://【eureka服务ip:port】/eureka/apps/【服务名】/【实例名】/status?value=UP批改为大家公司罕用的形式即 : curl -X PUT "http://admin:root123@eureka.test.com/eureka/apps/DEMO-PROVIDER/172.16.XX.XXX:8080/status?value=UP"

December 27, 2021 · 1 min · jiezi

关于springboot:SpringBoot2-API接口签名实现接口参数防篡改

简介 当初越来越多人关注接口平安,传统的接口在传输的过程中,容易被抓包而后更改外面的参数值达到某些目标。传统的做法是用平安框架或者在代码外面做验证,然而有些零碎是不须要登录的,随时能够调。这时候咱们能够通过对参数进行签名验证,如果参数与签名值不匹配,则申请不通过,间接返回错误信息。 我的项目代码地址: github.com/MrXuan3168/… 测试 启动我的项目GET申请能够用浏览器间接拜访 http://localhost:8080/signTes... A0161DC47118062053567CDD10FBACC6 是 username=admin&password=admin MD5加密后的后果。能够关上 md5jiami.51240.com/ 而后输出 {"password":"admin","username":"admin"} 进行加密验证,json字符串外面,必须保障字段是依照 ascll码进行排序的,username的ascll码 比 password的ascll码 大,所以要放在前面。 关上 postman 进行POST申请测试,申请Url为 http://localhost:8080/signTes... 参数为 { "username":"admin", "password":"admin" }调用过程 波及第三方技术 前端:js-md5(vue md5-npm包)、axios(vue ajax申请npm包) 装置命令 npm install js-md5 npm install axios 后端: fastjson、lombok <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> <scope>compile</scope> </dependency>签名逻辑 前端(客户端): 1.不论GET Url 还是 POST Body 的参数,都转换成 json 对象,java培训用 ascll码排序 对参数排序。2.排序后对参数进行MD5加密,存入 sign 值。3.把 sign 值 放在 申请URL 前面或者 Head头 外面(该我的项目间接放在URL前面)。 ...

December 27, 2021 · 6 min · jiezi

关于springboot:SpringBoot文件分片上传

背景最近好几个我的项目在运行过程中客户都提出文件上传大小的限度是否设置的大一些,用户常常须要上传好几个G的材料文件,如图纸,视频等,并且须要在上传大文件过程中进行优化实时展示进度条,进行技术评估后针对框架文件上传进行扩大降级,扩大接口反对大文件分片上传解决,缩小服务器刹时的内存压力,同一个文件上传失败后能够从胜利上传分片地位进行断点续传,文件上传胜利后再次上传无需期待达到秒传的成果,优化用户交互体验,具体的实现流程如下图所示 文件MD5计算对于文件md5的计算咱们应用spark-md5第三方库,大文件咱们能够分片别离计算再合并节省时间,然而经测试1G文件计算MD5须要20s左右的工夫,所以通过优化咱们抽取文件局部特色信息(文件第一片+文件最初一片+文件批改工夫),来保障文件的绝对唯一性,只须要2s左右,大大提高前端计算效率,对于前端文件内容块的读取咱们须要应用html5的api中fileReader.readAsArrayBuffer办法,因为是异步触发,封装的办法提供一个回调函数进行应用 createSimpleFileMD5(file, chunkSize, finishCaculate) { var fileReader = new FileReader(); var blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice; var chunks = Math.ceil(file.size / chunkSize); var currentChunk = 0; var spark = new SparkMD5.ArrayBuffer(); var startTime = new Date().getTime(); loadNext(); fileReader.onload = function() { spark.append(this.result); if (currentChunk == 0) { currentChunk = chunks - 1; loadNext(); } else { var fileMD5 = hpMD5(spark.end() + file.lastModifiedDate); finishCaculate(fileMD5) } }; function loadNext() { var start = currentChunk * chunkSize; var end = start + chunkSize >= file.size ? file.size : start + chunkSize; fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); } }文件分片切割咱们通过定义好文件分片大小,应用blob对象反对的file.slice办法切割文件,分片上传申请须要同步按程序申请,因为应用了同步申请,前端ui会阻塞无奈点击,须要开启worker线程进行操作,实现后通过postMessage办法传递音讯给主页面告诉ui进度条的更新,须要留神的是,worker线程办法不反对window对象,所以尽量不要应用第三方库,应用原生的XMLHttpRequest对象发动申请,须要的参数通过onmessage办法传递获取 ...

December 26, 2021 · 7 min · jiezi

关于springboot:Springboot国际化消息和源码解读

写在后面在REST接口的实现计划中,后端能够仅仅返回一个code,让前端依据code的内容做自定义的音讯揭示。当然,也有间接显示后端返回音讯的计划。在后端间接返回音讯的计划中,如果要提供多个不同语种国家应用,则须要做国际化音讯的实现。 400 BAD_REQUEST{ "code": "user.email.token", "message": "The email is token."}实现的指标: validation的error code能够依照不同语言响应;自定义error code能够依照不同语言响应;指定默认的语言;版本阐明 spring-boot: 2.1.6.RELEASEsping: 5.1.8.RELEASEjava: openjdk 11.0.13初始化我的项目<dependency>    <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>java增加如下的Controller类和User实体类,其中的ServiceException是一个自定义的异样类,该异样会带上业务处理过程中的异样码。 @Validated@RestController@RequestMapping("/user")public class UserController { private static final String TOKEN_EMAIL = "token@example.com"; @PostMapping public User createUser(@RequestBody @Valid User user) { if (TOKEN_EMAIL.equalsIgnoreCase(user.getEmail())) { String message = String.format("The email %s is token.", user.getEmail()); throw new ServiceException("user.email.token", message); } return user; }}@Datapublic class User { @NotNull @Length(min = 5, max = 12) @Pattern(regexp = "^r.*", message = "{validation.user.username}") private String username; @NotNull @Email private String email; @Range(min = 12, message = "{validation.user.age}") private int age;}Validation中实现国际化信息根本实现在默认的SpringBoot配置中,只有在申请中减少Accept-Language的Header即可实现Validation错误信息的国际化。如下的申请会应用返回中文的错误信息。 ...

December 26, 2021 · 5 min · jiezi

关于springboot:springboot中使用mybatis并进行测试

1. pom.xml 具体pom.xml文件参考blogger源码 其中h2的scope能够是test。 2. springboot应用mybatis进行数据库操作应用mybatis时,须要org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3[compile]依赖。 而后创立一个接口,应用@Mapper标记,mybatis会搜寻此标记,把这个接口解析为数据库操作接口。 package com.ws.product.blogger.dao;import com.ws.product.blogger.dao.pojo.User;import org.apache.ibatis.annotations.Mapper;import org.springframework.stereotype.Component;@Mapper // mybatis用于发现mapper接口@Component // 能够省略。次要是为了在IDEA里UserDaoTest中@Autowired时不报错public interface UserDao { User getById(int id); User getByUsername(String username); // 应用java bean传递多个参数,在xml中能够间接应用bean的property name,即#{username} int insert(User user); int delete(int id); // 两个参数,第二个参数是java bean,应用的时候须要应用#{user.username} int update(int id, User user);}User是一个POJO,用来保留数据库中的数据: package com.ws.product.blogger.dao.pojo;import lombok.Data;@Datapublic class User { private int id; private String username; private String password;}而后在src/main/resources目录下新建mapper目录,再新建UserDao.xml文件,具体的SQL语句就写在这里。这个文件在哪里无所谓,之后通过application.yml配置文件中的mybatis.mapper-locations: classpath:mapper/*.xml属性找到这个文件。 <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.ws.product.blogger.dao.UserDao"><!--namespace是java interface和xml的匹配关系--> <resultMap id="UserMap" type="com.ws.product.blogger.dao.pojo.User"> <id column="id" property="id" jdbcType="INTEGER"/> <result column="username" property="username" jdbcType="VARCHAR"/> <result column="password" property="password" jdbcType="VARCHAR"/> </resultMap> <select id="getById" resultMap="UserMap"> select * from t_user where id = #{id} </select> <select id="getByUsername" resultMap="UserMap"> select * from t_user where username = #{username} </select> <insert id="insert" parameterType="com.ws.product.blogger.dao.pojo.User"> insert into t_user (username, password) values ( #{username}, #{password} ) </insert> <delete id="delete"> delete from t_user where id = #{id} </delete> <update id="update"> update t_user <set> <if test="user.username != null">username = #{user.username},</if> <if test="user.password != null">password = #{user.password}</if> </set> where id = #{id} </update></mapper>这样,mybatis就具备了操作数据库的能力。对于独自一个DAO模块来说,这曾经足够了。如果想要真正执行数据库操作,须要新建一个main函数,应用@SpringBootApplication标记,application.yml中配置spring.datasource.url, username, password, driver-class-name属性,这会主动生成一个sqlSession,mybatis会应用这个sqlSession操作数据库。不要忘了,application.yml中还须要配置mybatis.mapper-locations属性。 ...

December 25, 2021 · 2 min · jiezi

关于springboot:Spring-Boot-版本升级-从-226RELEASE-升级到-251-问题解决方案

我的项目迁徙时,抉择了阿里云 Elasticsearch 服务, 版本是 7.10.0 且不能降级版本 [TOC] Spring Boot 从 2.2.6.RELEASE 降级到 2.5.1 遇到问题如下1、跨域1.1、基于 Spring Security 跨域设置import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors() .and() .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } /** * Spring Security 15.8 CORS * The easiest way to ensure that CORS is handled first is to use the CorsFilter. Users can integrate the CorsFilter with Spring Security by providing a CorsConfigurationSource using the following: * * @return * @see <a href="https://docs.spring.io/spring-security/site/docs/5.4.10/reference/html5/#cors"> * The easiest way to ensure that CORS is handled first is to use the CorsFilter. * Users can integrate the CorsFilter with Spring Security by providing a CorsConfigurationSource using the following:</a> */ @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); // 如果设置 true, setAllowedOrigins 须要指定具体域名 // 如果色湖 false, setAllowedOrigins 须要指定具体域名能够用通配符("*") // spring boot 2.4.0 新增 validateAllowCredentials 函数 // org.springframework:spring-web:jar:5.3.8:compile // @since 5.3 // configuration.setAllowCredentials(true); configuration.setAllowedOrigins(Collections.singletonList("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); configuration.applyPermitDefaultValues(); configuration.addAllowedHeader("*"); configuration.addAllowedMethod("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; }}1.2、非 Spring Security 形式package com.example.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;import org.springframework.web.filter.CorsFilter;import org.springframework.web.servlet.config.annotation.CorsRegistry;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;import java.util.ArrayList;import java.util.List;/** * 大抵通用,能够查看官网文档适状况解决 * */@Configurationpublic class CorsConfig extends WebMvcConfigurationSupport { private CorsConfiguration corsConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); corsConfiguration.setAllowCredentials(true); corsConfiguration.setMaxAge(3600L); return corsConfiguration; } @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowCredentials(true) .allowedMethods("GET", "POST", "DELETE", "PUT") .maxAge(3600); } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfig()); CorsConfiguration config = new CorsConfiguration(); // 是否容许申请带有验证信息 config.setAllowCredentials(true); // 容许拜访的客户端域名 // (springboot2.4以上的退出这一段可解决 allowedOrigins cannot contain the special value "*"问题) List<String> allowedOriginPatterns = new ArrayList<>(); allowedOriginPatterns.add("*"); config.setAllowedOriginPatterns(allowedOriginPatterns); // 设置拜访源地址 config.addAllowedOrigin("*"); 设置拜访源申请头 config.addAllowedHeader("*"); // 设置拜访源申请办法 config.addAllowedMethod("*"); // 对接口配置跨域设置 source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } /** * 发现如果继承了WebMvcConfigurationSupport,则在yml中配置的相干内容会生效。 须要从新指定动态资源 * * @param registry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations( "classpath:/static/"); registry.addResourceHandler("swagger-ui.html") .addResourceLocations( "classpath:/META-INF/resources/"); registry.addResourceHandler("doc.html") .addResourceLocations( "classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations( "classpath:/META-INF/resources/webjars/"); super.addResourceHandlers(registry); }}2、Elasticsearch @Document 的 type 属性被废除原来示例 ...

December 23, 2021 · 7 min · jiezi

关于springboot:SpringBoot整合高德地图-天气查询

申请key 登录高德,注册,增加利用,创立key 官网api: https://lbs.amap.com/api/webs...调用步骤:第一步,申请”web服务 API”密钥(Key);第二步,拼接HTTP申请URL,第一步申请的Key需作为必填参数一起发送;第三步,接管HTTP申请返回的数据(JSON或XML格局),解析数据。如无非凡申明,接口的输出参数和输入数据编码全副对立为UTF-8。 最次要的也是获取到key 相干代码pom.xml <!--httpclient--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.4</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.78</version> </dependency><dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version></dependency>举荐浏览:《Spring Boot+Vue全栈开发实战》,点此获取PDF文件完整版 application.properties server.port=2080#The config for HttpClienthttp.maxTotal=300http.defaultMaxPerRoute=50http.connectTimeout=1000http.connectionRequestTimeout=500http.socketTimeout=5000http.staleConnectionCheckEnabled=truegaode.key = 申请的keyHttpClientConfig package com.zjy.map.config;import lombok.Data;import org.apache.http.client.HttpClient;import org.apache.http.client.config.RequestConfig;import org.apache.http.conn.HttpClientConnectionManager;import org.apache.http.impl.client.HttpClientBuilder;import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.client.ClientHttpRequestFactory;import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.StringHttpMessageConverter;import org.springframework.web.client.RestTemplate;import java.nio.charset.Charset;import java.util.List;@Data@Configuration@ConfigurationProperties(prefix = "http", ignoreUnknownFields = true)public class HttpClientConfig {private Integer maxTotal;// 最大连贯private Integer defaultMaxPerRoute;// 每个host的最大连贯private Integer connectTimeout;// 连贯超时工夫private Integer connectionRequestTimeout;// 申请超时工夫private Integer socketTimeout;// 响应超时工夫/** * HttpClient连接池 * @return */@Beanpublic HttpClientConnectionManager httpClientConnectionManager() { PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); connectionManager.setMaxTotal(maxTotal); connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute); return connectionManager;}/** * 注册RequestConfig * @return */@Beanpublic RequestConfig requestConfig() { return RequestConfig.custom().setConnectTimeout(connectTimeout) .setConnectionRequestTimeout(connectionRequestTimeout).setSocketTimeout(socketTimeout) .build();}/** * 注册HttpClient * @param manager * @param config * @return */@Beanpublic HttpClient httpClient(HttpClientConnectionManager manager, RequestConfig config) { return HttpClientBuilder.create().setConnectionManager(manager).setDefaultRequestConfig(config) .build();}@Beanpublic ClientHttpRequestFactory requestFactory(HttpClient httpClient) { return new HttpComponentsClientHttpRequestFactory(httpClient);}/** * 应用HttpClient来初始化一个RestTemplate * @param requestFactory * @return */@Beanpublic RestTemplate restTemplate(ClientHttpRequestFactory requestFactory) { RestTemplate template = new RestTemplate(requestFactory); List<HttpMessageConverter<?>> list = template.getMessageConverters(); for (HttpMessageConverter<?> mc : list) { if (mc instanceof StringHttpMessageConverter) { ((StringHttpMessageConverter) mc).setDefaultCharset(Charset.forName("UTF-8")); } } return template;}举荐浏览:《Spring Boot+Vue全栈开发实战》,点此获取PDF文件完整版 ...

December 21, 2021 · 4 min · jiezi

关于springboot:GitHub疯传15k的SpringBootvue全栈开发实战项目太香了

Spring 作为一个轻量级的容器,在JavaEE开发中失去了宽泛的利用,然而Spring 的配置繁琐臃肿,在和各种第三方框架进行整合时代码量都十分大,并且整合的代码大多是反复的,为了使开发者可能疾速上手Spring,利用Spring框架疾速搭建JavaEE我的项目,Spring Boot应运而生。 Spring Boot 中对一些罕用的第三方库提供了默认的自动化配置计划,使得开发者只须要很少的Spring 配置就能运行一个残缺的Java EE利用。Spring Boot我的项目能够采纳传统的计划打成war包,而后部署到Tomcat 中运行。也能够间接打成可执行jar包,这样通过java -jar命令就能够启动一个 Spring Boot 我的项目。 前后端拆散家喻户晓当初开发都是前后端拆散。其中用到的技术最火的无疑是后端选用Spring Boot,前端选用Vue技术,的SpringBoot+vue组合了! 如何利用SpringBoot+vue搭建本人的我的项目?SpringBoot尽管易上手,然而真要用SpringBoot+Vue去搭建一个本人的我的项目的话对于一些刚入行没有多久的小伙伴来说还是比拟艰难的!所以明天LZ顺便为大家带来了一份阿里强推的SpringBoot+Vue全栈开发实战文档跟一个用SpringBoot+Vue从零开始搭建的我的项目(开发手册+源码全副为大家整顿好了)心愿能对大家有所帮忙! 须要的小伙伴点赞+转发之后间接私信【戳此处】即可获取收费残缺文档下载方式!第1章:Spring Boot入门 第2章:Spring Boot根底配置 第3章:Spring Boot整合视图层技术 第4章:Spring Boot整合Web开发 须要的小伙伴点赞+转发之后间接私信【戳此处】即可获取收费残缺文档下载方式!第5章:Spring Boot整合长久层技术 第6章:Spring Boot整合nosql 第7章:构建restful服务 须要的小伙伴点赞+转发之后间接私信【戳此处】即可获取收费残缺文档下载方式! 第8章:开发者工具与单元测试 第9章:Spring Boot缓存 第10章:Spring Boot平安治理 第11章:Spring Boot整合Web Socket 第12章:音讯服务 第13章:企业开发 须要的小伙伴点赞+转发之后间接私信【戳此处】即可获取收费残缺文档下载方式!第14章:利用监控 第15章:我的项目构建与部署 第16章:微人事我的项目实战 须要的小伙伴点赞+转发之后间接【戳此处】即可获取收费残缺文档下载方式!

December 17, 2021 · 1 min · jiezi

关于springboot:springbootstater-redis-lua-实现一个简单的发号器3-实现篇

接着上一篇 php + redis + lua 实现一个简略的发号器(1)-- 原理篇,本篇讲一下spring-boot-starter 版本的发号器的具体实现。 1、基础知识发号器的实现次要用到了上面的一些知识点: 1. php中的位运算的操作和求值 2. 计算机原码、补码、反码的基本概念 3. redis中lua脚本的编写和调试 4. 如何本人定一个spring-boot-starter 2、具体实现├── pom.xml├── src│   ├── main│   │   ├── java│   │   │   └── com│   │   │   └── srorders│   │   │   └── starter│   │   │   ├── SignGenerator.java│   │   │   ├── UuidConfiguration.java│   │   │   └── UuidProperties.java│   │   └── resources│   │   ├── META-INF│   │   │   └── spring.factories│   │   └── application.yml│   └── test│   └── java│   └── com│   └── srorders│   └── starter│   └── SignGeneratorTest.javapom的相干依赖: ...

December 8, 2021 · 4 min · jiezi

关于springboot:SpringBoot-在线协同办公小程序开发-全栈式项目实战完结ujfuytf

download:SpringBoot 在线协同办公小程序开发 全栈式我的项目实战【完结】结构字符串 你会常常需要打印字符串。要是有很多变量,防止上面这样: name = "Raymond" age = 22 born_in = "Oakland, CA" string = "Hello my name is " + name + "and I'm " + str(age) + " years old. I was born in " + born_in + "." print(string)额,这看起来多乱呀?你能够用个丑陋简洁的办法来代替, .format 。 这样做: 前言去年11月在PyCon China 2018 杭州站分享了 ,讲述了如何通过修改 Python 解释器达到加解密 Python 代码的目标。然而因为笔者迁延症发生,一直没有及时整顿成文字版,现在终于战胜了它,才有了本文。 本文将首先介绍下现有源码加密打算的思路、方法、长处与不足,进而介绍如何通过定制 Python 解释器来达到更好地加解密源码的目标。 现有加密打算因为 Python 的动静个性和开源个性,导致 Python 代码很难做到很好的加密。社区中的一些声音认为这样的限度是事实,应该通过法律手段而不是加密源码达到商业保护的目标;而还有一些声音则是不论如何都心愿能有一种手段来加密。于是乎,人们想出了各种或加密、或混淆的打算,借此来达到保护源码的目标。 常见的源码保护手段有如下几种: 发行 .pyc 文件代码混淆使用 py2exe使用 Cython上面来简略说说这些打算。 发行 .pyc 文件思路大家都知道,Python 解释器在执行代码的过程中会首先生成 .pyc 文件,而后解释执行 .pyc 文件中的内容。当然了,Python 解释器也能够间接执行 .pyc 文件。而 .pyc 文件是二进制文件,无奈间接看出源码内容。如果发行代码到客户环境时都是 .pyc 而非 .py 文件的话,那岂不是能达到保护 Python 代码的目标? ...

December 8, 2021 · 2 min · jiezi

关于springboot:SpringBoot基础之JDBCTemplate

最根底的数据库操作,SpringBoot省去了那一堆创立Connection,连贯敞开操作,当初应用起来不能更不便了。第一步须要引入maven包,因为我的是mysql所以,引入了mysql的包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>配置数据源,配置本人数据的地址账号密码信息 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/springboot username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver通过注入JdbcTemplate来托管了一起数据库的连贯开释等操作。第一种jdbcTemplate间接通过insert办法写入语句执行,然而此时返回的后果只是执行胜利还是失败。如果须要等到生成的ID的值,须要通过SimpleJdbcInsert的executeAndReturnKey办法来获取后果。 @Repository("rideRepository")public class RideRepositoryImpl implements RideRepository {@Autowiredprivate JdbcTemplate jdbcTempleate; public Ride createRide(Ride ride){ //第一种不返回插入的数据主动生成的ID值 jdbcTempleate.update("insert into ride (name,duration) values (?,?)", ride.getName(), ride.getDuration()); //第二种能够获取到生成的id值 SimpleJdbcInsert insert = new SimpleJdbcInsert(jdbcTempleate); List<String> columns = new ArrayList<>(); columns.add("name"); columns.add("duration"); insert.setTableName("ride"); insert.setColumnNames(columns); Map<String, Object> data = new HashMap<>(); data.put("name", ride.getName()); data.put("duration", ride.getDuration()); insert.setGeneratedKeyName("id"); Number key = insert.executeAndReturnKey(data); return null;} } 通过jdbcTemplate能够获取到查问后果,通过RowMapper对应到具体的bean,以及能够将RowMapper形象成自定义的RideRowMapper类 ...

December 4, 2021 · 2 min · jiezi

关于springboot:SpringBootWeb应用安全策略实现

背景近期我的项目上线,甲方要求通过平安检测能力进行验收,故针对扫描后果对系统进行了一系列的平安加固,本文对一些常见的平安问题及防护策略进行介绍,提供对应的解决方案 跨站脚本攻打XSS常产生于论坛评论等零碎,当初富文本编辑器已对XSS进行了防护,然而咱们任须要在后端接口进行数据过滤, 常见防护策略是通过过滤器将歹意提交的脚本进行过滤与替换 public class XSSFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //System.out.println("XSSFilter"); String contentType = request.getContentType(); if (StringUtils.isNotBlank(contentType) && contentType.contains("application/json")) { XSSBodyRequestWrapper xssBodyRequestWrapper = new XSSBodyRequestWrapper((HttpServletRequest) request); chain.doFilter(xssBodyRequestWrapper, response); } else { chain.doFilter(request, response); } }}public class XSSBodyRequestWrapper extends HttpServletRequestWrapper { private String body; public XSSBodyRequestWrapper(HttpServletRequest request) { super(request); try{ body = XSSScriptUtil.handleString(CommonUtil.getBodyString(request)); }catch (Exception e){ e.printStackTrace(); } } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes(Charset.forName("UTF-8"))); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; }}public class XSSScriptUtil { public static String handleString(String value) { if (value != null) { Pattern scriptPattern = Pattern.compile("<script>(\\s*.*?)</script>", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll("-"); scriptPattern = Pattern.compile("</script(\\s*.*?)>", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll("-"); scriptPattern = Pattern.compile("<script(\\s*.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll("-"); scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll("-"); scriptPattern = Pattern.compile("e­xpression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll("-"); scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll("-"); scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll("-"); scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll("-"); scriptPattern = Pattern.compile("<+.*(oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|onerror|onerroupdate|onfilterchange|onfinish|onfocus|onfocusin|onfocusout|onhelp|onkeydown|onkeypress|onkeyup|onlayoutcomplete|onload|onlosecapture|onmousedown|onmouseenter|onmouseleave|onmousemove|onmousout|onmouseover|onmouseup|onmousewheel|onmove|onmoveend|onmovestart|onabort|onactivate|onafterprint|onafterupdate|onbefore|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditocus|onbeforepaste|onbeforeprint|onbeforeunload|onbeforeupdate|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|onpaste|onpropertychange|onreadystatechange|onreset|onresize|onresizend|onresizestart|onrowenter|onrowexit|onrowsdelete|onrowsinserted|onscroll|onselect|onselectionchange|onselectstart|onstart|onstop|onsubmit|onunload)+.*=+", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll("-"); // 过滤emoji表情 scriptPattern = Pattern .compile( "[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]", Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll("-"); } return value; }}SQL注入sql注入是零碎最常见的平安问题之一,会导致登陆平安,数据拜访权限平安等,常见策略除了对sql语句放弃参数化编写外,咱们也须要应用拦截器对与提交参数进行检测,呈现敏感字符进行谬误提醒 ...

December 4, 2021 · 5 min · jiezi

关于springboot:SpringBoot基础之AOPAspectJ

日志切面,其中execution中能够革新为@Before("execution( (..))"),第一个代表返回类型,第二个代表办法名,括号中的点代表数个参数JoinPoint能够获取到执行的办法名称。@Before("execution( hello())")示意带有一个任何类型并且返回参数不限的hello办法 @Component@Aspectpublic class TracingAspect { Logger logger = Logger.getLogger("..."); //执行办法前能够截获,能够控制程序不进入办法 @Before("execution(void doSomething())") //@Before("execution(* *(..))") public void entering(JoinPoint joinPoint){ logger.info("entering method"+joinPoint.getStaticPart().getSignature().toString()); } //执行办法后能够截获,能够获取到后果,异样等 @After("execution(* *(..))") public void exiting(JoinPoint joinPoint){ logger.info("existing "+joinPoint.getSignature()); for(Object org:joinPoint.getArgs()) logger.info("Arg: "+org); } //捕捉异样 @AfterThrowing(pointcut = "execution(void throwsRuntimeException())", throwing = "ex") public void logException(RuntimeException ex){ logger.info("Exception:" + ex); } //捕捉返回后果 @AfterReturning(pointcut = "execution(* *(..))", returning = "string") public void logResult(String string){ logger.info("result:" + string); } //同时反对进入办法前,执行办法后,捕捉异样,捕捉返回后果 @Around("execution(* *(..))") public Object trace(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ String methodInformation = proceedingJoinPoint.getStaticPart().getSignature().toString(); logger.info("Entering:"+methodInformation); try{ return proceedingJoinPoint.proceed(); }catch(Throwable ex){ logger.info("Exception in:"+methodInformation + ex); throw ex; }finally { logger.info("Exiting "+methodInformation); } }}execution( (..))示意同一个package门路下 ...

December 2, 2021 · 1 min · jiezi

关于springboot:SpringBoot自动装配原理

1. SpringBoot主动拆卸原理接触过SpringBoot的同学必定都晓得在启动类上有一个@SpringBootApplication注解,他就是主动拆卸的神秘所在。 /** * Indicates a {@link Configuration configuration} class that declares one or more * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience * annotation that is equivalent to declaring {@code @Configuration}, * {@code @EnableAutoConfiguration} and {@code @ComponentScan}. * * @author Phillip Webb * @author Stephane Nicoll * @author Andy Wilkinson * @since 1.2.0 */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication { // 省略代码}点开@SpringBootApplication注解,先看它的正文,粗心是: ...

December 1, 2021 · 2 min · jiezi

关于springboot:Spring-Boot-切面的一种的测试方法

习惯了单元测试当前,一些代码在提交前如果不测试一下总是感觉心里面空空的,没有底气可言。 Spring Boot提供的官网正文联合弱小的Mockito可能解决大部分在测试方面的需要。但貌似对于代理模式下的切面却并不如意。 情景模仿假如咱们以后有一个StudentControllor,该控制器中存一个getNameById办法。 @RestControllerpublic class StudentController { @GetMapping("{id}") public Student getNameById(@PathVariable Long id) { return new Student("测试姓名"); } public static class Student { private String name; public Student(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }}在没有切背后,咱们拜访该办法将失去相应带有测试姓名的学生信息。 建设切面当初,咱们应用切面的办法在返回的名字后盾追加一个Yz后缀。 @Aspect@Componentpublic class AddYzAspect { @AfterReturning(value = "execution(* club.yunzhi.smartcommunity.controller.StudentController.getNameById(..))", returning = "student") public void afterReturnName(StudentController.Student student) { student.setName(student.getName() + "Yz"); }}测试如果咱们应用一般测试的办法来间接断言返回的姓名当然是可行的: ...

December 1, 2021 · 1 min · jiezi

关于springboot:Spring-Boot-261-重磅发布

11 月 29 日,Spring Boot 2.6.1 正式公布,此版本包含 8 个谬误修复和 3 个文档改良。 谬误修复 模式分析 PatternParseException 的操作音讯中的 matching-strategy 属性的名称不正确 #28839 修复 ErrorPageSecurityFilter 部署到 Servlet 3.1 的兼容问题 #28790 QuartzDataSourceScriptDatabaseInitiializer不提供 MariaDB #28779的映射 "test" 和 "Inlined Test Properties" 属性源程序不正确 #28776 在没有 spring-security-web 的 Servlet 应用程序中应用 Spring Security 时呈现 ArrayStoreException #28774 DefaultClientResources 在将 Lettuce 与 Actuator 一起应用时未正确敞开是收回正告 #28767 具备 permitAll 的页面无奈再通过主动配置的 MockMvc #28759 依赖治理 org.elasticsearch.distribution.integ-test-zip:elasticsearch 应将其类型申明为 zip #28746 文档改良修复文档 "External Application Properties" 局部中的拼写错误 #28834 修复参考文档 #28833 中 "spring --version" 的输入 ...

December 1, 2021 · 1 min · jiezi

关于springboot:SpringBoot-如何实现异步编程老鸟们都这么玩的

镜像下载、域名解析、工夫同步请点击 阿里巴巴开源镜像站 首先咱们来看看在Spring中为什么要应用异步编程,它能解决什么问题? 为什么要用异步框架,它解决什么问题? 在SpringBoot的日常开发中,个别都是同步调用的。但理论中有很多场景非常适合应用异步来解决,如:注册新用户,送100个积分;或下单胜利,发送push音讯等等。 就拿注册新用户这个用例来说,为什么要异步解决? 第一个起因:容错性、健壮性,如果送积分出现异常,不能因为送积分而导致用户注册失败;因为用户注册是次要性能,送积分是主要性能,即便送积分异样也要提醒用户注册胜利,而后前面在针对积分异样做弥补解决。第二个起因:晋升性能,例如注册用户花了20毫秒,送积分破费50毫秒,如果用同步的话,总耗时70毫秒,用异步的话,无需期待积分,故耗时20毫秒。故,异步能解决2个问题,性能和容错性。SpringBoot如何实现异步调用?对于异步办法调用,从Spring3开始提供了@Async注解,咱们只须要在办法上标注此注解,此办法即可实现异步调用。 当然,咱们还须要一个配置类,通过Enable模块驱动注解@EnableAsync 来开启异步性能。 实现异步调用第一步:新建配置类,开启@Async性能反对应用@EnableAsync来开启异步工作反对,@EnableAsync注解能够间接放在SpringBoot启动类上,也能够独自放在其余配置类上。咱们这里抉择应用独自的配置类SyncConfiguration。 @Configuration@EnableAsyncpublic class AsyncConfiguration {}第二步:在办法上标记异步调用减少一个Component类,用来进行业务解决,同时增加@Async注解,代表该办法为异步解决。 @Component@Slf4jpublic class AsyncTask { @SneakyThrows @Async public void doTask1() { long t1 = System.currentTimeMillis(); Thread.sleep(2000); long t2 = System.currentTimeMillis(); log.info("task1 cost {} ms" , t2-t1); } @SneakyThrows @Async public void doTask2() { long t1 = System.currentTimeMillis(); Thread.sleep(3000); long t2 = System.currentTimeMillis(); log.info("task2 cost {} ms" , t2-t1); }}第三步:在Controller中进行异步办法调用@RestController@RequestMapping("/async")@Slf4jpublic class AsyncController { @Autowired private AsyncTask asyncTask; @RequestMapping("/task") public void task() throws InterruptedException { long t1 = System.currentTimeMillis(); asyncTask.doTask1(); asyncTask.doTask2(); Thread.sleep(1000); long t2 = System.currentTimeMillis(); log.info("main cost {} ms", t2-t1); }}通过拜访http://localhost:8080/async/task查看控制台日志: ...

December 1, 2021 · 2 min · jiezi

关于springboot:SpringBoot中jsr303校验使用配置文件提取报错信息

配置文件地位与命名报错信息的文件名固定为ValidationMessages.properties,搁置于classpath下(即resources)。 应用形式ValidationMessages.properties配置文件中的配置形式为: name=name为空page.page=页号为空page.size=每页大小为空page.size.min=每页大小起码为1配合校验注解的应用形式为: @NotNull(message = "{page.page}")援用形式于@Value注解相似。

November 30, 2021 · 1 min · jiezi

关于springboot:SpringBoot数据源注入原理

1. SpringBoot数据源注入原理咱们晓得,在application.yaml中配置spring.datasource之后,就能够对数据源进行注入,可这是为什么呢? spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 username: root password: root(1) 默认数据源在SpringBoot的spring-boot-autoconfigure包中,META-INF文件夹下有一个名为spring.factories文件 关上这个文件,能够看到其中配置了org.springframework.boot.autoconfigure.EnableAutoConfiguration这是SpringBoot主动拆卸的注解 看到这里应该明确了,spring.factories就是SpringBoot提供的spiSpringBoot提供了注解,会对这里配置的所有Bean进行主动拆卸。 在spring.factories中能够找到一条配置:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration 这个就是数据源注入的要害。 在DataSourceAutoConfiguration中,能够看到默认反对两种类型的数据源:EmbeddedDatabaseConfiguration(内嵌数据库)和PooledDataSourceConfiguration(池化数据源)。 @Configuration(proxyBeanMethods = false) @Conditional(EmbeddedDatabaseCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import(EmbeddedDataSourceConfiguration.class) protected static class EmbeddedDatabaseConfiguration { } @Configuration(proxyBeanMethods = false) @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) protected static class PooledDataSourceConfiguration { }能够看到他们都由@Conditional注解指明了注入条件,具体的代码就不贴了,能够本人去看池化数据源的条件是要么配置了spring.datasource.type,要么满足PooledDataSourceAvailableCondition的条件,这个条件是要通过以下类的类加载器别离去加载以下几个类: com.zaxxer.hikari.HikariDataSourceorg.apache.tomcat.jdbc.pool.DataSourceorg.apache.commons.dbcp2.BasicDataSource存在oracle.jdbc.OracleConnection的oracle.ucp.jdbc.PoolDataSourceImpl内嵌数据库的条件是首先要未配置spring.datasource.url,另外要不满足池化数据源的条件,也就是说会判断是否匹配池化数据源的条件,没有匹配到才会创立内嵌数据库内嵌数据库反对H2、DERBY、HSQL等几种类型。 (2) Druid数据源咱们在最开始的application.yaml中配置了spring.datasource.type为DruidDataSource,阐明应用的是Druid数据源能够看下DruidDataSourceAutoConfigure中的代码: @Configuration@ConditionalOnClass(DruidDataSource.class)@AutoConfigureBefore(DataSourceAutoConfiguration.class)@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})@Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class})public class DruidDataSourceAutoConfigure { private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class); @Bean(initMethod = "init") @ConditionalOnMissingBean public DataSource dataSource() { LOGGER.info("Init DruidDataSource"); return new DruidDataSourceWrapper(); }}@ConditionalOnClass表明存在DruidDataSource的时候,配置才会失效。 ...

November 27, 2021 · 1 min · jiezi

关于springboot:SpringBoot集成Druid连接池

1. 数据源数据源(DataSource)是由SUN公司定义的用于获取数据库连贯的JDBC标准接口它位于javax.sql包中,用来代替DriverManager的形式来获取连贯package javax.sql;import java.sql.Connection;import java.sql.SQLException;import java.sql.Wrapper;// 省略正文public interface DataSource extends CommonDataSource, Wrapper { Connection getConnection() throws SQLException; Connection getConnection(String username, String password) throws SQLException;}应用数据源能够间接获取数据库的连贯对象,不须要再编写数据库的代码。 2. 数据库连接池数据源能够创立多个数据库连贯,这些连贯会保留在数据库连接池中。当须要建设数据库连贯时,只须要从连接池中取出一个闲暇的连贯,用完之后再放回去。 传统的JDBC拜访技术,每次拜访数据库都须要通过数据库驱动器Driver和数据库名称以及明码等等资源建设数据库连贯,会减少系统资源和CPU的开销,而连接池能够很好地解决这个问题。 另外,咱们能够通过连接池的管理机制监督数据库的连贯的数量、应用状况,为零碎开发、测试以及性能调整提供根据。 3. 集成Druid连接池Druid连接池是目前最好的数据库连接池,在性能、性能、扩展性方面,远超于DBCP、C3P0等。 SpringBoot次要有两种形式集成Druid连接池。 (1) 应用Druid启动器间接在pom.xml中引入druid-spring-boot-starter,而后在application.yaml中配置就行: <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.22</version></dependency>spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 username: root password: root写一个简略的测试用例,打印数据源的配置: @SpringBootTest@RunWith(SpringRunner.class)public class DruidStarterConfigTest { @Resource private DataSource dataSource; @Test public void test() throws SQLException { DruidDataSource druidDataSource = (DruidDataSource) dataSource; System.out.println(druidDataSource.getDriverClassName()); System.out.println(druidDataSource.getUrl()); System.out.println(druidDataSource.getUsername()); System.out.println(druidDataSource.getPassword()); }}运行后果: ...

November 27, 2021 · 1 min · jiezi

关于springboot:SpringBoot访问jar包静态文件

背景我的项目开发过程中咱们咱们会遇到拜访动态文件的状况,例如word书签模板,excel导入模板,条文法规文件等,在war包的状况下拜访是没有问题的,如果应用jar包部署,应用相对路径拜访会呈现问题,本文就此问题给出解决方案。 配置resources文件夹下创立动态目录systemfile,放入测试文件test.docx(文件名须要命名为英文) pom文件resource/build节点设置打包编译疏忽systemfile文件夹 <resources> <resource> <filtering>true</filtering> <directory>src/main/resources</directory> <excludes> <exclude>systemfile/*</exclude> </excludes> </resource> <resource> <filtering>false</filtering> <directory>src/main/resources</directory> <includes> <include>systemfile/*</include> </includes> </resource> </resources>拜访应用ClassPathResource的getInputStream获取jar包中的文件的流暂存到磁盘的临时文件中,间接拜访临时文件即可 String testFilePath = ClassPathFileUtil.getFilePath("systemfile/test.docx");public static String getFilePath(String classFilePath) { String filePath = ""; try { String templateFilePath = "tempfiles/classpathfile/"; File tempDir = new File(templateFilePath); if (!tempDir.exists()) { tempDir.mkdirs(); } String[] filePathList = classFilePath.split("/"); String checkFilePath = "tempfiles/classpathfile"; for (String item : filePathList) { checkFilePath += "/" + item; } File tempFile = new File(checkFilePath); if (tempFile.exists()) { filePath = checkFilePath; } else { //解析 ClassPathResource classPathResource = new ClassPathResource(classFilePath); InputStream inputStream = classPathResource.getInputStream(); checkFilePath = "tempfiles/classpathfile"; for (int i = 0; i < filePathList.length; i++) { checkFilePath += "/" + filePathList[i]; if (i==filePathList.length-1) { //文件 File file = new File(checkFilePath); FileUtils.copyInputStreamToFile(inputStream, file); }else{ //目录 tempDir = new File(checkFilePath); if (!tempDir.exists()) { tempDir.mkdirs(); } } } inputStream.close(); filePath = checkFilePath; } } catch (Exception e) { e.printStackTrace(); } return filePath; }留神我的项目启动时,须要革除动态文件的临时文件,防止文件更新 ...

November 27, 2021 · 1 min · jiezi

关于springboot:个人学习系列-Transactional失效的3种情况

面试的时候遇到过这个问题,过后一脸懵逼。当初记录一下。。。@Transactional生效场景1. 在类外部调用调用类外部@Transactional标注的办法1.1 定义一个谬误的@Transactional标注实现,设置一个外部调用@Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Resource UserMapper userMapper; @Override public void oneTest() { oneTestNoPublic(); } @Transactional(rollbackFor = Exception.class) public void oneTestNoPublic(){ // 插入一条数据 int num = userMapper.insert(new User("小红", "青岛市", 18)); if (num > 0){ // 插入后制作一个谬误 System.out.println(1 / 0); } // 再次插入一条数据 userMapper.insert(new User("小强", "烟台市", 21)); }}1.2 测试用例@RestController@RequestMapping("/transactional")public class TransactionalTestController { @Resource IUserService userService; /** * 在类外部调用调用类外部@Transactional标注的办法 */ @GetMapping("/one") public void oneTest(){ try { userService.oneTest(); } catch (Exception e){ e.printStackTrace(); } }}1.3 运行后,控制台报错: ...

November 26, 2021 · 2 min · jiezi

关于springboot:Java自定义注解实现

1. Java注解Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种正文机制。 Java语言中的类、办法、变量、参数和包等都能够被标注。 在编译器生成类文件时,标注能够被嵌入到字节码中因而Java虚拟机能够在运行时能够通过反射获取到标注内容。 2. 元注解JDK中提供了一些根底注解,称为元注解。最常见的几种比方:作用在代码上的: @Override - 查看该办法是否是重写办法。如果发现其父类,或者是援用的接口中并没有该办法时,会报编译谬误。@Deprecated - 标记过期办法。如果应用该办法,会报编译正告。@SuppressWarnings - 批示编译器去疏忽注解中申明的正告。作用在其余注解的: @Retention - 标识这个注解怎么保留,是只在代码中(SOURCE),还是编译入class文件中(CLASS),或者是在运行时能够通过反射拜访(RUNTIME)。@Documented - 标记这个注解是否蕴含在JavaDoc中。@Target - 标记这个注解应该润饰哪种 Java 成员。@Inherited - 标记如果一个类具备继承注解,那么他所有的子类也领有该注解后续新退出的: @SafeVarargs - Java 7 开始反对,疏忽任何应用参数为泛型变量的办法或结构函数调用产生的正告。@FunctionalInterface - Java 8 开始反对,标识一个匿名函数或函数式接口。@Repeatable - Java 8 开始反对,标识某注解能够在同一个申明上应用屡次。这里不对元注解进行具体的介绍,有须要的能够去查阅相干的文档。 3. 自定义注解Java容许咱们实现自定义注解来实现某些性能。总的来说,自定义注解须要实现3步:定义注解、标注注解、解决注解 (1) 定义注解应用@interface来定义一个注解,上面定义了一个名为MyAnnotation的注解。 @Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface MyAnnotation { String value() default "MyAnnotation";}@Retention(RetentionPolicy.RUNTIME)示意该注解在运行时也无效,这样就能够应用反射的形式来获取到。@Target(ElementType.TYPE)示意该注解是用在类或者接口上的 (2) 标注注解@MyAnnotation("test")public class MyClass1 { }@MyAnnotationpublic class MyClass2 { }这里实现了两个类,别离应用MyAnnotation进行注解,MyClass1的注解中配置了属性值test,MyClass2没有配置属性值。 (3) 解决注解public class MyClassTest { @Test public void test() { new ApplicationContextRunner().withBean(MyClass1.class).withBean(MyClass2.class).run(context -> { // 获取所有被MyAnnotation标注的类 Map<String, Object> beansWithAnnotation = context.getBeansWithAnnotation(MyAnnotation.class); for (Map.Entry<String, Object> entry : beansWithAnnotation.entrySet()) { System.out.println("BeanName is " + entry.getKey()); // 获取类上的MyAnnotation标注 MyAnnotation annotation = entry.getValue().getClass().getAnnotation(MyAnnotation.class); System.out.println("the value of annotation is " + Objects.requireNonNull(annotation).value()); } }); }}这里实现了一个测试类,依据注解的属性值的不同,打印不同的内容。 ...

November 25, 2021 · 2 min · jiezi

关于springboot:spring-boot异步方法Async踩坑

前言我的需要是创立一个新线程来执行一些操作,因为如果用同一个线程就会导致资源始终被占据,影响响应效率。 异步的概念我的了解:异步即为在多辆车在多条路上行驶,同步即为多辆车在一条路上行驶。举个栗子:同步:以后如果正在学习c语言的话,那么咱们依照课程安顿是不应该学习c++的,只有等这个学期结束学完c语言,下个学期能力学习c++。异步:离散数学和c语言都是在本学期学习,你不用等c语言学完再学离散数学。 spring boot如何实现异步Google搜寻发现存在异步的注解。 How To Do @Async in Spring @Asyncpublic void asyncMethodWithVoidReturnType() { System.out.println("Execute method asynchronously. " + Thread.currentThread().getName());}总结:1.开启异步反对 2.异步注解应用留神 3.异步示例 我的异步踩坑1.未仔细阅读two limitations:Self-invocation — won't work. 测试代码如下: public class AsyncTest { @Test public void test1() { System.out.println("test1的线程id为:" + Thread.currentThread().getId()); test2(); } @Async public void test2() { System.out.println("test2的线程id为:" + Thread.currentThread().getId()); }}成果:两个都是同一个线程,并没有达到test2是独立的线程的成果解决方案:将test2放到另一个class中,自我调用将不会失效。 2.必须为@Component或@Service注解能力使@Async失效在将异步办法放到其余类: // 第一个类public class AsyncTest1 { public void test1() { System.out.println("test1的线程id为:" + Thread.currentThread().getId()); // 新建一个AsyncTest2类,调用其异步办法 AsyncTest2 asyncTest2 = new AsyncTest2(); asyncTest2.test2(); }}// 第二个类,定义异步办法public class AsyncTest2 { @Async public void test2() { System.out.println("test2的线程id为:" + Thread.currentThread().getId()); }}But:后果不随我愿,仍然是打印显示是同一线程。 ...

November 20, 2021 · 1 min · jiezi

关于springboot:SpringBoot整合Minio文件存储

背景公司的开发框架集成了附件本地存储,阿里云,华为云等,现我的项目有要求附件存储与利用部署环境不能是同一台服务器,也不能应用云存储,通过技术选型后决定框架整合minio,将minio部署在另一台服务器开明外网端口即可解决问题 Minio装置部署下载minio装置部署包,创立对应配置文件,这里提供一个整合后的压缩包 下载地址:https://download.csdn.net/dow... 创立minioData文件夹作为文件存储门路,解压安装包依据搁置门路批改对应配置文件文件 minio-service.xml和run.bat <service> <id>minio</id> <name>MinIO Service</name> <description>MinIO is a High Performance Object Storage</description> <logpath>D:\minio\logs</logpath> <log mode="roll-by-size"> <sizeThreshold>10240</sizeThreshold> <keepFiles>8</keepFiles> </log> <executable>D:\minio\run.bat</executable></service>set MINIO_ACCESS_KEY=adminset MINIO_SECRET_KEY=abcd@1234minio.exe server -address :9999 D:\minioData解压部署包后cmd进入对应解压门路,输出命令minio.exe server D:\minioData 初始化后敞开cmd命令 应用服务装置工具装置服务,抉择minio-service.exe windows服务装置工具下载地址:https://download.csdn.net/dow... 启动服务后拜访 http://127.0.0.1:9999/ 用户名:admin 明码:abcd@1234 (端口及账户明码都是在run.bat文件中配置的) 进入零碎创立bucket用于存储文件(相似于阿里云) 配置pom文件 <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>7.1.0</version> </dependency>配置yml文件这里别离配置上传/下载地址是正式我的项目可能配置外网端口后,服务器中不能拜访对应的外网端口,上传走内网,下载走外网 #minio配置 # 上传地址 minio_uploadurl: http://192.168.1.42:9999/ # 下载地址 minio_downloadurl: http://192.168.1.42:9999/ # 账户 minio_accesskey: admin # 明码 minio_secrectkey: abcd@1234 # 存储文件夹 minio_bucknetname: xxxMinio工具类初始化client public MinioClient InitMinio() { MinioClient minioClient = MinioClient.builder(). endpoint(frameConfig.getMinio_uploadurl()). credentials(frameConfig.getMinio_accesskey(),frameConfig.getMinio_secrectkey()).build(); try{ boolean isExist = minioClient.bucketExists(frameConfig.getMinio_bucknetname()); if (!isExist) { minioClient.makeBucket(frameConfig.getMinio_bucknetname()); } }catch (Exception e){ e.printStackTrace(); } return minioClient; }上传文件getkey办法只是指定对应的自定义存储门路 ...

November 20, 2021 · 1 min · jiezi

关于springboot:SpringBoot多环境配置文件打包

背景在应用springboot开发我的项目过程中,会有多种环境切换,例如开发环境,测试环境,演示环境,生产环境等,咱们通过建设多个yml文件联合profiles.active属性进行环境指定,然而须要打包时就要手动更改配置文件一一打包,容易造成不必要的误操作,本文就介绍如何进行不同环境配置文件的动静切换及一次性打包多个不同环境的部署包 配置pom文件pom文件增加profiles配置,我的项目有多少个配置文件,增加多少个子节点,profiles标签于parent标签同级,这样咱们就能够在右侧maven的配置项中勾选指定的环境 <profiles> <profile> <!-- 开发环境 --> <id>dev</id> <properties> <profileActive>dev</profileActive> </properties> <!-- 默认环境 --> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <!-- 演示环境 --> <id>demo</id> <properties> <profileActive>demo</profileActive> </properties> </profile> <profile> <!-- 正式环境 --> <id>pro</id> <properties> <profileActive>pro</profileActive> </properties> </profile> </profiles>配置打包文件名咱们须要动静的指定最终部署jar包的名字用于辨别每个部署包对应的运行环境是哪一个,finalName标签与plugins标签同级 <finalName>${project.artifactId}-${profileActive}</finalName>配置applcation.yml文件咱们须要将profiles.active文件指定为pom文件中的profileActive标签☞ profiles: active: @profileActive@打包多个运行环境部署包通过windows的bat执行脚本,批量执行mvn命令,须要在我的项目的src同级目录运行 call mvn cleancall mvn package -P devcall mvn package -P democall mvn package -P pro

November 20, 2021 · 1 min · jiezi

关于springboot:SpringBoot基础之Spring-Data-Jpa

明天讲一下Spring Data Jpa的根底写法,实际上很easy,很多语句都能够用DSL来替换,很有意思。在Springboot启动类加上@EnableJpaRepositories能够间接容许应用Spring Data Jpa上面这个是常见的Jpa的Repository的写法 public interface LocationJpaRepository extends JpaRepository<Location, Long> {}Entity配置,@Id示意主键,@GeneratedValue(strategy = GenerationType.AUTO)示意自增长,@Column示意字段名称@OneToMany示意一对多的关系 @Entity@Table(name = "APPLICATION")public class Application { public Application() { } public Application(String name, String description, String owner) { this.name = name; this.description = description; this.owner = owner; } @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "application_id") private Integer id; @Column(name = "app_name", nullable = false) private String name; @Column(length = 2000) private String description; private String owner; @Override public String toString() { return "Application{" + "id=" + id + ", name='" + name + '\'' + ", description='" + description + '\'' + ", owner='" + owner + '\'' + '}'; }}@OneToMany须要有@ManyToOne对应,其中会有ManyToOne的表Exercise中减少外键索引,application_application_id,另外FetchType.LAZY示意只有在通过getExercises时才去表中查问,如果是FetchType.EAGER示意获取到application时就要同时去表中查问exercises ...

November 17, 2021 · 3 min · jiezi

关于springboot:SpringBootRabbitMQ-实现-RPC-调用

说到 RPC(Remote Procedure Call Protocol 近程过程调用协定),小伙伴们脑海里蹦出的预计都是 RESTful API、Dubbo、WebService、Java RMI、CORBA 等。 其实,RabbitMQ 也给咱们提供了 RPC 性能,并且应用起来很简略。 明天松哥通过一个简略的案例来和大家分享一下 Spring Boot+RabbitMQ 如何实现一个简略的 RPC 调用。 留神 对于 RabbitMQ 实现 RPC 调用,有的小伙伴可能会有一些误会,心想这还不简略?搞两个音讯队列 queue_1 和 queue_2,首先客户端发送音讯到 queue_1 上,服务端监听 queue_1 上的音讯,收到之后进行解决;解决实现后,服务端发送音讯到 queue_2 队列上,而后客户端监听 queue_2 队列上的音讯,这样就晓得服务端的处理结果了。 这种形式不是不能够,就是有点麻烦!RabbitMQ 中提供了现成的计划能够间接应用,十分不便。接下来咱们就一起来学习下。 1. 架构先来看一个简略的架构图: 这张图把问题说的很明确了: 首先 Client 发送一条音讯,和一般的音讯相比,这条音讯多了两个要害内容:一个是 correlation_id,这个示意这条音讯的惟一 id,还有一个内容是 reply_to,这个示意音讯回复队列的名字。Server 从音讯发送队列获取音讯并解决相应的业务逻辑,解决实现后,将处理结果发送到 reply_to 指定的回调队列中。Client 从回调队列中读取音讯,就能够晓得音讯的执行状况是什么样子了。这种状况其实非常适合解决异步调用。 2. 实际接下来咱们通过一个具体的例子来看看这个怎么玩。 2.1 客户端开发首先咱们来创立一个 Spring Boot 工程名为 producer,作为音讯生产者,创立时候增加 web 和 rabbitmq 依赖,如下图: 我的项目创立胜利之后,首先在 application.properties 中配置 RabbitMQ 的根本信息,如下: ...

November 16, 2021 · 3 min · jiezi

关于springboot:mybatis-springboot-整合源码分析-springboot实战电商项目mall4j

springboot实战电商我的项目mall4j (https://gitee.com/gz-yami/mall4j) java开源商城零碎 代码版本 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version></dependency>既然是springboot 那么要想晓得怎么运行的,有从三个中央动手 xxxAutoConfigurationyml对应的配置文件(xxxProperties)配置的注解咱们先来看三个类 1. MybatisAutoConfiguration 结构SqlSession这个类通过dataSource的配置,结构出SqlSessionFactory 与 SqlSessionTemplate。如果以前应用spring相干的api的话,应该会比拟相熟 jdbcTemplate 与 redisTemplate 之类的。SqlSessionTemplate这个命名,就会让人联想到这个也是相似的性能。而session 这个词很显著就是与服务之间交互保留连贯状态的货色。Factory是工厂模式。从而能够得出:SqlSessionFactory是用来创立SqlSession的,SqlSession能够关上或敞开与数据库的连贯。SqlSessionTemplate 就是操作这些开关的要害。 @EnableConfigurationProperties(MybatisProperties.class)public class MybatisAutoConfiguration implements InitializingBean { @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { // 省略... } @Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { // 省略... }}2. MybatisProperties 读取配置信息这个类次要是将配置文件外面的配置转成bean映射,配置文件相似这个样子 #mybatis的相干配置mybatis: #mapper配置文件 mapper-locations: classpath:mapper/*Mapper.xml type-aliases-package: com.frozen-watermelon.**.model #开启驼峰命名 configuration: map-underscore-to-camel-case: true@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)public class MybatisProperties { public static final String MYBATIS_PREFIX = "mybatis";}3. @MapperScan 扫描Mapper接口咱们通常为了确定被扫描的mapper所属的包,都会有这样一个配置 ...

November 16, 2021 · 6 min · jiezi

关于springboot:基于springboot实现一个简单的aop

简介 AOP(Aspect-Oriented Programming:面向切面编程) aop能将一些繁琐、反复、无关业务的逻辑封装起来,在一个中央进行对立解决,罕用于日志记录、事务管理、权限管制等,aop能在不扭转原有代码逻辑的根底上对某个办法、某类办法、或者整个类进行无侵入式的增强,无效升高了代码耦合度,并且进步了我的项目扩展性;ok废话说完,进入正题,如何实现一个aop 要实现aop,首先你要晓得你拿aop来干啥,咱们明天就以记录日志来说,因为这个最罕用,个别对于重要的数据库操作,咱们须要记录操作人、什么工夫、做了什么,对于做了什么怎么实现咱们前面细讲(要想晓得做了什么,必定得晓得是哪个办法、并且哪些参数,这些属于进阶操作,咱们先简略实现一个aop) 咱们先new一个切面 @Aspect@Componentpublic class LogAspect { @Pointcut("execution(* com.example.mydemos.controller..*(..))") public void controllerMenthod() { } @Before("controllerPointcut()") public void beforeExecute() { System.out.println("before..."); } @After("controllerPointcut()") public void afterExecute() { System.out.println("after..."); }}对于注解 @Aspect:通知spring这是一个切面;@Component:将切面交由spring来治理;@Pointcut:切入点,直白点就是指定你须要从哪个中央切入,再直白点就是你想加强的指标办法,这里须要理解下execution表达式,能够通过这里来指定你须要切入的办法,能够指定单个办法、整个类的所有办法、类的某些办法、整个包下所有类的所有办法等;@Before:指标办法执行前须要做的事;@After:指标办法执行后须要做的事还有几个罕用注解: @Around(能自在的指定在指标办法执行前后做加强逻辑,须要手动调用ProceedingJoinPoint的proceed办法来执行指标办法,不调用则指标办法不会执行,如果指标办法有返回值,还需手动返回)@AfterReturning(在指标办法失常执行实现后做加强,如果你须要获取办法返回值就用它)@AfterThrowing(当指标办法执行过程中抛出异样时执行)给大家分享一套《大厂Java深度面经》,写的十分具体,完整版PDF文档点此获取。执行机会: 切入指标办法时,先织入Around,再织入Before,退出指标办法时,先织入Around,再织入AfterReturning,最初才织入After 来个测试controller就是个平平无奇的一般controller @RestControllerpublic class HiController { @GetMapping("/hi") public String sayHello() { System.out.println("hi, good morning~"); return "hi bro ~"; }}我这个controller是放在Pointcut对应com.example.mydemos.controller包下的,所以该包下的所有类的所有办法都会被加强 先假如后验证 依照上述demo当我拜访"/hi"时,会先执行@Before对应办法,输入"before…",再执行HiController 中的sayHello办法,输入"hi, good morning~",并且返回"hi bro ~",最初执行@After对应办法,输入"after…" 验证:我的项目跑起来拜访"/hi" 控制台 验证胜利~ 一个最根底的aop实现结束,接下来搞点进阶操作 获取指标办法参数 再来个测试controller @RestControllerpublic class HelloController { @GetMapping("/hello/{title}/{content}") public String sayHello(@PathVariable("title") String title, @PathVariable("content") String content) { System.out.println(title + ":" + content); return "hello ya~"; }} ...

November 15, 2021 · 1 min · jiezi

关于springboot:SpringBoot源码-自定义starter

starter是 springboot的翅膀,插上翅膀,就能飞得很高~ 想飞的同学,跟着我,一步步 diy本人的 starter~ 1.创立pom我的项目创立一个pom我的项目,命名为 sayhello-spring-boot-starter,引入两个依赖,如图: 2. 创立 beanSayHello.java package bean;public class SayHello { // 只有一个属性 private String name; public void setName(String name){ this.name = name; } // 最初输入的信息 public String getMsg(){ return name + "say hello~~"; }}3. 创立properties类SayHelloProperties.java package properties;import org.springframework.boot.context.properties.ConfigurationProperties;// 读取配置文件中 hello:name 的配置@ConfigurationProperties(prefix = "hello")public class SayHelloProperties { private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; }}4. 创立主动配置类MyAutoConfiguration.java ...

November 15, 2021 · 1 min · jiezi

关于springboot:K8S-部署-SpringBoot-项目一篇够用

当初比拟多的互联网公司都在尝试将微服务迁到云上,这样的可能通过一些成熟的云容器治理平台更为不便地治理微服务集群,从而进步微服务的稳定性,同时也能较好地晋升团队开发效率。 然而迁云存在肯定的技术难点,明天这篇文章次要介绍如何从0开始搭建一套基于K8s部署的SpringBoot案例教程。 根底环境筹备: mac操作系统SpringBoot的简略Web工程minikube的环境搭建装置一个适宜咱们高级入门的k8s环境,比拟好的举荐是应用minikube工具,同时应用该工具能够更好地升高咱们对k8s的学习门槛。首先咱们须要下载minikube文件: curl -Lo minikube https://github.com/kubernetes/minikube/releases/download/v1.5.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/在装置minikube的时候,尝试下载镜像的时候可能会卡住,例如呈现下边的这类异样: 【idea @ Mac】>>>>>>minikube start --registry-mirror=https://w4i0ckag.mirror.aliyuncs.com Darwin 10.15.3 上的 minikube v1.16.0✨ 依据现有的配置文件应用 docker 驱动程序 Starting control plane node minikube in cluster minikube Pulling base image ...E0126 17:03:30.131026 34416 cache.go:180] Error downloading kic artifacts: failed to download kic base image or any fallback image Creating docker container (CPUs=2, Memory=1988MB) ... StartHost failed, but will try again: creating host: create: creating: setting up container node: preparing volume for minikube container: docker run --rm --entrypoint /usr/bin/test -v minikube:/var gcr.io/k8s-minikube/kicbase:v0.0.15-snapshot4@sha256:ef1f485b5a1cfa4c989bc05e153f0a8525968ec999e242efff871cbb31649c16 -d /var/lib: exit status 125stdout:stderr:Unable to find image 'gcr.io/k8s-minikube/kicbase:v0.0.15-snapshot4@sha256:ef1f485b5a1cfa4c989bc05e153f0a8525968ec999e242efff871cbb31649c16' locallydocker: Error response from daemon: Get https://gcr.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers).See 'docker run --help'. docker "minikube" container is missing, will recreate. Creating docker container (CPUs=2, Memory=1988MB) ... Failed to start docker container. Running "minikube delete" may fix it: recreate: creating host: create: creating: setting up container node: preparing volume for minikube container: docker run --rm --entrypoint /usr/bin/test -v minikube:/var gcr.io/k8s-minikube/kicbase:v0.0.15-snapshot4@sha256:ef1f485b5a1cfa4c989bc05e153f0a8525968ec999e242efff871cbb31649c16 -d /var/lib: exit status 125stdout:stderr:Unable to find image 'gcr.io/k8s-minikube/kicbase:v0.0.15-snapshot4@sha256:ef1f485b5a1cfa4c989bc05e153f0a8525968ec999e242efff871cbb31649c16' locallydocker: Error response from daemon: Get https://gcr.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers).See 'docker run --help'.❌ Exiting due to GUEST_PROVISION: Failed to start host: recreate: creating host: create: creating: setting up container node: preparing volume for minikube container: docker run --rm --entrypoint /usr/bin/test -v minikube:/var gcr.io/k8s-minikube/kicbase:v0.0.15-snapshot4@sha256:ef1f485b5a1cfa4c989bc05e153f0a8525968ec999e242efff871cbb31649c16 -d /var/lib: exit status 125stdout:stderr:Unable to find image 'gcr.io/k8s-minikube/kicbase:v0.0.15-snapshot4@sha256:ef1f485b5a1cfa4c989bc05e153f0a8525968ec999e242efff871cbb31649c16' locallydocker: Error response from daemon: Get https://gcr.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers).See 'docker run --help'. If the above advice does not help, please let us know: https://github.com/kubernetes/minikube/issues/new/choose此时能够尝试先在宿主机上安装好对应的镜像文件: ...

November 10, 2021 · 4 min · jiezi

关于springboot:深入浅出内存马二-之SpringBoot内存马

0x01 前言在上一篇文章中深入浅出内存马(一),我介绍了基于Tomcat的Filter内存马,不光是Filter 还有listener、servlet、controller 等不同模式的内存马。现在企业开发过程中,大部分应用的都是spring系列的框架进行开发,特地是SpringBoot,当初根本是企业开发的标配。所以探讨Spring系列下的内存马就显得十分必要了。 明天咱们就来钻研钻研Spring Boot下的内存马实现。 0x02 需要随着微服务部署技术的迭代演进,大型业务零碎在达到真正的应用服务器的时候,会通过一些系列的网关,简单平衡,防火墙。所以如果你新建的shell路由不在这些网关的白名单中,那么就很有可能无法访问到,在达到应用服务器之前就会被抛弃,咱们该如何解决这个问题? 所以,在注入内存马的时候,就尽量不要用新建的路由,或者shell地址。最好是在拜访失常的业务地址之前,就能执行咱们的代码。 依据这个文章外面的说法基于内存 Webshell 的无文件攻打技术钻研 在通过一番文档查阅和源码浏览后,发现可能有不止一种办法能够达到以上成果。其中通用的技术点次要有以下几个: 在不应用注解和批改配置文件的状况下,应用纯 java 代码来取得以后代码运行时的上下文环境;在不应用注解和批改配置文件的状况下,应用纯 java 代码在上下文环境中手动注册一个 controller;controller 中写入 Webshell 逻辑,达到和 Webshell 的 URL 进行交互回显的成果; 0x03 SpringBoot的生命周期为了满足下面的需要,咱们须要理解SpringBoot的生命周期,咱们须要钻研的是:一个申请到到应用层之前,须要通过那几个局部?是如何一步一步到到咱们的Controller的? 咱们用IDEA来搭建一个SpingBoot2 的环境 拜访地址: 咱们还是把断点打在 org.apache.catalina.core.ApplicationFilterChain中的 internalDoFilter办法中 能够看到整个执行流程 这部分在上一篇文章中曾经详细描述过,这里不在赘述。 然而这里不同的是在通过 Filter 层面解决后,就会进入相熟的 spring-webmvc 组件 org.springframework.web.servlet.DispatcherServlet类的 doDispatch 办法中。 跟进去这个办法 能够看到是遍历this.handlerMappings 这个迭代器中的mapper的getHandler 办法解决Http中的request申请。 持续追踪,最终会调用到 org.springframework.web.servlet.handler.AbstractHandlerMapping类的 getHandler 办法,并通过 getHandlerExecutionChain(handler, request) 办法返回 HandlerExecutionChain 类的实例。 持续跟进getHandlerExecutionChain 办法, protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { HandlerExecutionChain chain = handler instanceof HandlerExecutionChain ? (HandlerExecutionChain)handler : new HandlerExecutionChain(handler); Iterator var4 = this.adaptedInterceptors.iterator(); while(var4.hasNext()) { HandlerInterceptor interceptor = (HandlerInterceptor)var4.next(); if (interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor; if (mappedInterceptor.matches(request)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { chain.addInterceptor(interceptor); } } //返回的是HandlerExecutonChain,这里蕴含了所有的拦截器 return chain; }好了,当初咱们晓得程序在哪里退出的拦截器(interceptor)后,追踪到这行代码 ...

November 10, 2021 · 4 min · jiezi

关于springboot:在-Java-Spring-Boot-项目中使用结构化日志节省时间

【注】本文译自: [Saving Time with Structured Logging - Reflectoring](https://reflectoring.io/struc...) 日志记录是考察事件和理解应用程序中产生的事件的终极资源。每个应用程序都有某种类型的日志。 然而,这些日志通常很凌乱,剖析它们须要付出很多致力。在本文中,咱们将钻研如何利用结构化日志来大大增加日志的价值。 咱们将通过一些十分实用的技巧来进步应用程序日志数据的价值,并应用 Logz.io 作为日志平台来查问日志。 代码示例本文附有 [GitHub 上](https://github.com/thombergs/...)的工作代码示例。 什么是结构化日志?“失常”日志是非结构化的。它们通常蕴含一个音讯字符串: 2021-08-08 18:04:14.721 INFO 12402 --- [ main] i.r.s.StructuredLoggingApplication : Started StructuredLoggingApplication in 0.395 seconds (JVM running for 0.552)此音讯蕴含咱们在考察事件或剖析问题时心愿取得的所有信息: 日志事件的日期创立日志事件的记录器的名称,以及日志音讯自身。所有信息都在该日志音讯中,但很难查问这些信息!因为所有信息都在一个字符串中,如果咱们想从日志中获取特定信息,就必须解析和搜寻这个字符串。例如,如果咱们只想查看特定记录器的日志,则日志服务器必须解析所有日志音讯,查看它们是否具备辨认记录器的特定模式,而后依据所需的记录器过滤日志音讯。 结构化日志蕴含雷同的信息,但采纳结构化模式而不是非结构化字符串。通常,结构化日志以 JSON 格局出现: { "timestamp": "2021-08-08 18:04:14.721", "level": "INFO", "logger": "io.reflectoring....StructuredLoggingApplication", "thread": "main", "message": "Started StructuredLoggingApplication ..."}这种 JSON 构造容许日志服务器无效地存储,更重要的是检索日志。 例如,当初能够通过 timestamp 或 logger 轻松过滤日志,而且搜寻比解析特定模式的字符串更无效。 然而结构化日志的价值并不止于此:咱们能够依据须要向结构化日志事件中增加任何自定义字段! 咱们能够增加上下文信息来帮忙咱们辨认问题,或者咱们能够向日志增加指标。 凭借咱们当初触手可及的所有数据,咱们能够创立弱小的日志查问和仪表板,即便咱们刚在中午醒来考察事件,咱们也能找到所需的信息。 当初让咱们看几个用例,它们展现了结构化日志记录的弱小性能。 为所有日志事件增加代码门路咱们首先要看的是代码门路。每个应用程序通常有几个不同的门路,传入申请能够通过应用程序。思考这个图:Java Spring Boot 我的项目中应用结构化日志节省时间此示例具备(至多)三种不同的代码门路,传入申请能够采纳这些门路: 用户代码门路:用户正在从他们的浏览器应用应用程序。浏览器向 Web 控制器发送申请,控制器调用畛域代码。第三方零碎代码门路:应用程序的 HTTP API 也从第三方零碎调用。在这个例子中,第三方零碎调用与用户浏览器雷同的 web 控制器。计时器代码门路:与许多应用程序一样,此应用程序有一些由计时器触发的打算工作。这些代码门路中的每一个都能够具备不同的特色。域服务波及所有三个代码门路。在波及域服务谬误的事件期间,理解导致谬误的代码门路将大有帮忙!如果咱们不晓得代码门路,咱们很容易在事件调查期间做出毫无结果的猜想。 ...

November 10, 2021 · 3 min · jiezi

关于springboot:使用-Spring-Boot-构建可重用的模拟模块

【译】本文译自: Building Reusable Mock Modules with Spring Boot - Reflectoring 将代码库宰割成涣散耦合的模块,每个模块都有一组专门的职责,这不是很好吗? 这意味着咱们能够轻松找到代码库中的每个职责来增加或批改代码。也意味着代码库很容易把握,因为咱们一次只须要将一个模块加载到大脑的工作记忆中。 而且,因为每个模块都有本人的 API,这意味着咱们能够为每个模块创立一个可重用的模仿。在编写集成测试时,咱们只需导入一个模仿模块并调用其 API 即可开始模仿。咱们不再须要晓得咱们模仿的类的每一个细节。 在本文中,咱们将着眼于创立这样的模块,探讨为什么模仿整个模块比模仿单个 bean 更好,而后介绍一种简略但无效的模仿残缺模块的办法,以便应用 Spring Boot 进行简略的测试设置。 代码示例本文附有 GitHub 上的工作代码示例。 什么是模块?当我在本文中议论“模块”时,我的意思是: 模块是一组高度内聚的类,这些类具备专用的 API 和一组相干的职责。咱们能够将多个模块组合成更大的模块,最初组合成一个残缺的应用程序。 一个模块能够通过调用它的 API 来应用另一个模块。 你也能够称它们为“组件”,但在本文中,我将保持应用“模块”。 如何构建模块?在构建应用程序时,我倡议事后思考如何模块化代码库。咱们的代码库中的天然边界是什么? 咱们的应用程序是否须要与内部零碎进行通信?这是一个天然的模块边界。咱们能够构建一个模块,其职责是与内部零碎对话! 咱们是否确定了属于一起的用例的性能“边界上下文”?这是另一个很好的模块边界。咱们将构建一个模块来实现应用程序的这个性能局部中的用例! 当然,有更多办法能够将应用程序拆分为模块,而且通常不容易找到它们之间的边界。他们甚至可能会随着工夫的推移而扭转!更重要的是在咱们的代码库中有一个清晰的构造,这样咱们就能够轻松地在模块之间挪动概念! 为了使模块在咱们的代码库中不言而喻,我倡议应用以下包构造: 每个模块都有本人的包每个模块包都有一个 api 子包,蕴含所有裸露给其余模块的类每个模块包都有一个外部子包 internal ,其中蕴含: 实现 API 公开的性能的所有类一个 Spring 配置类,它将 bean 提供给实现该 API 所需的 Spring 应用程序上下文就像俄罗斯套娃一样,每个模块的 internal 子包可能蕴含带有子模块的包,每个子模块都有本人的 api 和 internal 包给定 internal 包中的类只能由该包中的类拜访。这使得代码库十分清晰,易于导航。在我对于清晰架构边界 中浏览无关此代码构造的更多信息,或 示例代码中的一些代码。 这是一个很好的包构造,但这与测试和模仿有什么关系呢? 模仿单个 Bean 有什么问题?正如我在开始时所说的,咱们想着眼于模仿整个模块而不是单个 bean。然而首先模仿单个 bean 有什么问题呢? ...

November 9, 2021 · 4 min · jiezi

关于springboot:SpringBoot如何集成Caffeine

引言后面咱们有学习Caffeine 本地缓存性能之王Caffeine,并且也提到SpringBoot默认应用的本地缓存也是Caffeine啦,明天咱们来看看Caffeine如何与SpringBoot集成的。 集成caffeinecaffeine与SpringBoot集成有两种形式: 一种是咱们间接引入 Caffeine 依赖,而后应用 Caffeine 办法实现缓存。相当于应用原生api引入 Caffeine 和 Spring Cache 依赖,应用 SpringCache 注解办法实现缓存。SpringCache帮咱们封装了Caffeinepom文件引入 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId></dependency><dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.6.0</version></dependency>第一种形式首先配置一个Cache,通过结构者模式构建一个Cache对象,而后后续对于缓存的增删查都是基于这个cache对象。 @Configurationpublic class CacheConfig { @Bean public Cache<String, Object> caffeineCache() { return Caffeine.newBuilder() // 设置最初一次写入或拜访后通过固定工夫过期 .expireAfterWrite(60, TimeUnit.SECONDS) // 初始的缓存空间大小 .initialCapacity(100) // 缓存的最大条数 .maximumSize(1000) .build(); }第一种形式咱们就一一不介绍了,基本上就是应用caffeineCache来依据你本人的业务来操作以下办法这种形式应用的话是对代码有侵入性的。 第二种形式须要在SpingBoot启动类标上EnableCaching注解,这个玩意跟很多框架都一样,比方咱们肴集成dubbo也须要标上@EnableDubbole注解等。 @SpringBootApplication @EnableCaching public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }在application.yml配置咱们的应用的缓存类型、过期工夫、缓存策略等。 spring:profiles: active: devcache: type: CAFFEINE caffeine: spec: maximumSize=500,expireAfterAccess=600s如果咱们不习惯应用这种形式的配置,当然咱们也能够应用JavaConfig的配置形式来代替配置文件。 ...

November 5, 2021 · 3 min · jiezi

关于springboot:如何在-Spring-中使用事件

【注】本文译自:Spring Events | Baeldung 1. 概述在本教程中,咱们将探讨如何在 Spring 中应用事件。 事件是框架中最容易被忽视的性能之一,但也是更有用的性能之一。和 Spring 中的许多其余货色一样,事件公布是 ApplicationContext 提供的性能之一。 有一些简略的指导方针能够遵循: 如果咱们应用 Spring Framework 4.2 之前的版本,事件类应该扩大 ApplicationEvent。从 4.2 版本开始,事件类不再须要扩大 ApplicationEvent 类。发布者应该注入一个 ApplicationEventPublisher 对象。监听器应实现 ApplicationListener 接口。 2.自定义事件Spring 容许咱们创立和公布默认同步的自定义事件。这有一些长处,例如监听器可能参加发布者的事务上下文。 2.1.一个简略的应用程序事件让咱们创立一个简略的事件类——只是一个用于存储事件数据的占位符。 在这种状况下,事件类蕴含一个 String 音讯: public class CustomSpringEvent extends ApplicationEvent { private String message; public CustomSpringEvent(Object source, String message) { super(source); this.message = message; } public String getMessage() { return message; }}2.2. 发布者当初让咱们创立该事件的发布者。发布者结构事件对象并将其公布给正在收听的任何人。 要公布事件,发布者能够简略地注入 ApplicationEventPublisher 并应用 publishEvent() API: @Componentpublic class CustomSpringEventPublisher { @Autowired private ApplicationEventPublisher applicationEventPublisher; public void publishCustomEvent(final String message) { System.out.println("Publishing custom event. "); CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message); applicationEventPublisher.publishEvent(customSpringEvent); }}或者,发布者类能够实现ApplicationEventPublisherAware 接口,这也会在应用程序启动时注入事件发布者。通常,将 @Autowire 注入发布者会更简略。 ...

November 3, 2021 · 2 min · jiezi

关于springboot:Spring-Boot-2x基础教程构建RESTful-API与单元测试

首先,回顾并具体阐明一下在疾速入门中应用的@Controller、@RestController、@RequestMapping注解。如果您对Spring MVC不相熟并且还没有尝试过疾速入门案例,倡议先看一下疾速入门的内容。 @Controller:润饰class,用来创立解决http申请的对象 @RestController:Spring4之后退出的注解,原来在@Controller中返回json须要@ResponseBody来配合,如果间接用@RestController代替@Controller就不须要再配置@ResponseBody,默认返回json格局 @RequestMapping:配置url映射。当初更多的也会间接用以Http Method间接关联的映射注解来定义,比方:GetMapping、PostMapping、DeleteMapping、PutMapping等 上面咱们通过应用Spring MVC来实现一组对User对象操作的RESTful API,配合正文具体阐明在Spring MVC中如何映射HTTP申请、如何传参、如何编写单元测试。 RESTful API具体设计如下: 定义User实体 @Datapublic class User { private Long id; private String name; private Integer age;}留神:相比1.x版本教程中自定义set和get函数的形式,这里应用@Data注解能够实现在编译器主动增加set和get函数的成果。该注解是lombok提供的,只须要在pom中引入退出上面的依赖就能够反对: <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId></dependency>实现对User对象的操作接口 @RestController@RequestMapping(value = "/users") // 通过这里配置使上面的映射都在/users下public class UserController { // 创立线程平安的Map,模仿users信息的存储 static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); /** * 解决"/users/"的GET申请,用来获取用户列表 * * @return */ @GetMapping("/") public List<User> getUserList() { // 还能够通过@RequestParam从页面中传递参数来进行查问条件或者翻页信息的传递 List<User> r = new ArrayList<User>(users.values()); return r; } /** * 解决"/users/"的POST申请,用来创立User * * @param user * @return */ @PostMapping("/") public String postUser(@RequestBody User user) { // @RequestBody注解用来绑定通过http申请中application/json类型上传的数据 users.put(user.getId(), user); return "success"; } /** * 解决"/users/{id}"的GET申请,用来获取url中id值的User信息 * * @param id * @return */ @GetMapping("/{id}") public User getUser(@PathVariable Long id) { // url中的id可通过@PathVariable绑定到函数的参数中 return users.get(id); } /** * 解决"/users/{id}"的PUT申请,用来更新User信息 * * @param id * @param user * @return */ @PutMapping("/{id}") public String putUser(@PathVariable Long id, @RequestBody User user) { User u = users.get(id); u.setName(user.getName()); u.setAge(user.getAge()); users.put(id, u); return "success"; } /** * 解决"/users/{id}"的DELETE申请,用来删除User * * @param id * @return */ @DeleteMapping("/{id}") public String deleteUser(@PathVariable Long id) { users.remove(id); return "success"; }}这里相较1.x版本教程中,用更细化的@GetMapping、@PostMapping等系列注解替换了以前的@RequestMaping注解;另外,还应用@RequestBody替换了@ModelAttribute的参数绑定。 ...

November 3, 2021 · 2 min · jiezi

关于springboot:spring和nacos的加载机制

spring-cloud-context提供疏导上下文、加密、刷新范畴和环境端点等标准和实现,一起来看它的实现, SpringFactoriesLoader类是通过类加载器通过getResources()和getSystemResources()查找所有META-INF/spring.factories的文件,这外面的配置个别是springboot主动配置的配置类,进行初始化并缓存。 Environment接口通过继承PropertyResolver蕴含了profiles和properties两个方面,一个是定义不同配置文件的活动状态,第二个是通过属性名称获取属性值,而使得疏忽底层数据起源是否来自属性文件、系统配置、环境变量等等。通常应用的实现类是StandardServletEnvironment。 PropertySource类是一个配置资源类,像常见的NacosPropertySource就是nacos的加载,这里能够看进去NacosPropertySource就是基于MapPropertySource,实现很简略。 PropertySourceLocator类作用是为Environment(环境)定位(可能是近程)属性源的策略。实现类有NacosPropertySourceLocator,nacos是通过configService.getConfig(dataId, group, timeout)下载到本地文件做缓存的,所以其实相当于读取本地文件了。而后在NacosPropertySource层面再包了一层Map(这里不是应用的PropertySourceLoader,而是应用Nacos本人的NacosDataParserHandler来解析内容)并返回NacosPropertySource。 springboot的启动办法,SpringApplication.run(),能够进去看到开始就通过SpringFactoriesLoader加载了实现了ApplicationContextInitializer和ApplicationListener接口的类,实现类有BootstrapApplicationListener(加载bootstrap),ConfigFileApplicationListener(加载application),以及上面两个相干的 NacosConfigBootstrapConfiguration,定义了NacosPropertySourceLocator等Nacos的类 PropertySourceBootstrapConfiguration,初始化时会加载所有的PropertySourceLocator的bean组件,并进行加载,实现了environment.getPropertySources()的初始化。 PropertySourceLoader是加载工具解析类,实现有YamlPropertySourceLoader(yml)、PropertiesPropertySourceLoader(xml),有List> load(String name, Resource resource)办法,依据对应的名称加载出并解析。 ----------------记录下spring-boot启动流程----------------- springboot的启动后会加载所有实现ApplicationContextInitializer和ApplicationListener接口的类,而后springboot其实有个疏导上下文(是非web容器)和规范的上下文,具体源码在SpringApplication.prepareEnvironment()办法会调用listeners.environmentPrepared(environment);事件,这个listeners里有个SpringApplicationRunListener类,它会触发Application.getListeners(),这里的Listeners就是之前加载的所有ApplicationListener类,外面有个BootstrapApplicationListener,会触发onApplicationEvent(ApplicationEnvironmentPreparedEvent event)办法,这里就是会生成一个疏导上下文了,并且会加载spring.factories里所有BootstrapConfiguration.class的类,这里就蕴含下面NacosConfigBootstrapConfiguration类和PropertySourceBootstrapConfiguration类。生成完疏导上下文之后会application.addInitializers(new AncestorInitializer(context)),在规范上下文中加一个ApplicationContextInitializer初始化实现类,这外面是把疏导上下文作为规范上下文的parent。 创立完疏导上下文之后就创立规范上下文了,SpringApplication.createApplicationContext(),之后再就是一系列的生命周期初始化的操作了

November 3, 2021 · 1 min · jiezi

关于springboot:springbootmavenplugin的buildimage使用

这个插件的作用就是帮你主动生成跨全平台的镜像服务,无需再本人手动写Dockerfile文件了,它是基于buildpacks这个标准,有一系列的生命周期,和maven差不多意思。失常来说,如果你是在互联网的话,它会主动的检测你我的项目的语言,运行时环境(python、nodejs、jvm)等等,主动从网络下载对应的依赖,一键即可生成,切实是十分好用! 它的一些生命周期和配置能够在https://github.com/paketo-bui...这里看到,首先spring-boot-maven-plugin2.5.2版本应用的默认构建器是paketobuildpacks:builder:base,这里咱们能够去看看github上的配置GitHub - paketo-buildpacks/base-builder,次要是builder.toml配置文件,外面有很多不同语言运行时环境的构建包(这里定义了很多,理论执行时就会检测,应用特定的构建包) 。 我是用java,所以就会用到paketo-buildpacks/java这里包,持续找https://github.com/paketo-bui...,咱们看buildpack.toml文件,外面又有很多依赖,这里个别的spring-boot我的项目只会用到5个依赖("paketo-buildpacks/ca-certificates", "paketo-buildpacks/bellsoft-liberica", "paketo-buildpacks/executable-jar","paketo-buildpacks/dist-zip","paketo-buildpacks/spring-boot",),咱们次要看bellsoft-liberica这个,这个是jdk相干的配置 如果是这么简略,那就没必要写一篇博客了,我遇到的问题是须要在外部网络应用,这时候就会报错,它默认会从下面说的bellsoft-liberica/buildpack.toml里配置的uri下载,就会报错,这里找了半天,终于找到了答案,不过解决方案须要在spring-boot-maven-plugin的2.5.x以上版本才反对。maven的配置如下:<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.5.2</version> <configuration> <image> <bindings> <binding> /bellsoft-jdk-config:/platform/bindings/bellsoft-jdk-config </binding> </bindings> </image> </configuration></plugin>这里binding的意思是在构建器(构建器实际上就是个镜像)运行时,带上--volume 参数,把本地的地址映射到容器里的地址,:后面是本人的配置,能够任意,前面是容器里的地址,是固定的。 那本地地址里须要一些配置,次要是一个名为type的文件,内容就是dependency-mapping,而后再是其余的文件,文件名是在paketo-buildpacks/bellsoft-liberica的buildpack.toml里的uri上面的sha256的值,内容就是你jdk的外部网络可下载地址,配置好之后就能够一键生成镜像啦

November 3, 2021 · 1 min · jiezi

关于springboot:spring-gateway的gatewayFilter和GlobalFilter执行顺序

gateway有两种filter,第一种gatewayFilter(Route Filter)就是在yml配置文件里定义的,比方 discovery: locator: enabled: true filters: - StripPrefix=1这里的StripPrefix就是gatewayFilter,是在StripPrefixGatewayFilterFactory里定义的,这个filter的执行程序是1(源码在RouteDefinitionRouteLocator的loadGatewayFilters办法,这里会加载配置,失去filter和参数值,而后按配置的程序,默认进行排序,比方这里的StripPrefix是第一个,那他的order值就是1) GlobalFilter就是个别自定义的filter了,编写一个spring组件,继承GlobalFilter和Ordered即可 他们两种filter的执行程序都是由order来定义的,具体源码在FilterWebHandler的handle办法,先拿到配置文件里的gatewayFilter,在拿GlobalFilter,而后排序失去最终的执行程序。

November 3, 2021 · 1 min · jiezi

关于springboot:使用-Spring-Boot-和-SpringBootTest-进行测试

【注】本文译自: Testing with Spring Boot and @SpringBootTest - Reflectoring 应用@SpringBootTest 注解,Spring Boot 提供了一种不便的办法来启动要在测试中应用的应用程序上下文。在本教程中,咱们将探讨何时应用 @SpringBootTest 以及何时更好地应用其余工具进行测试。咱们还将钻研自定义应用程序上下文的不同办法以及如何缩小测试运行工夫。  代码示例本文附有 GitHub 上的工作代码示例。 “应用 Spring Boot 进行测试”系列本教程是系列的一部分: 应用 Spring Boot 进行单元测试应用 Spring Boot 和 @WebMvcTest 测试 MVC Web Controller应用 Spring Boot 和 @DataJpaTest 测试 JPA 查问应用 Spring Boot 和 @SpringBootTest 进行测试集成测试与单元测试在开始应用 Spring Boot 进行集成测试之前,让咱们定义集成测试与单元测试的区别。单元测试涵盖单个“单元”,其中一个单元通常是单个类,但也能够是组合测试的一组内聚类。集成测试能够是以下任何一项: 涵盖多个“单元”的测试。它测试两个或多个内聚类集群之间的交互。笼罩多个层的测试。这实际上是第一种状况的特化,例如可能涵盖业务服务和长久层之间的交互。涵盖整个应用程序门路的测试。在这些测试中,咱们向应用程序发送申请并查看它是否正确响应并依据咱们的预期更改了数据库状态。Spring Boot 提供了 @SpringBootTest 注解,咱们能够应用它来创立一个应用程序上下文,其中蕴含咱们对上述所有测试类型所需的所有对象。然而请留神,适度应用 @SpringBootTest 可能会导致测试套件运行工夫十分长。因而,对于涵盖多个单元的简略测试,咱们应该创立简略的测试,与单元测试十分类似,在单元测试中,咱们手动创立测试所需的对象图并模仿其余部分。这样,Spring 不会在每次测试开始时启动整个应用程序上下文。 测试切片咱们能够将咱们的 Spring Boot 应用程序作为一个整体来测试、一个单元一个单元地测试、也能够一层一层地测试。应用 Spring Boot 的测试切片注解,咱们能够别离测试每一层。在咱们具体钻研 @SpringBootTest 注解之前,让咱们摸索一下测试切片注解,以查看 @SpringBootTest 是否真的是您想要的。@SpringBootTest 注解加载残缺的 Spring 应用程序上下文。相比之下,测试切片正文仅加载测试特定层所需的 bean。正因为如此,咱们能够防止不必要的模仿和副作用。 ...

November 3, 2021 · 3 min · jiezi

关于springboot:SpringBoot利用AOP输出请求相应日志支持输出RequestBody中的参数

增加依赖视状况增加spring-boot-starter-aop依赖。 配置AOP切面罕用注解作用:@Aspect:申明该类为一个注解类;@Pointcut:定义一个切点,前面追随一个表达式,表达式能够定义为某个 package 下的办法,也能够是自定义注解等;切点定义好后,就是围绕这个切点做文章了:@Before: 在切点之前,织入相干代码;@After: 在切点之后,织入相干代码;@AfterReturning: 在切点返回内容后,织入相干代码,个别用于对返回值做些加工解决的场景;@AfterThrowing: 用来解决当织入的代码抛出异样后的逻辑解决;@Around: 在切入点前后织入代码,并且能够自在的管制何时执行切点; package com.guomz.demo.aspect;import com.guomz.demo.util.JSONUtil;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;/** * 输入每个申请响应的日志 */@Component@Aspect@Slf4jpublic class WebLogAspect { @Pointcut("execution(public * com.guomz.demo.controller..*.*(..))") public void webLog(){ } /** * 在切点之前织入 * @param joinPoint * @throws Throwable */ @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 开始打印申请日志 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 打印申请相干参数 log.info("========================================== Start =========================================="); // 打印申请 url log.info("URL : {}", request.getRequestURL().toString()); // 打印 Http method log.info("HTTP Method : {}", request.getMethod()); // 打印调用 controller 的全门路以及执行办法 log.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); // 打印申请的 IP log.info("IP : {}", request.getRemoteAddr()); // 打印申请入参 log.info("Request Args : {}", JSONUtil.toJson(joinPoint.getArgs())); } /** * 在切点之后织入 * @throws Throwable */ @After("webLog()") public void doAfter() throws Throwable { log.info("=========================================== End ==========================================="); // 每个申请之间空一行 log.info(""); } /** * 盘绕 * @param proceedingJoinPoint * @return * @throws Throwable */ @Around("webLog()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); // 打印出参 log.info("Response Args : {}", JSONUtil.toJson(result)); // 执行耗时 log.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime); return result; }}在切点注解中对controller包下的全副办法进行了植入,JSONUtil为本人实现的json序列化工具。本文援用自:Spring Boot AOP 切面对立打印申请与响应日志 ...

November 2, 2021 · 1 min · jiezi

关于springboot:SpringBoot整合Easyexcel操作Excel闲暇之余让我们学习更多

对于封面:Easyexcel 官网文档 Easyexcel | github 前言最近也是在写的一个小练习中,须要用到这个。趁着这次就将写个整合的Demo给大家。 心愿可能让大家有所播种。 浏览完本文,我想你对于应用Java配合Easyexcel操作Excel是齐全没有问题的啦。 一、环境筹备1.1、导入相干依赖依赖我应用Easyexcel的jar包是2021年10月的,说一句是最新版本,莫问题吧 easyexcel | maven <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --><dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.0.2</version></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId></dependency><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId></dependency><dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.74</version></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId></dependency><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version></dependency>1.2、我的项目构造搭建个我的项目大家都会啦,这里放一下我本人的构造。 二、读Excel操作 readExcel2.1、后期筹备筹备好一个xslx文件模板,我就是筹备了我本人了。 咱们创立一个实体类,来对应xlsx中的列名。 实体类@Datapublic class DemoModel { /** * 用名字去匹配,这里须要留神,如果名字反复,会导致只有一个字段读取到数据,所以咱们罕用上面这样的格局来写确定。 */ @ExcelProperty(value = "博客名", index = 0) private String name; @ExcelProperty(value = "社区", index = 1) private String communityName; @ExcelProperty(value = "主页", index = 2) private String homePageUrl; @ExcelProperty(value = "波及畛域", index = 3) private String specialty; @ExcelProperty(value = "分割邮箱", index = 4) private String email; /** * 这里用string 去接日期能力格式化。我想接管年月日格局 */ @DateTimeFormat("yyyy-MM-dd HH:mm:ss") @ExcelProperty(value = "公布的第一篇原创文章", index = 5) private String startDate;}监听器/** * 有个很重要的点 DocumentListener 不能被spring治理,要每次读取excel都要new,而后外面用到spring能够构造方法传进去 * @author crush */public class DemoListener extends AnalysisEventListener<DemoModel> { private static final Logger LOGGER = LoggerFactory.getLogger(DemoListener.class); /** * 每隔5条存储数据库,理论应用中能够3000条,而后清理list ,不便内存回收 */ private static final int BATCH_COUNT = 10; List<DemoModel> list = new ArrayList<DemoModel>(); /** * 假如这个是一个DAO,当然有业务逻辑这个也能够是一个service。当然如果不必存储这个对象没用。 */ private DemoMapper demoMapper; public DemoListener() { // 这里是demo,所以轻易new一个。理论应用如果到了spring,请应用上面的有参构造函数 demoMapper = new DemoMapper(); } /** * 如果应用了spring,请应用这个构造方法。每次创立Listener的时候须要把spring治理的类传进来 * * @param demoDAO */ public DemoListener(DemoMapper demoMapper) { this.demoMapper = demoMapper; } /** * 这个每一条数据解析都会来调用 * * @param data * one row value. Is is same as {@link AnalysisContext#readRowHolder()} * @param context */ @Override public void invoke(DemoModel data, AnalysisContext context) { LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); list.add(data); // 达到BATCH_COUNT了,须要去存储一次数据库,避免数据几万条数据在内存,容易OOM if (list.size() >= BATCH_COUNT) { saveData(); // 存储实现清理 list list.clear(); } } /** * 所有数据解析实现了 都会来调用 * * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { // 这里也要保留数据,确保最初遗留的数据也存储到数据库 saveData(); LOGGER.info("所有数据解析实现!"+count); } /** * 加上存储数据库 */ private void saveData() { LOGGER.info("{}条数据,开始存储数据库!", list.size()); //进行数据库层面操作 demoMapper.save(list); LOGGER.info("存储数据库胜利!"); }}mapper层:此处只是模仿 ...

November 1, 2021 · 5 min · jiezi

关于springboot:SpringBoot源码mvc工作流程中

DispatcherServlet 解决流程上一节讲了Spring容器启动,会把url与类办法的映射关系保存起来,这一节,就能看到它的作用啦。 DispatcherServlet是整个SpringMVC的外围,负责申请解决以及返回响应的工作。直奔主题,找到DispatcherServlet类,再找到该类下的doService()办法,要害代码: try { doDispatch(request, response);}点击这个孤单的办法,进去,上外围马: protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; // 定义handler执行链,重要 HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; ModelAndView mv = null; // 查看是否是文件申请 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // 获取以后申请的handler mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // 获取以后申请的适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 判断get办法是否有批改,没有间接返回 String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 前置解决 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // hander理论执行 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); applyDefaultViewName(processedRequest, mv); // handler后置解决 mappedHandler.applyPostHandle(processedRequest, response, mv); // 解决散发后的后果 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}看到那个闪亮的办法没 - getHandler(processedRequest),点击进去,下马: ...

October 31, 2021 · 2 min · jiezi

关于springboot:spring-boot-设置创建用户和修改用户总结

前言对于设置批改和创立用户博主耽搁了挺长时间的,此次也是对于教程装璜器模式有了一个更深的了解。 需要形容在每个实体上都减少createUser和updateUser字段,别离代表的是创立以后数据的用户和更新以后数据的用户。 难点剖析:1.找到注解使在数据在创立和更新时执行相应的操作2.获取以后登陆用户 踩坑过程1.在更新或者创立执行操作通过关键字在google查问首先看到的是@PrePersist和@PreUpdate @PrePersist:该注解用在办法上能够使该办法在执行长久化操作之前执行@PreUpdate:该注解用在办法上能够使该办法在执行数据更新并保留操作之前执行 2.获取以后登陆用户形容:在@PrePersist和@PreUpdate注解的办法上获取以后登陆用户并进行createUser和updateUser设置。 第一想法:1.间接调用userService的getCurrentLoginUser(获取以后登陆用户)办法 @Override public Optional<User> getCurrentLoginUser() { logger.debug("依据认证获取以后登录用户名,并获取该用户"); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { logger.debug("依据username调用userRepository获取用户") Optional<User> user = this.userRepository.findByUsername(authentication.getName()); return user; } logger.debug("认证用户在数据库中不存在"); return Optional.empty(); }后果:陷入死循环 其余尝试:应用@Transient想着将在实体应用到的userRepository设置为实体不存在的属性,后果:又出了其余更多错。 @CreateBy and @LastModifiedBy@CreateBy:设置创立数据的用户@LastModifiedBy:设置最近批改数据的用户注:@CreateBy 和 @LastModifiedBy通常搭配AuditorAware应用 AuditingWhat is database Auditing?It keeps a track of who created or changed an entity and when the change happened.so:AuditorAware是spring领有该性能的一个接口 三者搭配应用1.指定实体的字段应用@CreateBy和@LastModifiedBy: @ManyToOne @JsonView(CreateUserJsonView.class) @NotFound(action = NotFoundAction.IGNORE) @CreatedBy private User createUser; @ManyToOne @JsonView(UpdateUserJsonView.class) @NotFound(action = NotFoundAction.IGNORE) @LastModifiedBy private User updateUser;2.在实体上加上@EntityListeners(AuditingEntityListener.class)注解①(或者能够间接用实体实例化AuditorAware接口)② ...

October 29, 2021 · 2 min · jiezi

关于springboot:SpringBoot源码mvc工作流程上

SpringMVC 这么重要,怎么能错过,搞起~ 在初始化容器的时候,会把url与类办法的映射关系注册进去,所有从AbstractHandlerMethodMapping 类说起,找到该类下的initHandlerMethods() 办法,代码如下:protected void initHandlerMethods() { // 获取容器初始化的bean,遍历 for (String beanName : getCandidateBeanNames()) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { processCandidateBean(beanName); } } handlerMethodsInitialized(getHandlerMethods());}点击进入processCandidateBean()办法,外围代码如下: protected void processCandidateBean(String beanName) { Class<?> beanType = null; // 获取bean的类型 beanType = obtainApplicationContext().getType(beanName); // 如果有注解 @Controller 或 @RequestMapping,则进入 if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); }}isHandler()办法很简略,就是判断beanType是否有 @Controller 或 @RequestMapping 注解: protected boolean isHandler(Class<?> beanType) { return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType,equestMapping.class));}回到 processCandidateBean()办法,点击 detectHandlerMethods(),进入,外围代码: protected void detectHandlerMethods(Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { Class<?> userType = ClassUtils.getUserClass(handlerType); // 泛型T是理论是 RequestMappingInfo 类型 Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { // 获取办法的映射 return getMappingForMethod(method, userType); }); methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method,userType); // 注册办法 registerHandlerMethod(handler, invocableMethod, mapping); }); }}点击 getMappingForMethod()办法,外围代码: ...

October 29, 2021 · 2 min · jiezi

关于springboot:SpringBootVue3-项目实战打造企业级在线办公系统MK

download:SpringBoot+Vue3 我的项目实战,打造企业级在线办公零碎MK1) 去掉循環冗餘括號Go语言在眾多鉅匠的豐厚實戰經歷的基础上诞生,去除了C语言语法中一些冗餘、繁缛的部分。上面的代码是C语言的數值循環:// C语言的for數值循環for(int a = 0;a<10;a++){ // 循環代码}在Go语言中,這樣的循環變爲:for a := 0;a<10;a++{ // 循環代码}for 兩邊的括號被去掉,int 聲明被简化爲:=,间接經過編译器右值推導获得 a 的變量類型並聲明。2) 去掉表達式冗餘括號同樣的简化也能夠在判別语句中表現出來,以下是C语言的判別语句:if (表達式){ // 表達式成立}在Go语言中,無須增加表達式括號,代码如下:if 表達式{ // 表達式成立}3) 強迫的代码作風Go语言中,左括號必须緊接著语句不換行。其余样式的括號將被視爲代码編译錯誤。這個个性剛開端會使開發者有一些不習氣,但隨著對Go语言的不時熟習,開發者就會發現作風統一讓大家在阅讀代码時把注意力集中到了解決問題上,而不是代码作風上。 同時Go语言也提供了一套格式化工具。一些Go语言的開發環境或者編輯器在保存時,都會運用格式化工具對代码进行格式化,讓代码提交時曾經是統一格局的代码。4) 不再纠結於 i++ 和 ++iC语言非常經典的考試題爲:純文本復製int a, b;a = i++;b = ++i;

October 29, 2021 · 1 min · jiezi

关于springboot:全网最细的SpringBoot系列教程不一样的Hello

对于SpringBoot第1篇:SprintBoot的前世今生稍后会奉上,本篇是SpringBoot系列的第2篇文章,在前面系列的教程中,会具体分享SpringBoot生态圈中的各个成员,例如: 最根底的3层架构拜访数据库的3种罕用计划MyBatisMyBatis-Plusfluent mybatisNoSQLRedisMongoDBElasticSearch音讯队列RabbitMQKafkaRocketMQ...系列教程特点支流:分享的都是支流的技术点具体:十分具体,会交叉各种小知识点全面:如前文所述,数据拜访层会分享MyBatisMyBatis-Plusfluent mybatissharding-JDB深度:会分享研发过程中须要留神的各种知识点,比方日志输入常遇到的坑,相对的干货创立工程Step1: 启动idea,我用的是IDEA Community Edition(不同版本,界面长的会略微有些差异),点击【New Project】按钮,就是下图中的【+】图标 Step2:如下图:抉择MavenProject SDK:抉择你本地JDK版本,我本地装置的是JDK11,想尝鲜的同学,能够装置JDK的最新版本:JDK17设置好JDK后,按【Next】按钮 Step3:下图中设置我的项目的信息,点击【Artifact Coordinates】会开展更具体的信息 老码农设置的信息如下,依据你本人的我的项目理论状况,大家自行灵便调整 属性输出阐明 知识点:对于GroupId groupId个别分为多个段,段之间用【.】宰割,通常 第一段为域:比方org(非营利组织)、com(商业组织)、cn(中国)、top(国内通用顶级域名GTLD(Generictop-level domain top))等第二段为公司名称:比方我的就设置成coderoldgeek第三段为项目名称:我设置成的是【springboot】第四段能够是子项目名称:我设置成的是【examples】groupId不要轻易设置,最好和包构造保持一致。 设置好这些信息,间接按【Finish】按钮。 Step4: 我的项目开始创立,可能会须要几秒钟,创立好后,如下图展现 Step5: 对于目录构造阐明oldgeek-springboot-examples├─.idea │─src│ └─main│ ├─java│ ├─resources│ └─test└─pom.xml 目录具体阐明参照下表: 目录阐明 对于Maven的具体教程:老码农正在认真整顿,稍后会分享给大家。创立子模块为什么要创立子模块?本篇文章前面波及的例子,其实不必创立子工程,间接在:src/main/java编写代码也能够实现。创立子模块:前面会分享很多内容,所以想依照知识点创立子工程,便于大家依据本人须要去参考。创立子模块Step1: 右键选父工程:【oldgeek-springboot-examples 】间断点击【New】 -> 【Module...】 Step2: 同样,抉择【Maven】-> 【Module SDK】,按【Next】按钮 Step3: 下图中,只须要输出Name即可,其余的不要批改Name:springboot-hello Step4: 创立子模块,工程的目录构造如下oldgeek-springboot-examples├─.idea├─springboot-hello│ └─src│ └─main│ ├─java│ └─resources│ └─pom.xml │─src│ └─main│ ├─java│ └─resources│ └─test└─pom.xml 目录阐明 对于Maven的具体教程:老码农正在认真整顿,稍后会分享给大家。有些同学会有疑难 父工程 src/main/java和src/main/resources/还有用吗?能够删掉吗?答:如果按模块创立工程,这两个目录能够删掉父工程:pom.xml 文件能够删掉吗?答:不能够,这个有大用处,本篇文章临时不开展,咱们徐徐来,莫急筹备编写第一个能启动工程,激动人心的时刻马上就要来了,持续跟着做Step1: 顺次点击:【springboot-hello】-> 【src】-> 【main】-> 【java】按右键,如下图,顺次点击 【New】-> 【Package】 ...

October 28, 2021 · 2 min · jiezi

关于springboot:使用-Spring-Boot-和-WebMvcTest-测试-MVC-Web-Controller

【注】本文译自: Testing MVC Web Controllers with Spring Boot and @WebMvcTest - Reflectoring 在无关应用 Spring Boot 进行测试的系列的第二局部中,咱们将理解 Web 控制器。首先,咱们将摸索 Web 控制器的理论作用,这样咱们就能够构建涵盖其所有职责的测试。而后,咱们将找出如何在测试中涵盖这些职责。只有涵盖了这些职责,咱们能力确保咱们的控制器在生产环境中按预期运行。  代码示例本文附有 GitHub 上的工作代码示例。 依赖咱们将应用 JUnit Jupiter (JUnit 5) 作为测试框架,应用 Mockito 进行模仿,应用 AssertJ 来创立断言,应用 Lombok 来缩小样板代码: dependencies { compile('org.springframework.boot:spring-boot-starter-web') compileOnly('org.projectlombok:lombok') testCompile('org.springframework.boot:spring-boot-starter-test') testCompile('org.junit.jupiter:junit-jupiter:5.4.0') testCompile('org.mockito:mockito-junit-jupiter:2.23.0')}AssertJ 和 Mockito 追随 spring-boot-starter-test 依赖主动取得。 Web 控制器的职责让咱们从一个典型的 REST 控制器开始: @RestController@RequiredArgsConstructorclass RegisterRestController { private final RegisterUseCase registerUseCase; @PostMapping("/forums/{forumId}/register") UserResource register(@PathVariable("forumId") Long forumId, @Valid @RequestBody UserResource userResource, @RequestParam("sendWelcomeMail") boolean sendWelcomeMail) { User user = new User(userResource.getName(), userResource.getEmail()); Long userId = registerUseCase.registerUser(user, sendWelcomeMail); return new UserResource(userId, user.getName(), user.getEmail()); }}控制器办法用 @PostMapping 注解来定义它应该侦听的 URL、HTTP 办法和内容类型。它通过用 @PathVariable、@RequestBody 和 @RequestParam 注解的参数获取输出,这些参数会从传入的 HTTP 申请中主动填充。参数能够应用 @Valid 进行注解,以批示 Spring 应该对它们 bean 验证。而后控制器应用这些参数,调用业务逻辑返回一个一般的 Java 对象,默认状况下该对象会主动映射到 JSON 并写入 HTTP 响应体。这里有很多 spring 魔法。总之,对于每个申请,控制器通常会执行以下步骤: ...

October 27, 2021 · 5 min · jiezi

关于springboot:activiti-添加流程模型并返回-modelId

好买网 GoodMai。com IT技术交易平台 /**增加流程模型并返回modelId * @param process_id //流程惟一标识key * @param process_author //流程作者 * @param name //流程名称 * @param modelname //模型名称 * @param description //模型形容 * @param category //模型分类 * @from fhadmin.cn * @throws UnsupportedEncodingException */ protected String createModel(String process_id,String process_author,String name,String modelname,String description,String category) throws UnsupportedEncodingException{ ObjectMapper objectMapper = new ObjectMapper(); ObjectNode editorNode = objectMapper.createObjectNode(); editorNode.put("id", "canvs"); editorNode.put("resourceId", "canvs"); ObjectNode stencilSetNode = objectMapper.createObjectNode(); stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#"); //命名空间(禁止批改) stencilSetNode.put("author", "fhadmin.cn"); //流程节点作者 editorNode.set("stencilset", stencilSetNode); ObjectNode propertiesNode = objectMapper.createObjectNode(); propertiesNode.put("process_id",process_id); //流程惟一标识 propertiesNode.put("process_author",process_author); //流程作者 propertiesNode.put("name",name); //流程名称 editorNode.set("properties", propertiesNode); ObjectNode modelObjectNode = objectMapper.createObjectNode(); modelObjectNode.put("name", modelname); //模型名称 modelObjectNode.put("revision", 1); //模型版本 modelObjectNode.put("description", description); //模型形容 Model modelData = repositoryService.newModel(); modelData.setCategory(category); //模型分类 modelData.setDeploymentId(null); modelData.setKey(null); modelData.setMetaInfo(modelObjectNode.toString()); modelData.setName(modelname); //模型名称 modelData.setTenantId(""); modelData.setVersion(1); repositoryService.saveModel(modelData); //保留模型,存储数据到表:act_re_model 流程设计模型部署表 repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));//保留资源,存储数据到表:act_ge_bytearray 二进制数据表 return modelData.getId(); }---------------------------------fhadmin.cn---------------------------------自定义表单 ...

October 27, 2021 · 1 min · jiezi

关于springboot:从浏览器发送请求给SpringBoot后端时是如何准确找到哪个接口的下篇

纸上得来终觉浅,绝知此事要躬行留神: 本文 SpringBoot 版本为 2.5.2; JDK 版本 为 jdk 11. 前言:前文:你理解SpringBoot启动时API相干信息是用什么数据结构存储的吗?(上篇) 写文的起因,前文说过就不再复述了。 问题大抵如下: 为什么浏览器向后端发动申请时,就晓得要找的是哪一个接口?采纳了什么样的匹配规定呢? SpringBoot 后端是如何存储 API 接口信息的?又是拿什么数据结构存储的呢? @ResponseBody@GetMapping("/test")public String test(){ return "test";}说实话,听他问完,我感觉我又不够卷了,几乎灵魂拷问,我一个答不进去。咱们一起去理解理解吧! 如果文章中有不足之处,请你肯定要及时批正!在此郑重感激。 启动流程 一、申请流程其余的不看了,咱们就间接从 DispatcherServlet 处动手了. 咱们只看咱们关注的,不是咱们关注的,咱们就不做多探讨了. 这边同样也画了一个流程图给大家参考: 1.1、DispatcherServlet咱们都相熟SpringMVC 解决申请的模式,就不多探讨了.间接肝了.0 1)doService@Overrideprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request); // Keep a snapshot of the request attributes in case of an include, // to be able to restore the original attributes after the include. Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } // 使框架对象可用于处理程序和视图对象。 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); if (this.flashMapManager != null) { FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); } RequestPath previousRequestPath = null; if (this.parseRequestPath) { previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE); ServletRequestPathUtils.parseAndCache(request); } try { // 从这里去下一步. doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } if (this.parseRequestPath) { ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request); } }}2)doDispatchprotected 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); // Determine handler for the current request. // 获取匹配的执行链 这里就是咱们下一处入口了 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } //返回此处理程序对象的 HandlerAdapter。 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); 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); } } }}3)getHandler返回此申请的 HandlerExecutionChain。按程序尝试所有处理程序映射。 ...

October 26, 2021 · 5 min · jiezi

关于springboot:你了解SpringBoot启动时API相关信息是用什么数据结构存储的吗上篇

纸上得来终觉浅,绝知此事要躬行留神: 本文 SpringBoot 版本为 2.5.2; JDK 版本 为 jdk 11. 后续文章 从浏览器发送申请给SpringBoot后端时,是如何精确找到哪个接口的?(下篇) 前言:在写文章的时候,我都会习惯性的记录下,是什么因素促使我去写的这篇文章。并竟对于感兴趣的货色,写起来也上心,也更得心应手,文章品质相应也更高。当然更多的是想和更多人分享本人的认识,与更多的人一起交换。“三人行,必有我师焉” ,欢送大家留言评论交换。 写这篇文章的起因是在于昨天一个学 Go 语言的后端小伙伴,问了我一个问题。 问题大抵如下: 为什么浏览器向后端发动申请时,就晓得要找的是哪一个接口?采纳了什么样的匹配规定呢? SpringBoot 后端是如何存储 API 接口信息的?又是拿什么数据结构存储的呢? @ResponseBody@GetMapping("/test")public String test(){ return "test";}说实话,听他问完,我感觉我又不够卷了,几乎灵魂拷问,我一个答不进去。咱们一起去看看吧。 我对于SpringBoot的框架源码浏览教训可能就一篇SpringBoot主动拆卸原理算是吧,所以在肯定水平上我集体对于SpringBoot 框架了解的还是十分通俗的。 如果文章中有不足之处,请你肯定要及时批正!在此郑重感激。 一、注解派生概念算是一点点前提概念吧 在java体系中,类是能够被继承,接口能够被实现。然而注解没有这些概念,而是有一个派生的概念。举例,注解A。被标记了在注解B头上,那么咱们能够说注解B就是注解A的派生。 如: 就像 注解 @GetMapping 上就还有一个 @RequestMapping(method = RequestMethod.GET) ,所以咱们实质上也是应用了 @RequestMapping注解。 @Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documented@RequestMapping(method = RequestMethod.GET)public @interface GetMapping {}还有 @Controller 和 @RestController 也是如此。 @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Controller@ResponseBodypublic @interface RestController {}废话不多说,间接肝啦。 二、启动流程更后面的不去做探索了,咱们间接到这个入口处。 做了一个大抵的剖析流程图给大家做参考,也是我集体探索的路线。 2.1、AbstractHandlerMethodMapping/** HandlerMapping实现的形象基类,定义了申请和HandlerMethod之间的映射。对于每个注册的处理程序办法,一个惟一的映射由定义映射类型<T>细节的子类保护 */public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean { // ... /**在初始化时检测处理程序办法。 能够说是入口处啦*/ @Override public void afterPropertiesSet() { initHandlerMethods(); } /**扫描 ApplicationContext 中的 bean,检测和注册处理程序办法。 */ protected void initHandlerMethods() { //getCandidateBeanNames() :确定应用程序上下文中候选 bean 的名称。 for (String beanName : getCandidateBeanNames()) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { //确定指定候选 bean 的类型,如果标识为处理程序类型,则调用detectHandlerMethods // 这里的处理程序 就为咱们在controller 中书写的那些接口办法 processCandidateBean(beanName); } } // 这里的逻辑不做探讨啦 handlerMethodsInitialized(getHandlerMethods()); } // ...} ...

October 26, 2021 · 4 min · jiezi

关于springboot:Spring-Boot系列之使用Scheduled实现定时任务

假如,咱们有一个数据同步的需要:每隔5秒执行一次数据同步。那么咱们该如何实现这个数据同步工作呢? 哈喽,大家好,我是小冯。 明天给分享在Spring Boot我的项目中应用@Scheduled实现定时工作。 疾速开始咱们就下面的需要,基于Spring Boot框架,搭建一个简略的数据同步调度工作。 Demo如下。 创立工程<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency></dependencies>因为咱们是基于Spring Boot开发,所以不须要其余依赖。 package com.fengwenyi.demospringbootscheduled;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/** * @author <a href="https://www.fengwenyi.com">Erwin Feng</a> * @since 2021-09-29 */@SpringBootApplicationpublic class DemoSpringBootScheduledApplication { public static void main(String[] args) { SpringApplication.run(DemoSpringBootScheduledApplication.class, args); }}启用调度注解package com.fengwenyi.demospringbootscheduled.config;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.EnableScheduling;/** * @author <a href="https://www.fengwenyi.com">Erwin Feng</a> * @since 2021-09-29 */@Configuration@EnableSchedulingpublic class ScheduledConfiguration {}数据同步工作package com.fengwenyi.demospringbootscheduled.task;import lombok.extern.slf4j.Slf4j;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;/** * @author <a href="https://www.fengwenyi.com">Erwin Feng</a> * @since 2021-10-21 */@Component@Slf4jpublic class DemoTask { @Scheduled(initialDelay = 5, fixedRate = 5, timeUnit = TimeUnit.SECONDS) public void dataSynchronizationTask() { log.info("开始执行数据同步工作"); }}执行通过意思步骤,咱们的demo就搭建好了,跑一下,控制台打印日志如下: ...

October 25, 2021 · 3 min · jiezi

关于springboot:SpringBoot-参数别名实现

SpringBoot 参数别名实现首发于 Dale‘s blog 背景与痛点我的项目中经常出现一种状况:定了某个参数之后,前端又要求改参数名字,而你又不想因为这个名字而扭转代码的优雅,于是就须要领有一个参数对应两个参数名的能力。具体来说:业务领有自定义协定,在java实体中定义的名字是全名,例如:name getter、setter 别离为 getName() 和 setName(String name)。然而,在协定转发的过程中须要应用简写来优化音讯体的大小{"nm":"Dale"},不晓得何种起因,前端要求应用nm对应原有的name。 思路应用注解设置参数的别名,将别名与实体的参数名绑定。而后再利用ExtendedServletRequestDataBinder.addBindValues从新将别名对应的值与实体的参数名对应。有些拗口,以背景中的例子为例:接管到的参数为nm,值为Dale。通过绑定之后,将nm的值绑定到name上。 废话不多,间接上代码。 代码ValueFrom /** * 申请参数别名注解 * * @author Dale */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ValueFrom { /** * 参数别名列表 */ String[] value();}AliasDataBinder /** * 别名数据绑定 * * @author Dale */public class AliasDataBinder extends ExtendedServletRequestDataBinder { public AliasDataBinder(Object target, String objectName) { super(target, objectName); } @Override protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) { super.addBindValues(mpvs, request); Class<?> targetClass = Objects.requireNonNull(getTarget()).getClass(); Class<?> targetFatherClass = targetClass.getSuperclass(); // 利用反射获取类的字段 Field[] fields = targetClass.getDeclaredFields(); Field[] superFields = targetFatherClass.getDeclaredFields(); for (Field field : fields) { ValueFrom valueFromAnnotation = field.getAnnotation(ValueFrom.class); if (mpvs.contains(field.getName()) || valueFromAnnotation == null) { continue; } for (String alias : valueFromAnnotation.value()) { if (mpvs.contains(alias)) { mpvs.add(field.getName(), Objects.requireNonNull(mpvs.getPropertyValue(alias)).getValue()); break; } } } // 将参数绑定到父类上 for (Field field : superFields) { ValueFrom valueFromAnnotation = field.getAnnotation(ValueFrom.class); if (mpvs.contains(field.getName()) || valueFromAnnotation == null) { continue; } for (String alias : valueFromAnnotation.value()) { if (mpvs.contains(alias)) { mpvs.add(field.getName(), Objects.requireNonNull(mpvs.getPropertyValue(alias)).getValue()); break; } } } }}AliasModelAttributeMethodProcessor ...

October 19, 2021 · 2 min · jiezi

关于springboot:SpringBoot-整合-Thymeleaf-如何使用后台模板快速搭建项目

如果你和我一样,是一名 Java 路线上的编程男孩,其实我不太倡议你花工夫学 Thymeleaf,当然他的思维还是值得借鉴的。然而他的实质在我看来就是 Jsp 技术的翻版(Jsp 当初用的真的很少很少)。弄前端齐全能够间接上手前端框架 vue。 并竟学Java在我眼里,目前没有什么是不要学的。兼测试、运维、前端啥都要会点。另外就目前来说,学Java的人数恐怕依然后端中最宏大的。 收费后盾模板在文末,大家有需要能够间接下载。 我想如果不是学校作业,也不会灵机一动写这篇文章。 浏览本文播种 学会 Thymeleaf 罕用语法♀️通晓 Thymeleaf 如何与 SpringBoot 集成♀️应用 Thymeleaf 实现学校老师作业 如果有需要,能够间接下个模板,联合SpringBoot 写个毕业设计一、 Thymeleaf 初介绍 Thymeleaf 官网 Thymeleaf 官网文档 Thymeleaf是实用于 Web 和独立环境的古代服务器端 Java 模板引擎。 Thymeleaf 的次要指标是为您的开发工作流程带来优雅的天然模板——HTML能够在浏览器中正确显示,也能够作为动态原型工作,从而增强开发团队的合作。 凭借 Spring Framework 的模块、与您最喜爱的工具的大量集成以及插入您本人的性能的能力,Thymeleaf 是古代 HTML5 JVM Web 开发的现实抉择——只管它还有更多功能。 ---官网介绍 二、SpringBoot 整合 Thymeleaf 次要针对咱们在我的项目中最常见的几种用法进行解说。同时咱们也是在我的项目中间接讲 Thymeleaf 的用法。 2.1、新建 SpringBoot 我的项目这个就不用说了哈,我想大家都是会这个的吧。 2.2、导入依赖<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.2</version> <relativePath/></parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency></dependencies>2.3、SpringBoot 动态资源寄存门路首先咱们将模板下载下来: ...

October 19, 2021 · 2 min · jiezi

关于springboot:附答案超全SpringBoot面试题总结

我把所有Java相干的面试题和答案都整顿成了PDF,并且带书签目录,浏览起来十分不便 面试题及答案PDF下载:https://www.hicxy.com/?p=2645 面试题及答案PDF下载:https://www.hicxy.com/?p=2645 面试题及答案PDF下载:https://www.hicxy.com/?p=2645 1. Spring Boot 有哪几种读取配置的形式?Spring Boot 能够通过 @PropertySource@Value@Environment,@ConfigurationProperties来绑定变量 2. spring boot 外围配置文件是什么?bootstrap.properties 和 application.properties 有何区别 ?单纯做 Spring Boot 开发,可能不太容易遇到 bootstrap.properties 配置文件,然而在联合 Spring Cloud 时,这个配置就会常常遇到了,特地是在须要加载一些近程配置文件的时侯。 spring boot 外围的两个配置文件: bootstrap (. yml 或者 . properties): boostrap 由父 ApplicationContext 加载的,比 applicaton 优先加载,配置在应用程序上下文的疏导阶段失效。一般来说咱们在 Spring Cloud Config 或者 Nacos 中会用到它。且 boostrap 外面的属性不能被笼罩;application (. yml 或者 . properties): 由ApplicatonContext 加载,用于 spring boot 我的项目的自动化配置。3. Spring Boot 打成的 jar 和一般的 jar 有什么区别1、 Spring Boot 我的项目最终打包成的 jar 是可执行 jar ,这种 jar 能够间接通过 java -jar xxx.jar 命令来运行,这种 jar 不能够作为一般的 jar 被其余我的项目依赖,即便依赖了也无奈应用其中的类。2、 Spring Boot 的 jar 无奈被其余我的项目依赖,次要还是他和一般 jar 的构造不同。一般的 jar 包,解压后间接就是包名,包里就是咱们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 \BOOT-INF\classes 目录下才是咱们的代码,因而无奈被间接援用。如果非要援用,能够在 pom.xml 文件中减少配置,将 Spring Boot 我的项目打包成两个 jar ,一个可执行,一个可援用。 ...

October 18, 2021 · 2 min · jiezi

关于springboot:重识CurdRepository

序言有一个需要:判断数据库中是否存在name为xxx的数据。因为在记忆中抹除了对于CurdRepository的提供的办法。所以在实现该性能的时候并没有想到findByxxx办法。 问题再现需要判断数据库中是否有相应的name,如果有name则返回false,否则为true;我的实现办法 @Override public Boolean existByName(String name) { // 调用findOne办法,获取数据库中name属性为name的数据(不止一条则会报错) // TownSpecs.equalName(name)结构查问条件 Optional<Town> result = this.townRepository.findOne(TownSpecs.equalName(name)); return this.townRepository.findOne(TownSpecs.equalName(name)).isPresent(); }CurdRepository在教程学习的时候始终在应用它,却没有去想它的作用,为啥要内置一个CurdRepository?为啥像PagingAndSortingRepository都继承了它?那么它作用到底是啥? curd翻译后恍然大悟!猜想是负责内置增删改查办法的库! 验证看官网文档:没有想要的后果:都是如下的根本办法 发现关键字派生:derive按关键字spring data crudrepository derive进行google搜寻,第一条就是想要的: 1. Structure of Derived Query Methods in Spring1.Derived method names have two main parts separated by the first By keyword:派生办法名称有两个次要局部,由第一个by关键字分隔: 例如:List<User> findByName(String name) 依据name进行查问2.We can also use Distinct, First, or Top to remove duplicates or limit our result set: 咱们还能够应用Distinct、First或Top删除反复项或限度后果集:我的项目中曾经用到过了 3.Equality Condition Keywords相等条件关键字 ...

October 16, 2021 · 1 min · jiezi

关于springboot:SpringBoot实现文件在线预览

背景最近公司外部oa系统升级,须要减少文件在线预览服务,最常见的文件就是office文档,一开始构思几个计划,比方office软件自带的文件转换,openoffice转换,offce365服务,aspose组件转换,最终采纳了aspose转换,起因是组件功能完善,不依赖其它软件装置环境 零碎设计文件类型及计划文件类型预览计划wordaspsoe-word转换图片预览(版本21.1)pptaspose-slides转化你图片预览(版本20.4)excelaspose-cell转换html预览(版本20.4)pdfpdfbox缓缓图片预览(版本2.0.15)png,jpg,gif整合viewer.js预览(版本1.5.0)mp4整合vedio.js预览(js版本7.10.2)txt读取文件内容预览注:aspose因版权问题,工程示例代码中全副应用试用版,转换图片会呈现水印 流程设计 零碎实现辨认文件后缀URL指向文件实在门路时依据后缀名判断 public static String getTypeByExtenssion(String linkUrl) { if (linkUrl == null) return null; linkUrl = linkUrl.toLowerCase(); for (String ext : extensions) { if (linkUrl.endsWith(ext)) { return ext; } } return null; }URL为文件输入流时依据文件输入流的disposition private static String getTypeByDisposition(String disposition) { String ext = null; if (!StringUtils.isEmpty(disposition)) { disposition = StringUtils.replace(disposition, "\"", ""); String[] strs = disposition.split(";"); for (String string : strs) { if (string.toLowerCase().indexOf("filename=") >= 0) { ext = StringUtils.substring(string, string.lastIndexOf(".")); break; } } } return ext; }依据文件输入流content-type types = new HashMap<String, String>(); types.put("application/pdf", ".pdf"); types.put("application/msword", ".doc"); types.put("text/plain", ".txt"); types.put("application/javascript", ".js"); types.put("application/x-xls", ".xls"); types.put("application/-excel", ".xls"); types.put("text/html", ".html"); types.put("application/x-rtf", ".rtf"); types.put("application/x-ppt", ".ppt"); types.put("image/jpeg", ".jpg"); types.put("application/vnd.openxmlformats-officedocument.wordprocessingml.template", ".docx"); types.put("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xlsx"); types.put("application/vnd.openxmlformats-officedocument.presentationml.presentation", ".pptx"); types.put("message/rfc822", ".eml"); types.put("application/xml", ".xml");依据stream的固定字节判断 FILE_TYPE_MAP.put(".pdf", "255044462D312E"); // Adobe Acrobat (pdf) FILE_TYPE_MAP.put(".doc", "D0CF11E0"); // MS Word FILE_TYPE_MAP.put(".xls", "D0CF11E0"); // MS Excel 留神:word 和 excel的文件头一样 FILE_TYPE_MAP.put(".jpg", "FFD8FF"); // JPEG (jpg) FILE_TYPE_MAP.put(".png", "89504E47"); // PNG (png) FILE_TYPE_MAP.put(".gif", "47494638"); // GIF (gif) FILE_TYPE_MAP.put(".tif", "49492A00"); // TIFF (tif) FILE_TYPE_MAP.put(".bmp", "424D"); // Windows Bitmap (bmp) FILE_TYPE_MAP.put(".dwg", "41433130"); // CAD (dwg) FILE_TYPE_MAP.put(".html", "68746D6C3E"); // HTML (html) FILE_TYPE_MAP.put(".rtf", "7B5C727466"); // Rich Text Format (rtf) FILE_TYPE_MAP.put(".xml", "3C3F786D6C"); FILE_TYPE_MAP.put(".zip", "504B0304"); // docx的文件头与zip的一样 FILE_TYPE_MAP.put(".rar", "52617221"); FILE_TYPE_MAP.put(".psd", "38425053"); // Photoshop (psd) FILE_TYPE_MAP.put(".eml", "44656C69766572792D646174653A"); // Email FILE_TYPE_MAP.put(".dbx", "CFAD12FEC5FD746F"); // Outlook Express (dbx) FILE_TYPE_MAP.put(".pst", "2142444E"); // Outlook (pst) FILE_TYPE_MAP.put(".mdb", "5374616E64617264204A"); // MS Access (mdb) FILE_TYPE_MAP.put(".wpd", "FF575043"); // WordPerfect (wpd) FILE_TYPE_MAP.put(".eps", "252150532D41646F6265"); FILE_TYPE_MAP.put(".ps", "252150532D41646F6265"); FILE_TYPE_MAP.put(".qdf", "AC9EBD8F"); // Quicken (qdf) FILE_TYPE_MAP.put(".pwl", "E3828596"); // Windows Password (pwl) FILE_TYPE_MAP.put(".wav", "57415645"); // Wave (wav) FILE_TYPE_MAP.put(".avi", "41564920"); FILE_TYPE_MAP.put(".ram", "2E7261FD"); // Real Audio (ram) FILE_TYPE_MAP.put(".rm", "2E524D46"); // Real Media (rm) FILE_TYPE_MAP.put(".mpg", "000001BA"); // FILE_TYPE_MAP.put(".mov", "6D6F6F76"); // Quicktime (mov) FILE_TYPE_MAP.put(".asf", "3026B2758E66CF11"); // Windows Media (asf) FILE_TYPE_MAP.put(".mid", "4D546864"); // MIDI (mid)文件解析word分页转换图片Document doc = new Document(fileConvertInfo.getFilePath());for (int i = 0; i < doc.getPageCount(); i++) { Document extractedPage = doc.extractPages(i, 1); extractedPage.save(fileConvertInfo.getFileDirPath() + "split_" + (i + 1) + ".jpeg", SaveFormat.JPEG);}ppt分页转换图片Presentation ppt = new Presentation(fileConvertInfo.getFilePath()); for (int i = 0; i < ppt.getSlides().size(); i++) { ISlide slide = ppt.getSlides().get_Item(i); int height = (int) (ppt.getSlideSize().getSize().getHeight() - 150); int width = (int) (ppt.getSlideSize().getSize().getWidth() - 150); BufferedImage image = slide.getThumbnail(new java.awt.Dimension(width, height)); //每一页输入一张图片 File outImage = new File(fileConvertInfo.getFileDirPath() + "split_" + (i + 1) + ".jpeg"); ImageIO.write(image, "jpeg", outImage);}excel转换htmlWorkbook wb = new Workbook(fileConvertInfo.getFilePath());HtmlSaveOptions opts = new HtmlSaveOptions();opts.setExportWorksheetCSSSeparately(true);opts.setExportSimilarBorderStyle(true);Worksheet ws = wb.getWorksheets().get(0);wb.save(fileConvertInfo.getFileDirPath() + "convert.html", opts);excel分页转换图片(另一种预览形式)Workbook wb = new Workbook(fileConvertInfo.getFilePath());ImageOrPrintOptions imgOptions = new ImageOrPrintOptions();imgOptions.setImageFormat(ImageFormat.getJpeg());for (int i = 0; i < wb.getWorksheets().getCount(); i++) { Worksheet sheet = wb.getWorksheets().get(i); SheetRender sr = new SheetRender(sheet, imgOptions); sr.toImage(i, fileConvertInfo.getFileDirPath() + "split_" + (i + 1) + ".jpeg");}pdf分页转换图片PDDocument pdf = PDDocument.load(new File((fileConvertInfo.getFilePath())));int pageCount = pdf.getNumberOfPages();PDFRenderer renderer = new PDFRenderer(pdf);for (int i = 0; i < pageCount; i++) { BufferedImage image = renderer.renderImage(i, 1.25f); // 第二个参数越大生成图片分辨率越高,转换工夫也就越长 ImageIO.write(image, "JPEG", new File(fileConvertInfo.getFileDirPath() + "split_" + (i + 1) + ".jpeg"));}pdf.close();预览图片<body><div id="app"> <img id="image" style="display: none"></div></body></html><script> $(function () { $("#image").attr("src", getQueryString("file")); var image = new Viewer(document.getElementById('image'),{ url: 'data-original', button:false, navbar:false, backdrop: false }); document.getElementById('image').click(); })</script>预览视频<body><div id="app"> <video id="myvideo" class="video-js vjs-big-play-centered" controls data-setup="{}" width="1366" height="768" preload="auto"> <source id="vedio" src="//vjs.zencdn.net/v/oceans.mp4" type="video/mp4"></source> </video></div></body></html><script> $(function () { $("#vedio").attr("src", getQueryString("file")); })</script>零碎成果应用办法间接运行我的项目,输出预览地址 ...

October 16, 2021 · 3 min · jiezi

关于springboot:Spring单元测试教程JUnit5Mockito

1. 测试如果项目组有测试团队,最常接触的概念有:功能测试、回归测试、冒烟测试等,但这些都是由测试人员发动的。 开发人员写的经常是“单元测试”,但其实能够细分成 单元测试 和 集成测试 两个。 划分的起因拿常见的 Spring IoC 举例。Spring 不同Bean之间相互依赖,例如某API业务逻辑中会依赖不同模块的 Service,Service 办法中又可能依赖不同的 Dao 层办法,甚至还会通过 RPC、HTTP 调用内部服务办法。这给咱们写测试用例带来了难度,原本只想测试某个办法的性能,却要思考一连串的依赖关系。 1.1. 单元测试单元测试:是指对软件中的最小可测试单元进行检查和验证。通常任何软件都会划分为不同的模块和组件。独自测试一个组件时,咱们叫做单元测试。单元测试用于验证相干的一小段代码是否失常工作。单元测试不是用于发现应用程序范畴内的 bug,或者回归测试的 bug,而是别离检测每个代码片段。 单元测试不验证利用程序代码是否和内部依赖失常工作。它聚焦与单个组件并且 Mock 所有和它交互的依赖。例如,办法中调用发短信的服务,以及和数据库的交互,咱们只须要 Mock 假执行即可,毕竟测试的焦点在以后办法上。 单元测试的特点: 不依赖任何模块。基于代码的测试,不须要在 ApplicationContext 中运行。办法执行快,500ms以内(也和不启动 Spring 无关)。同一单元测试可反复执行N次,并每次运行后果雷同。1.2. 集成测试集成测试:在单元测试的根底上,将所有模块依照设计要求组装成为子系统或零碎,进行集成测试。集成测试次要用于发现用户端到端申请时不同模块交互产生的问题。集成测试范畴能够是整个应用程序,也能够是一个独自的模块,取决于要测试什么。 在集成测试中,咱们应该聚焦于从控制器层到长久层的残缺申请。应用程序应该运行嵌入服务(例如:Tomcat)以创立应用程序上下文和所有 bean。这些 bean 有的可能会被 Mock 笼罩。 集成测试的特点: 集成测试的目标是测试不同的模块一共工作是否达到预期。应用程序应该在 ApplicationContext 中运行。Spring boot 提供 @SpringBootTest 注解创立运行上下文。应用 @TestConfiguration 等配置测试环境。2. 测试框架2.1. spring-boot-starter-testSpringBoot中无关测试的框架,次要来源于 spring-boot-starter-test。一旦依赖了spring-boot-starter-test,上面这些类库将被一起依赖进去: JUnit:java测试事实上的规范。Spring Test & Spring Boot Test:Spring的测试反对。AssertJ:提供了流式的断言形式。Hamcrest:提供了丰盛的matcher。Mockito:mock框架,能够按类型创立mock对象,能够依据办法参数指定特定的响应,也反对对于mock调用过程的断言。JSONassert:为JSON提供了断言性能。JsonPath:为JSON提供了XPATH性能。测试环境自定义Bean@TestComponent:该注解是另一种@Component,在语义上用来指定某个Bean是专门用于测试的。该注解实用于测试代码和正式混合在一起时,不加载被该注解形容的Bean,应用不多。@TestConfiguration:该注解是另一种@TestComponent,它用于补充额定的Bean或笼罩已存在的Bean。在不批改正式代码的前提下,使配置更加灵便。2.2. JUnit前者说了,JUnit 是一个Java语言的单元测试框架,但同样可用于集成测试。以后最新版本是 JUnit5。 常见区别有: JUnit4 所需 JDK5+ 版本即可,而 JUnit5 需 JDK8+ 版本,因而反对很多 Lambda 办法。JUnit 4将所有内容捆绑到单个jar文件中。Junit 5由3个子我的项目组成,即JUnit Platform,JUnit Jupiter和JUnit Vintage。外围是JUnit Jupiter,它具备所有新的junit正文和TestEngine实现,以运行应用这些正文编写的测试。而JUnit Vintage蕴含对JUnit3、JUnit4的兼容,所以spring-boot-starter-test新版本pom中往往会主动exclusion它。SpringBoot 2.2.0 开始引入 JUnit5 作为单元测试默认库,在 SpringBoot 2.2.0 之前,spring-boot-starter-test 蕴含了 JUnit4 的依赖,SpringBoot 2.2.0 之后替换成了 Junit Jupiter。JUnit5 和 JUnit4 在注解上的区别在于: ...

October 13, 2021 · 2 min · jiezi

关于springboot:SpringBoot成长记11SpringBoot完结总结

后面10节的回顾通过后面10节SpringBoot成长记的剖析,你应该对SpringBoot的原理和设计思维有了很清晰的意识了。 最初这一节呢,咱们将之前每一节最重要的知识点,提取进去一个总结。 第一节成长记1:你真的懂SpringBoot吗? 咱们通过去官网文档寻找SpringBoot外围性能,教给大家两个思维,先脉络后细节的思维和重视思考的思维。 我本人对SpringBoot性能的思考:SpringBoot定义了新的web利用启动流程、SpringBoot核解决了Spring集成各种技术的复杂性。 第二节成长记2:从HelloWorld开始剖析SpringBoot 咱们从HelloWorld开始剖析,剖析了:SpringApplication的创立时外围组件图、SpringApplication Run办法的脉络 传授给大家2个思维 1)连蒙带猜的思维,依据办法名称,正文大体猜想办法和类的作用 2)抓大放小的思维,排除不重要的,剖析最次要的。 第三节成长记3:SpringBoot扩大点之SpringApplicationRunListeners SpringApplicationRunListeners的扩大 1)对流程的扩大办法 starting started running等 2)对配置文件的扩大办法environmentPrepared 3)对容器的扩大办法contextPrepared、contextLoaded Listeners的扩大点设计 基于配置封装List<Listeners>,基于类封装不同阶段的扩大 基于观察者模式设计批量事件播送器、查问缓存升高O(n)复杂度 第四节成长记4:Run办法中配置文件的解决 咱们剖析了SpringBoot对对配置文件的解析,配置文件解决外围逻辑是创立配置文件对象、给配置文件对象设置一堆值,次要是各个配置文件。 而最要害的是配置文件的设计思考 1)配置文件须要查找:ResourceLoader(ClassLoader) 2)不同配置文件的解析:不同的Resolver 3)配置文件的内容封装:PropertySource+profile 泛型设计,存储不同格局内容 4)多个配置文件MutablePropertySources:List<PropertySource> 第五节成长记5:Spring容器的创立 Spring容器的创立时的外围组件:reader和scanner、DefaultListableBeanFactory reader和scanner的设计和作用:reader解析和减少BeanDefination、scanner查找和过滤BeanDefination 术语遍及BeanDefinition、BeanDefinitionRegistry、InternalBean 从Reader和Scanner开始思考下Spring容器的形象设计,Resource->ClassLoader->Reader/Scanner->BeanDefination的设计 第六节成长记6:筹备SpringContext容器 prepareContext()的外围脉络:给容器Context和容器DefaultListableBeanFactory设置一些属性 触发的扩大点(十分重要的操作):applyInitializers()触发的扩大点操作、触发listener对容器扩大操作 术语遍及BeanFactoryPostProcessor是什么? beanFactory的一些属性补充(值得一提的操作): 1 )beanFactory.registerSingleton注册对象到容器的singletonObjects属性 2)BeanDefinitionLoader.load()补充BeanDefinition,这里补充了1个LearnSpringBootApplication 的BeanDefinition到beanFactory中 设置几个属性或者组件(不重要的操作) 第七节成长记7:容器的扩大操作是如何执行的 疾速摸一下refreshCotenxt的脉络 invokeBeanFactoryPostProcessors之前的次要操作概括 术语遍及BeanDefinitionRegistryPostProcessor是什么? invokeBeanFactoryPostProcessors的外围脉络 1)if-else外围脉络逻辑:执行扩大办法1:postProcessBeanDefinitionRegistry()、执行扩大办法2:postProcessBeanFactory() 2)3个For循环的外围脉络逻辑:之前执行的是Spring外部定义好的一些BeanFactoryPostProcessor,在执行了if-else逻辑后,其实扫描进去了ClassPath下更多第三方和其余的BeanFactoryPostProcessor 第八节成长记8:SpringBoot实现主动拆卸配置,第三方性能的扩大之处 1)SpringBoot实现主动拆卸配置 术语遍及starter是什么? 外围的ConfigurationClassPostProcessor 思考:ConfigurationClassPostProcessor的增加BeanDefination的设计 2)第三方性能的扩大之处实现原理 思考:mybatis的Mapper如何被增加的? 基于ImportBeanDefinitionRegistrar的扩大 基于BeanFactoryPostProcessor的扩大 ...

October 12, 2021 · 1 min · jiezi

关于springboot:SpringBoot成长记10Bean实例化的流程和设计

之前咱们曾经剖析SpringBoot在run办法时,它会执行的refresh()容器的操作。 在SpringBoot中,refresh()有十几个办法,每个办法的大重要水平是不一样的,咱们通过抓大放小的形式,剖析解决上图3个外围逻辑。 并且曾经钻研完了invokeBeanFactoryPostProcessors和onRefresh的逻辑,剖析它们的原理和设计思维。 之前次要剖析的: 原理有对SpringBoot的主动拆卸配置如何做到的、第三方技术如何进行扩大的、tomcat如何启动的 设计思维有SpringBoot扩大接口设计、有对Tomcat组件的扩大设计、Spring容器形象思维的设计、SpringBoot和第三方技术整合的扩大设计等等。 refresh()还有一个十分要害的操作,就是bean的实例化,明天咱们就来看下refresh最初一个办法—finishBeanFactoryInitialization。 看看它如何执行Bean实例化的流程和设计的。 finishBeanFactoryInitialization之前和之后的操作详情 能够看到,bean的实例化前后,还是做了一些事件的,次要执行的是一些扩大点,比方listener的扩大点执行、LifycycleProcessor的执行。 这一节咱们外围关系的是bean创立流程和设计,所以咱们抓大放小,过就能够。间接来看上面创立bean吧。 preInstantiateSingletons办法的外围脉络其实bean的实例化大家或多或少都晓得一些。所以我不会特地具体的每一个办法都带大家看。 咱们还是本着先脉络后细节,最初思考的思维来剖析Bean的实例化,当你用这种办法剖析玩后,和你之前剖析对吧下有什么区别,能够感触下。 如果之后大家有诉求须要精读Bean实例化的逻辑,我之后能够思考放在Spring成长记中为大家认真带来Bean实例化的剖析。 这里咱们次要过下它的外围源码就能够了。 因为SpringBoot是为了更好的应用Spring,它是基于Spring的。如果你懂Spring实例化,这块其实十分好了解的。 让咱们来看下吧! finishBeanFactoryInitialization次要的代码如下所示: protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // Initialize conversion service for this context. if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { beanFactory.setConversionService( beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); } // Register a default embedded value resolver if no bean post-processor // (such as a PropertyPlaceholderConfigurer bean) registered any before: // at this point, primarily for resolution in annotation attribute values. if (!beanFactory.hasEmbeddedValueResolver()) { beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal)); } // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early. String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false); for (String weaverAwareName : weaverAwareNames) { getBean(weaverAwareName); } // Stop using the temporary ClassLoader for type matching. beanFactory.setTempClassLoader(null); // Allow for caching all bean definition metadata, not expecting further changes. beanFactory.freezeConfiguration(); // Instantiate all remaining (non-lazy-init) singletons. beanFactory.preInstantiateSingletons(); }这个办法的脉络其实比较清楚,其实最要害的只有一句话: ...

October 12, 2021 · 3 min · jiezi

关于springboot:SpringBoot成长记9onRefresh如何启动内嵌的Tomcat容器的

上一节咱们次要剖析了refreshContext中,次要有3个逻辑,如下图: 上一节重点解析了invokeBeanFactoryPostProcessors执行容器扩大点,实现了主动配备配置、第三方执行扩大的执行。 明天咱们持续剖析refreshContext另一个重要的逻辑onRefresh()逻辑,让咱们开始吧! 疾速概览: onRefresh启动内嵌tomcat前的操作refreshContext中onRefresh之前还有一些逻辑,咱们先来疾速看下它们次要做了什么。首先来看下代码: @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { //省略 // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); //省略 }下面次要波及了3个办法,从名字行就能猜出来它们做了什么: 1)registerBeanPostProcessors 通过扫描到BeanDefination中找出BeanPostProcessor,减少几个Bean的扩大点BeanPostProcessor 按4类程序一一减少。 回顾术语BeanPostProcessor是什么?之前BeanFactoryPostProcessor是对容器的扩大,次要有一个办法,能够给容器设置属性,补充一些单例对象,补充一些BeanDefinition。那BeanPostProcessor是对bean的扩大,有before和after两类办法,对Bean如何做扩大,在bean的创立前后,给bean补充一些属性等。2)initMessageSource 注册音讯M essageSource对象到容器 DelegatingMessageSource 国际化相干反对,默认的没有。 3)initApplicationEventMulticaster 注册播送对象到容器 这个对象就是之前触发listener扩大点的播送对象。 相熟了onRefresh办法之前的大体逻辑后,目前为止,整个rereshConext()执行的逻辑次要如下: ...

October 12, 2021 · 6 min · jiezi

关于springboot:SpringBoot成长记8SpringBoot如何实现自动装配配置和扩展

后面咱们摸清楚了整个invokeBeanFactoryPostProcessors办法的if-else逻辑和3个for循环的外围脉络逻辑。 接下来咱们来看下细节,我会通过抓大放小的思维,带大家看到在扩大点执行的过程中,最最要的有哪一些。 SpringBoot的主动拆卸配置如何做到的、第三方技术如何进行扩大的。 SpringBoot的主动拆卸配置如何做到的?if-else逻辑中哪些BeanFactoryPostProcessor执行了?之前咱们提到过,invokeBeanFactoryPostProcessors执行的BeanFactoryPostProcessor次要起源是容器的两个属性beanFactoryPostProcessors 和BeanDefinitionMap。 首先这两个属性,会在之前执行扩大操作,比方listener或者initializer的办法时,设置进去值的。执行到invokeBeanFactoryPostProcessors时,之前会设置如下图所示的值: 从上图能够看进去,执行invokeBeanFactoryPostProcessors的时候曾经有4个BeanFactoryPostProcessor。 当执行invokeBeanFactoryPostProcessors,外围脉络上一节咱们剖析出了是次要一个if-else+3个for循环组成的,这个if-else中有分了外部的、实现PriorityOrderd、Ordered、NonOrder这个四个程序执行。联合下面4个BeanFactoryPostProcessor,整体执行如下图所示: 从图中能够看进去,十分要害的一点那就是:在执行扩大办法1的过程中,通过Spring外部的一个ConfigurationClassPostProcessor,补充了新的BeanDefinition,减少了新的BeanFactoryPostProcessor。 ConfigurationClassPostProcessor这个执行十分要害,因为它补充了新的BeanDefinition。 它外围用来进行加载ClassPath下所有java注解定义的BeanDefinition。比方:本人包下定义的@Service,@Component,@Controller@Configuration @Bean等注解定义的Bean,也包含内部的starter中@Configuration @Bean等配置。 也就是你定义的大多数bean和内部starter定义的Bean的BeanDefinition都会被放入到容器中。 另外,补充了新的BeanDefinition,这里咱们简化了下,假如以后利用,只依赖了一个myBatis-starter,之后只会补充一个MyBatis相干的BeanDefinition,一个BeanFactoryPostProcessor—MapperScannerConfigurer。从名字上猜想,它应该是用来扫描MyBatis相干bean的。 invokeBeanFactoryPostProcessors的if-else逻辑中,触发了2个扩大操作,最初还会执行扩大办法2,之前的所有BeanFactoryPostProcessor,对立会执行扩大办法2。 扩大办法2执行的逻辑,根本没有什么外围的,这里咱们就间接过了,你晓得invokeBeanFactoryPostProcessors这里会触发这个扩大点,并且在扩大办法1之后执行就行了。 最终执行完if-else后,BeanFactory中的次要有如下的beanDefination: beanDefinitionNames = {ArrayList@3752} size = 164 0 = "org.springframework.context.annotation.internalConfigurationAnnotationProcessor" 1 = "org.springframework.context.annotation.internalAutowiredAnnotationProcessor" 2 = "org.springframework.context.annotation.internalCommonAnnotationProcessor" 3 = "org.springframework.context.event.internalEventListenerProcessor" 4 = "org.springframework.context.event.internalEventListenerFactory" 5 = "learnSpringBootApplication" 6 = "org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory" 7 = "userController" 8 = "myBeanPostProcessor" 9 = "userServiceImpl" 10 = "userMapper" 11 = "org.springframework.boot.autoconfigure.AutoConfigurationPackages" 12 = "org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration" 13 = "propertySourcesPlaceholderConfigurer" 14 = "org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration" 15 = "websocketServletWebServerCustomizer" 16 = "org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration" 17 = "org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat" 18 = "tomcatServletWebServerFactory" 19 = "org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration" 20 = "servletWebServerFactoryCustomizer" 21 = "tomcatServletWebServerFactoryCustomizer" 22 = "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor" 23 = "org.springframework.boot.context.internalConfigurationPropertiesBinderFactory" 24 = "org.springframework.boot.context.internalConfigurationPropertiesBinder" 25 = "org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator" 26 = "org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata" 27 = "server-org.springframework.boot.autoconfigure.web.ServerProperties" 28 = "webServerFactoryCustomizerBeanPostProcessor" 29 = "errorPageRegistrarBeanPostProcessor" 30 = "org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration" 31 = "dispatcherServlet" 32 = "spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties" 33 = "spring.http-org.springframework.boot.autoconfigure.http.HttpProperties" 34 = "org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration" 35 = "dispatcherServletRegistration" 36 = "org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration" 37 = "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration" 38 = "taskExecutorBuilder" 39 = "applicationTaskExecutor" 40 = "spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties" 41 = "org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration" 42 = "defaultValidator" 43 = "methodValidationPostProcessor" 44 = "org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration" 45 = "error" 46 = "beanNameViewResolver" 47 = "org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration" 48 = "conventionErrorViewResolver" 49 = "org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration" 50 = "errorAttributes" 51 = "basicErrorController" 52 = "errorPageCustomizer" 53 = "preserveErrorControllerTargetClassPostProcessor" 54 = "spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties" 55 = "org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration" 56 = "requestMappingHandlerAdapter" 57 = "requestMappingHandlerMapping" 58 = "welcomePageHandlerMapping" 59 = "mvcConversionService" 60 = "mvcValidator" 61 = "mvcContentNegotiationManager" 62 = "mvcPathMatcher" 63 = "mvcUrlPathHelper" 64 = "viewControllerHandlerMapping" 65 = "beanNameHandlerMapping" 66 = "routerFunctionMapping" 67 = "resourceHandlerMapping" 68 = "mvcResourceUrlProvider" 69 = "defaultServletHandlerMapping" 70 = "handlerFunctionAdapter" 71 = "mvcUriComponentsContributor" 72 = "httpRequestHandlerAdapter" 73 = "simpleControllerHandlerAdapter" 74 = "handlerExceptionResolver" 75 = "mvcViewResolver" 76 = "mvcHandlerMappingIntrospector" 77 = "org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter" 78 = "defaultViewResolver" 79 = "viewResolver" 80 = "requestContextFilter" 81 = "org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration" 82 = "formContentFilter" 83 = "org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari" 84 = "dataSource" 85 = "org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration$Hikari" 86 = "org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration" 87 = "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$PooledDataSourceConfiguration" 88 = "org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration" 89 = "hikariPoolDataSourceMetadataProvider" 90 = "org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration" 91 = "org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker" 92 = "org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfiguration" 93 = "dataSourceInitializerPostProcessor" 94 = "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration" 95 = "spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties" 96 = "com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration" 97 = "sqlSessionFactory" 98 = "sqlSessionTemplate" 99 = "mybatis-plus-com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties"SpringBoot的主动拆卸配置理论是通过starter+@Conditional注解+@Import注解+invokeBeanFactoryPostProcessors触发扩大点独特实现的。 ...

October 12, 2021 · 6 min · jiezi

关于springboot:SpringBoot成长记7容器的扩展操作是如何执行的

目前咱们剖析的代码曾经到了容器解决相干的SpringBoot原理,代码如下: public ConfigurableApplicationContext run(String... args) { //DONE 扩大点 SpringApplicationRunListeners listeners.starting(); //DONE 配置文件的解决和形象封装 ConfigurableEnvironment //容器相干解决 //1)外围就是创立了Context和BeanFactory对象,外部初始化了Reader和Scanner,加载了一些外部Bean context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] {ConfigurableApplicationContext.class }, context); //2) 给容器Context、BeanFactory设置了一堆属性和组件,执行了initialize/listener的扩大点 //比拟重要属性有:singletonObjects 、beanDefinitionMap 、beanFactoryPostProcessors、applicationListeners prepareContext(context, environment, listeners, applicationArguments,printedBanner); //3) TODO 容器要害的扩大操作执行了,也是很多容器性能和第三方性能的扩大之处 refreshContext(context); //其余逻辑}曾经剖析的阶段如下图: prepareContext()筹备实现之后,接下来就是refreshContext()。容器要害的扩大操作执行了,也是很多容器性能和第三方性能的扩大之处,咱们来一起看下吧。 疾速摸一下refreshCotenxt的脉络refreshCotenxt()办法最终调用了容器的refresh办法,咱们还是先来看下它的脉络,之后从两头抽丝剥茧的找到重点。 先来疾速的看下它的代码脉络: public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }整体由一个try-catch形成,外部有很多个办法组成,看上去让人找不到重点所在,感觉每个办法都挺重要的。 ...

October 12, 2021 · 6 min · jiezi

关于springboot:SpringBoot成长记5Spring容器的创建

后面你相熟了SpringBoot的扩大点SpringApplicationRunListeners的设计,配置文件ConfigurableEnvironment的形象封装。其实这些都还不是它最外围的,最最外围的时Spring的容器的创立和筹备,主动配置的拆卸,tomcat的容器的启动。 这一节咱们就来开始钻研Spring的容器相干的逻辑,看看它有什么形象的设计和扩大点,又次要做了哪一些事件呢? public ConfigurableApplicationContext run(String... args) { //扩大点 SpringApplicationRunListeners listeners.starting(); //配置文件的解决和形象封装 ConfigurableEnvironment //容器相干解决 context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] {ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments,printedBanner); refreshContext(context); //其余逻辑}容器相干的逻辑次要由三个办法组成:createApplicationContext()、prepareContext()、refreshContext()。 这三个办法,每一个都做了很多事件,咱们一个个来剖析下。 Spring容器的创立时的外围组件createApplicationContext容器创立的逻辑,其实非常简单,就是通过反射创立了一个容器的实现类,默认创立AnnotationConfigServletWebServerApplicationContext。 代码如下: public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch (this.webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break; default: contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }你可能第一次看这个办法,只是这样晓得了反射创立了一个容器的实现类可能就完结了。 ...

October 12, 2021 · 1 min · jiezi

关于springboot:SpringBoot成长记4Run方法中配置文件的处理

上一节,咱们次要理解了SpringBoot的一个扩大点设计SpringApplicationRunListeners。并没有找到咱们想要找到的Spring容器创立和web容器启动、主动拆卸配置的这些外围性能。 之前咱们说过,xxxxEnvironment示意了配置文件的封装,这一节就让咱们来看下,SpringBoot启动过程中,如何通过解决配置文件,设置到Environment对象中的。 咱们接着往下持续剖析run办法,会看到如下代码: public ConfigurableApplicationContext run(String... args) { //1、扩大点 SpringApplicationRunListeners listeners.starting(); //2、配置文件的解决(待剖析) ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); //其余逻辑}咱们还是抓大放小,外围关注这一段逻辑,显著是重复呈现了environment这个词汇相干的代码,必定就是这里解决的配置文件。 这段代码次要的逻辑能够概括如下: 1)DefaultApplicationArguments命令行参数的解析和封装,也不是咱们关注的重点 2)prepareEnvironment这个办法应该就是真正配置文件的解决逻辑 3)前面这两个办法,configureIgnoreBeanInfo、printBanner,明细就是基于配置设置一个参数、打印一下Banner日志输入而已,一看就不是重点。 即如下图所示: 通过下面的初步剖析,咱们能够看到,外围关注的应该是prepareEnvironment这个办法的逻辑,那接下来就让咱们看看它是如何解决的配置文件吧。 SpringBoot的配置文件解析如何设计的?在剖析SpringBoot prepareEnvironment办法是如何解决配置文件之前,你能够思考下,如果让你编写配置文件的解析,你会怎么思考呢?你可能会思考: 从哪里找到配置文件,之后依据文件格式解析下配置文件,那每个配置文件我都能够形象为一个对象,对象中能够有配置文件的名称,地位,具体配置值等等。最初配置文件可能是多个 ,须要一个汇合来寄存这些对象。形成一个列表,示意多个配置文件解析后的后果。 这个思路其实你略微思考下,就能够得出。 那么SpringBoot其实也没有什么浅近的,它大体也是这个思路,你有这个思路,再去了解代码,其实就会轻松很多。这个思维是我想要教给你们的,对了解代码会十分有用。 让咱们来一起看下代码: private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); ConfigurationPropertySources.attach(environment); listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }这个办法的脉络,大体就是: ...

October 12, 2021 · 2 min · jiezi

关于springboot:SpringBoot成长记3扩展点之SpringApplicationRunListeners

上一节咱们相熟了SpringApplication的创立和run办法的脉络。这一节就来先剖析下脉络的中第一个比拟有意思的扩大点—SpringApplicationRunListeners。 如下: SpringApplicationRunListeners在run办法中地位在之前的run办法中,很多容器(context)相干的办法,配置(Environment)相干的办法,SpringApplicationRunListeners的代码比拟扩散。 之前咱们说过要抓大放小,咱们剖析SpringApplicationRunListeners,你能够概括run办法代码如下: public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); //一些逻辑 return context;}如下图: 再进行抓大放小,后面两步不重要,变成如下: public ConfigurableApplicationContext run(String... args) { //一些逻辑 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); //一些逻辑 listeners.started(context); //一些逻辑 listeners.running(context); //一些逻辑 return context;}晓得了SpringApplicationRunListeners在run办法的地位逻辑后,根本能够看出它是通过一个办法获取到所有的listeners,之后执行listeners的不同办法。 如下图所示: 那它扩大是如何设计的,有什么亮点呢?让咱们来认真看看吧。 SpringApplicationRunListeners的扩大是如何设计的?首先SpringApplicationRunListeners扩大是一个列表,那listeners是怎么获取的呢?代码如下: private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); } SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) { this.log = log; this.listeners = new ArrayList<>(listeners); }你发现还是依据之前的工具办法,getSpringFactoriesInstances获取的。这个工具办法,能够通过classLoader获取classPath指定地位某个接口所有实现类的实例对象列表。 ...

October 11, 2021 · 2 min · jiezi

关于springboot:Kafka成长记6Producer如何将消息放入到内存缓冲区上

之前咱们剖析了Producer的配置解析、组件剖析、拉取元数据、音讯的初步序列化形式、音讯的路由策略。如下图: 这一节咱们持续剖析发送音讯的内存缓冲器原理—RecordAccumulator.append()。 如何将音讯放入内存缓冲器的?在doSend中的,拉取元数据、音讯的初步序列化形式、音讯的路由策略之后就是accumulator.append()。 如下代码所示:(去除了多余的日志和异样解决,截取了外围代码) private Future<RecordMetadata> doSend(ProducerRecord<K, V> record, Callback callback) { TopicPartition tp = null; try { //拉取元数据、音讯的初步序列化形式、音讯的路由策略 long waitedOnMetadataMs = waitOnMetadata(record.topic(), this.maxBlockTimeMs); long remainingWaitMs = Math.max(0, this.maxBlockTimeMs - waitedOnMetadataMs); byte[] serializedKey = keySerializer.serialize(record.topic(), record.key()); byte[] serializedValue = valueSerializer.serialize(record.topic(), record.value()); int serializedSize = Records.LOG_OVERHEAD + Record.recordSize(serializedKey, serializedValue); ensureValidRecordSize(serializedSize); tp = new TopicPartition(record.topic(), partition); long timestamp = record.timestamp() == null ? time.milliseconds() : record.timestamp(); Callback interceptCallback = this.interceptors == null ? callback : new InterceptorCallback<>(callback, this.interceptors, tp); // 将路由后果、初步序列化的音讯放入到音讯内存缓冲器中 RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey, serializedValue, interceptCallback, remainingWaitMs); if (result.batchIsFull || result.newBatchCreated) { this.sender.wakeup(); } return result.future; } catch (Exception e) { throw e; } //省略其余各种异样捕捉 }accumulator.append() 它次要是将路由后果、初步序列化的音讯放入到音讯内存缓冲器中。 ...

October 11, 2021 · 3 min · jiezi

关于springboot:Kakfa成长记8Producer如何将消息放入到内存缓冲区下

上一节咱们次要剖析了RecordAccumulator通过BufferPool申请内存的源码原理,在之前的剖析中,在KafkaProducer发送音讯时,把音讯放入内存缓冲区中次要分为了三步。如下: 而且之前咱们次要剖析了前两步的代码,如下正文所示: public RecordAppendResult append(TopicPartition tp, long timestamp, byte[] key, byte[] value, Callback callback, long maxTimeToBlock) throws InterruptedException { // We keep track of the number of appending thread to make sure we do not miss batches in // abortIncompleteBatches(). appendsInProgress.incrementAndGet(); try { // check if we have an in-progress batch // 1、创立寄存音讯的内存后果 实质是一个map汇合,外部次要是双端队列 (已剖析) Deque<RecordBatch> dq = getOrCreateDeque(tp); //这段tryAppend代码发第一条音讯的时候不会执行,临时没有剖析 synchronized (dq) { if (closed) throw new IllegalStateException("Cannot send after the producer is closed."); RecordAppendResult appendResult = tryAppend(timestamp, key, value, callback, dq); if (appendResult != null) return appendResult; } //2、BufferPool申请内存块逻辑 (已剖析) // we don't have an in-progress record batch try to allocate a new batch int size = Math.max(this.batchSize, Records.LOG_OVERHEAD + Record.recordSize(key, value)); log.trace("Allocating a new {} byte message buffer for topic {} partition {}", size, tp.topic(), tp.partition()); ByteBuffer buffer = free.allocate(size, maxTimeToBlock); //3、将音讯封装到内存块中,放入之前筹备的内存构造 (待剖析) synchronized (dq) { // Need to check if producer is closed again after grabbing the dequeue lock. if (closed) throw new IllegalStateException("Cannot send after the producer is closed."); RecordAppendResult appendResult = tryAppend(timestamp, key, value, callback, dq); if (appendResult != null) { // Somebody else found us a batch, return the one we waited for! Hopefully this doesn't happen often... free.deallocate(buffer); return appendResult; } MemoryRecords records = MemoryRecords.emptyRecords(buffer, compression, this.batchSize); RecordBatch batch = new RecordBatch(tp, records, time.milliseconds()); FutureRecordMetadata future = Utils.notNull(batch.tryAppend(timestamp, key, value, callback, time.milliseconds())); dq.addLast(batch); incomplete.add(batch); return new RecordAppendResult(future, dq.size() > 1 || batch.records.isFull(), true); } } finally { appendsInProgress.decrementAndGet(); } }之前剖析的逻辑,能够概括如下图所示: ...

October 11, 2021 · 5 min · jiezi

关于springboot:Kafka成长记6Producer如何将消息放入到内存缓冲区上

之前咱们剖析了Producer的配置解析、组件剖析、拉取元数据、音讯的初步序列化形式、音讯的路由策略。如下图: 这一节咱们持续剖析发送音讯的内存缓冲器原理—RecordAccumulator.append()。 如何将音讯放入内存缓冲器的?在doSend中的,拉取元数据、音讯的初步序列化形式、音讯的路由策略之后就是accumulator.append()。 如下代码所示:(去除了多余的日志和异样解决,截取了外围代码) private Future<RecordMetadata> doSend(ProducerRecord<K, V> record, Callback callback) { TopicPartition tp = null; try { //拉取元数据、音讯的初步序列化形式、音讯的路由策略 long waitedOnMetadataMs = waitOnMetadata(record.topic(), this.maxBlockTimeMs); long remainingWaitMs = Math.max(0, this.maxBlockTimeMs - waitedOnMetadataMs); byte[] serializedKey = keySerializer.serialize(record.topic(), record.key()); byte[] serializedValue = valueSerializer.serialize(record.topic(), record.value()); int serializedSize = Records.LOG_OVERHEAD + Record.recordSize(serializedKey, serializedValue); ensureValidRecordSize(serializedSize); tp = new TopicPartition(record.topic(), partition); long timestamp = record.timestamp() == null ? time.milliseconds() : record.timestamp(); Callback interceptCallback = this.interceptors == null ? callback : new InterceptorCallback<>(callback, this.interceptors, tp); // 将路由后果、初步序列化的音讯放入到音讯内存缓冲器中 RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey, serializedValue, interceptCallback, remainingWaitMs); if (result.batchIsFull || result.newBatchCreated) { this.sender.wakeup(); } return result.future; } catch (Exception e) { throw e; } //省略其余各种异样捕捉 }accumulator.append() 它次要是将路由后果、初步序列化的音讯放入到音讯内存缓冲器中。 ...

October 11, 2021 · 3 min · jiezi

关于springboot:Kafka成长记5Producer-消息的初步序列化和分区路由源码原理

Kafka成长记的前4节咱们通过KafkaProducerHelloWorld剖析了Producer配置解析、组件组成、元数据拉取原理。 但KafkaProducerHelloWorld发送音讯的代码并没有剖析完,咱们剖析了如到了如下图所示的地位: 接下来,咱们持续往下剖析,这一节咱们次要剖析下发送音讯的初步序列化和分区路由源码原理。 自定义音讯的初步序列化的形式在producer.send()执行doSend()的时候,waitOnMetadata拉取元数据胜利之后脉络是什么呢? private Future<RecordMetadata> doSend(ProducerRecord<K, V> record, Callback callback) { TopicPartition tp = null; try { // first make sure the metadata for the topic is available long waitedOnMetadataMs = waitOnMetadata(record.topic(), this.maxBlockTimeMs); long remainingWaitMs = Math.max(0, this.maxBlockTimeMs - waitedOnMetadataMs); byte[] serializedKey; try { serializedKey = keySerializer.serialize(record.topic(), record.key()); } catch (ClassCastException cce) { throw new SerializationException("Can't convert key of class " + record.key().getClass().getName() + " to class " + producerConfig.getClass(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG).getName() + " specified in key.serializer"); } byte[] serializedValue; try { serializedValue = valueSerializer.serialize(record.topic(), record.value()); } catch (ClassCastException cce) { throw new SerializationException("Can't convert value of class " + record.value().getClass().getName() + " to class " + producerConfig.getClass(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG).getName() + " specified in value.serializer"); } int partition = partition(record, serializedKey, serializedValue, metadata.fetch()); int serializedSize = Records.LOG_OVERHEAD + Record.recordSize(serializedKey, serializedValue); ensureValidRecordSize(serializedSize); tp = new TopicPartition(record.topic(), partition); long timestamp = record.timestamp() == null ? time.milliseconds() : record.timestamp(); log.trace("Sending record {} with callback {} to topic {} partition {}", record, callback, record.topic(), partition); // producer callback will make sure to call both 'callback' and interceptor callback Callback interceptCallback = this.interceptors == null ? callback : new InterceptorCallback<>(callback, this.interceptors, tp); RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey, serializedValue, interceptCallback, remainingWaitMs); if (result.batchIsFull || result.newBatchCreated) { log.trace("Waking up the sender since topic {} partition {} is either full or getting a new batch", record.topic(), partition); this.sender.wakeup(); } return result.future; // handling exceptions and record the errors; // for API exceptions return them in the future, // for other exceptions throw directly } catch (ApiException e) { log.debug("Exception occurred during message send:", e); if (callback != null) callback.onCompletion(null, e); this.errors.record(); if (this.interceptors != null) this.interceptors.onSendError(record, tp, e); return new FutureFailure(e); } catch (Exception e) { throw e; } //省略其余各种异样捕捉}次要脉络就是: ...

October 11, 2021 · 4 min · jiezi

关于springboot:Kafka成长记4Producer-元数据拉取源码原理下

上一节结尾,咱们总结道: 初始化KafkaProducer时并没有去拉取元数据,然而创立了Selector组件,启动了Sender线程,select阻塞期待申请响应。因为还没有发送任何申请,所以初始化时并没有去真正拉取元数据。 真正拉取元数据是在第一次send办法调用时,会唤醒唤醒Selector之前阻塞的select(),进入第二次while循环,从而发送拉取元数据申请,并且通过Obejct.wait的机制期待60s,等到从Broker拉取元数据胜利后,才会继续执行真正的生产音讯的申请,否则会报拉取元数据超时异样。 如下图: 而唤醒Selector的select之后应该会进入第二次while循环,那第二次while循环如何发送申请拉取元数据申请,并且在胜利后notifyAll()进行唤醒操作的呢? 咱们明天来一起看一下。 第二次while循环-开始触发元数据拉取唤醒了阻塞的select,你还记得阻塞后的逻辑么? 唤醒后会依据nioSelector.select()返回的readKeys这个int数字,如果大于0如执行pollSelectionKeys的一些操作,因为间接被wakeUp(),理论readKeys是0,所以poll办法间接就返回了,不会执行pollSelectionKeys的解决。 而且Selector的poll办法返回后,因为pollSelectionKeys没有执行,所以之后一系列办法handleCompletedSends、handleCompletedReceives、handleDisconnections、handleConnections、handleTimedOutRequests均没有执行。(你能够本人尝试断点下,就会发现。) 下面的逻辑执行实现,也就说第一次循环会完结,从新进行第二次循环。整体过程如下图所示:(次要执行了灰色的备注标注的流程) 第二次循环maybeUpdate执行的起因 既然进入第二次循环,就会从新执行将从新执行maybeUpdate()、poll()、handle结尾的这些办法。 你还记得maybeUpdate的外围脉络么?它次要是依据3个工夫决定了metadataTimeout是否为0,来决定是否执行。代码如下: @Override public long maybeUpdate(long now) { // should we update our metadata? long timeToNextMetadataUpdate = metadata.timeToNextUpdate(now); long timeToNextReconnectAttempt = Math.max(this.lastNoNodeAvailableMs + metadata.refreshBackoff() - now, 0); long waitForMetadataFetch = this.metadataFetchInProgress ? Integer.MAX_VALUE : 0; // if there is no node available to connect, back off refreshing metadata long metadataTimeout = Math.max(Math.max(timeToNextMetadataUpdate, timeToNextReconnectAttempt), waitForMetadataFetch); if (metadataTimeout == 0) { // Beware that the behavior of this method and the computation of timeouts for poll() are // highly dependent on the behavior of leastLoadedNode. Node node = leastLoadedNode(now); maybeUpdate(now, node); } return metadataTimeout; } public synchronized long timeToNextUpdate(long nowMs) { long timeToExpire = needUpdate ? 0 : Math.max(this.lastSuccessfulRefreshMs + this.metadataExpireMs - nowMs, 0); long timeToAllowUpdate = this.lastRefreshMs + this.refreshBackoffMs - nowMs; return Math.max(timeToExpire, timeToAllowUpdate); }第一次循环的时候metadataTimeout失去的是非0,而第二次循环这个值其实曾经变成0了。 ...

October 11, 2021 · 10 min · jiezi

关于springboot:Kafka成长记3Producer-元数据拉取源码原理上

上一节咱们剖析了Producer的外围组件,咱们失去了一张要害的组件图。你还记得么? 简略概括下下面的图就是: 创立了Metadata组件,外部通过Cluster保护元数据 初始化了发送音讯的内存缓冲器RecordAccumulator 创立了NetworkClient,外部最重要的是创立了NIO的Selector组件 启动了一个Sender线程,Sender援用了下面的所有组件,开始执行run办法。 图的最下方能够看到,上一节截止到了run办法的执行,这一节咱们首先会看看run办法外围脉络做了什么。接着剖析下Producer第一个外围流程:元数据拉取的源码原理。 让咱们开始吧! Sender的run办法在做什么?这一节咱们就持续剖析下,sender的run办法开始执行会做什么。 public void run() { log.debug("Starting Kafka producer I/O thread."); // main loop, runs until close is called while (running) { try { run(time.milliseconds()); } catch (Exception e) { log.error("Uncaught error in kafka producer I/O thread: ", e); } } log.debug("Beginning shutdown of Kafka producer I/O thread, sending remaining records."); // okay we stopped accepting requests but there may still be // requests in the accumulator or waiting for acknowledgment, // wait until these are completed. while (!forceClose && (this.accumulator.hasUnsent() || this.client.inFlightRequestCount() > 0)) { try { run(time.milliseconds()); } catch (Exception e) { log.error("Uncaught error in kafka producer I/O thread: ", e); } } if (forceClose) { // We need to fail all the incomplete batches and wake up the threads waiting on // the futures. this.accumulator.abortIncompleteBatches(); } try { this.client.close(); } catch (Exception e) { log.error("Failed to close network client", e); } log.debug("Shutdown of Kafka producer I/O thread has completed."); }这个run办法的外围脉络很简略。次要就是2个while循环+线程的close,而2个while循环,他们都调用了run(long time)的这个办法。 ...

October 11, 2021 · 10 min · jiezi

关于springboot:Kafka成长记2Producer核心组件分析

上一节咱们次要从HelloWorld开始,剖析了Kafka Producer的创立,重点剖析了如何解析生产者配置的源码原理。 public KafkaProducer(Properties properties) { this(new ProducerConfig(properties), null, null); } Kafka Producer的创立除了配置解析,还有要害的一步就是调用了一个重载的构造函数。这一节咱们就来看下它次要做了什么。 KafkaProducer初始化的哪些组件?既然时一个要害组件创立,剖析的构造函数,咱们首要做的就是剖析它的代码脉络,看看外围的组件有哪些,画一个组件图先。 让咱们来看下构造函数的代码: private KafkaProducer(ProducerConfig config, Serializer<K> keySerializer, Serializer<V> valueSerializer) { try { log.trace("Starting the Kafka producer"); Map<String, Object> userProvidedConfigs = config.originals(); this.producerConfig = config; this.time = new SystemTime(); clientId = config.getString(ProducerConfig.CLIENT_ID_CONFIG); if (clientId.length() <= 0) clientId = "producer-" + PRODUCER_CLIENT_ID_SEQUENCE.getAndIncrement(); Map<String, String> metricTags = new LinkedHashMap<String, String>(); metricTags.put("client-id", clientId); MetricConfig metricConfig = new MetricConfig().samples(config.getInt(ProducerConfig.METRICS_NUM_SAMPLES_CONFIG)) .timeWindow(config.getLong(ProducerConfig.METRICS_SAMPLE_WINDOW_MS_CONFIG), TimeUnit.MILLISECONDS) .tags(metricTags); List<MetricsReporter> reporters = config.getConfiguredInstances(ProducerConfig.METRIC_REPORTER_CLASSES_CONFIG, MetricsReporter.class); reporters.add(new JmxReporter(JMX_PREFIX)); this.metrics = new Metrics(metricConfig, reporters, time); this.partitioner = config.getConfiguredInstance(ProducerConfig.PARTITIONER_CLASS_CONFIG, Partitioner.class); long retryBackoffMs = config.getLong(ProducerConfig.RETRY_BACKOFF_MS_CONFIG); this.metadata = new Metadata(retryBackoffMs, config.getLong(ProducerConfig.METADATA_MAX_AGE_CONFIG)); this.maxRequestSize = config.getInt(ProducerConfig.MAX_REQUEST_SIZE_CONFIG); this.totalMemorySize = config.getLong(ProducerConfig.BUFFER_MEMORY_CONFIG); this.compressionType = CompressionType.forName(config.getString(ProducerConfig.COMPRESSION_TYPE_CONFIG)); /* check for user defined settings. * If the BLOCK_ON_BUFFER_FULL is set to true,we do not honor METADATA_FETCH_TIMEOUT_CONFIG. * This should be removed with release 0.9 when the deprecated configs are removed. */ if (userProvidedConfigs.containsKey(ProducerConfig.BLOCK_ON_BUFFER_FULL_CONFIG)) { log.warn(ProducerConfig.BLOCK_ON_BUFFER_FULL_CONFIG + " config is deprecated and will be removed soon. " + "Please use " + ProducerConfig.MAX_BLOCK_MS_CONFIG); boolean blockOnBufferFull = config.getBoolean(ProducerConfig.BLOCK_ON_BUFFER_FULL_CONFIG); if (blockOnBufferFull) { this.maxBlockTimeMs = Long.MAX_VALUE; } else if (userProvidedConfigs.containsKey(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG)) { log.warn(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG + " config is deprecated and will be removed soon. " + "Please use " + ProducerConfig.MAX_BLOCK_MS_CONFIG); this.maxBlockTimeMs = config.getLong(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG); } else { this.maxBlockTimeMs = config.getLong(ProducerConfig.MAX_BLOCK_MS_CONFIG); } } else if (userProvidedConfigs.containsKey(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG)) { log.warn(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG + " config is deprecated and will be removed soon. " + "Please use " + ProducerConfig.MAX_BLOCK_MS_CONFIG); this.maxBlockTimeMs = config.getLong(ProducerConfig.METADATA_FETCH_TIMEOUT_CONFIG); } else { this.maxBlockTimeMs = config.getLong(ProducerConfig.MAX_BLOCK_MS_CONFIG); } /* check for user defined settings. * If the TIME_OUT config is set use that for request timeout. * This should be removed with release 0.9 */ if (userProvidedConfigs.containsKey(ProducerConfig.TIMEOUT_CONFIG)) { log.warn(ProducerConfig.TIMEOUT_CONFIG + " config is deprecated and will be removed soon. Please use " + ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG); this.requestTimeoutMs = config.getInt(ProducerConfig.TIMEOUT_CONFIG); } else { this.requestTimeoutMs = config.getInt(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG); } this.accumulator = new RecordAccumulator(config.getInt(ProducerConfig.BATCH_SIZE_CONFIG), this.totalMemorySize, this.compressionType, config.getLong(ProducerConfig.LINGER_MS_CONFIG), retryBackoffMs, metrics, time); List<InetSocketAddress> addresses = ClientUtils.parseAndValidateAddresses(config.getList(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG)); this.metadata.update(Cluster.bootstrap(addresses), time.milliseconds()); ChannelBuilder channelBuilder = ClientUtils.createChannelBuilder(config.values()); NetworkClient client = new NetworkClient( new Selector(config.getLong(ProducerConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG), this.metrics, time, "producer", channelBuilder), this.metadata, clientId, config.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION), config.getLong(ProducerConfig.RECONNECT_BACKOFF_MS_CONFIG), config.getInt(ProducerConfig.SEND_BUFFER_CONFIG), config.getInt(ProducerConfig.RECEIVE_BUFFER_CONFIG), this.requestTimeoutMs, time); this.sender = new Sender(client, this.metadata, this.accumulator, config.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION) == 1, config.getInt(ProducerConfig.MAX_REQUEST_SIZE_CONFIG), (short) parseAcks(config.getString(ProducerConfig.ACKS_CONFIG)), config.getInt(ProducerConfig.RETRIES_CONFIG), this.metrics, new SystemTime(), clientId, this.requestTimeoutMs); String ioThreadName = "kafka-producer-network-thread" + (clientId.length() > 0 ? " | " + clientId : ""); this.ioThread = new KafkaThread(ioThreadName, this.sender, true); this.ioThread.start(); this.errors = this.metrics.sensor("errors"); if (keySerializer == null) { this.keySerializer = config.getConfiguredInstance(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, Serializer.class); this.keySerializer.configure(config.originals(), true); } else { config.ignore(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG); this.keySerializer = keySerializer; } if (valueSerializer == null) { this.valueSerializer = config.getConfiguredInstance(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, Serializer.class); this.valueSerializer.configure(config.originals(), false); } else { config.ignore(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG); this.valueSerializer = valueSerializer; } // load interceptors and make sure they get clientId userProvidedConfigs.put(ProducerConfig.CLIENT_ID_CONFIG, clientId); List<ProducerInterceptor<K, V>> interceptorList = (List) (new ProducerConfig(userProvidedConfigs)).getConfiguredInstances(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, ProducerInterceptor.class); this.interceptors = interceptorList.isEmpty() ? null : new ProducerInterceptors<>(interceptorList); config.logUnused(); AppInfoParser.registerAppInfo(JMX_PREFIX, clientId); log.debug("Kafka producer started"); } catch (Throwable t) { // call close methods if internal objects are already constructed // this is to prevent resource leak. see KAFKA-2121 close(0, TimeUnit.MILLISECONDS, true); // now propagate the exception throw new KafkaException("Failed to construct kafka producer", t); } }这个构造函数的代码还是比拟都多的,不过没关系,先扫一下它的脉络: ...

October 11, 2021 · 10 min · jiezi

关于springboot:Kafka成长记9Kafka内存缓冲区中的消息最终如何发送出去的

之前三节咱们次要剖析了KafkaProducer是如何将音讯放入到内存缓冲区的。 下面的逻辑只是Accumulator.append()的一段外围逻辑而已,还记得之前咱们剖析过的KafkaProducerHelloWorld的整体逻辑么? 之前剖析的代码逻辑如下图所示: 从最开始配置解析,音讯对象Record的创立,到元数据拉取、key和value的最后序列化、Product分区路由的原理、音讯如何放入内存缓冲区的原理。 之前咱们曾经剖析到了图中红线的局部的结尾了—唤醒Sender线程发送音讯。 这一节咱们就持续剖析,音讯放入了内存缓冲中之后,触发唤醒Sender线程,之后Sender线程如何将打包好Batch发送进来的。 什么条件会唤醒Sender线程从下面的流程图能够看到,在producer.send()执行doSend()的时候,accumulator.append()将音讯内存缓冲器之后,会唤醒Sender线程。 那咱们来看下RecordBatch放入缓冲器后,什么条件会唤醒Sender线程呢? private Future<RecordMetadata> doSend(ProducerRecord<K, V> record, Callback callback) { TopicPartition tp = null; try { // 1.waitOnMetadata 期待元数据拉取 // 2.keySerializer.serialize和valueSerializer.serialize,很显著就是将Record序列化成byte字节数组 // 3.通过partition进行路由分区,依照肯定路由策略抉择Topic下的某个分区 //省略代码... // 4.accumulator.append将音讯放入缓冲器中 RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey, serializedValue, interceptCallback, remainingWaitMs); if (result.batchIsFull || result.newBatchCreated) { log.trace("Waking up the sender since topic {} partition {} is either full or getting a new batch", record.topic(), partition); //5.唤醒Sender线程的selector.select()的阻塞,开始解决内存缓冲器中的数据。 this.sender.wakeup(); } return result.future; } catch (ApiException e) { log.debug("Exception occurred during message send:", e); if (callback != null) callback.onCompletion(null, e); this.errors.record(); if (this.interceptors != null) this.interceptors.onSendError(record, tp, e); return new FutureFailure(e); } catch (Exception e) { throw e; } //省略其余各种异样捕捉}从下面代码,能够很分明的看到,唤醒sender线程的逻辑很简略,就是以后Batch曾经写满,或者是新的batch创立了。 ...

October 11, 2021 · 8 min · jiezi

关于springboot:淘到宝了阿里SpringBootVue全栈项目开发文档被我弄到手了

前言明天给大家分享一份《SpringBoot+Vue全栈开发实战》电子书,通过本书你能够把握Spring Boot全栈开发流程,独立实现大型SPA利用 讲述Spring、Spring MVC. MyBatis. Spring Boot和Vue全栈开发技术所有的知识点都配有实例,让读者了解实践的同时也把握开发技能通过微人事我的项目实战,进步你的全栈开发程度内容简介Spring Boot致力于简化开发配置并为企业级开发提供一系列非业务性功能, 而Vue则采纳数据驱动视图的形式将程序员从繁琐的DOM操作中解救出来。利用Spring Boot+Vue,咱们能够疾速开发出大型SPA利用。 本书分为16章,重点解说Spring Boot 2 + Vue 2全栈开发所波及的各种技术点。所有技术点都配有操作实例,循序渐进,直到疏导读者开发出一个残缺的微人事SPA利用。 其中,第1-15章从视图层技术、长久化技术、NoSQL、 RESTful、 缓存、平安、WebSocket、音讯服务以及企业开发等各个技术点对Spring Boot进行介绍;第16章通过-一个Spring Boot+Vue搭建的前后端拆散我的项目率领读者将后面15章所学的技术点利用到我的项目中,使读者深刻领会前后端拆散带来的益处,并学会搭建前后端拆散的我的项目架构。 支付形式:点赞+关注,点击这里取得材料的收费支付形式 内容目录Spring Boot入门 Spring Boot根底配置 Spring Boot整合视图层技术 Spring Boot整合Web开发 Spring Boot整合长久层技术 Spring Boot整合NoSQL 构建RESTful服务 开发者工具与单元测试 Spring Boot缓存 Spring Boot平安治理 Spring Boot整合WebSocket 音讯服务 企业开发 利用监控 我的项目构建与部署 微人事我的项目实战

October 11, 2021 · 1 min · jiezi

关于springboot:SpringBoot成长记2从HelloWorld开始分析SpringBoot

上一节咱们提到过,意识一个新技术的时候,通常是从一个入门的HelloWorld开始,之后浏览它的一些入门文档和书籍、视频,从而把握它的根本应用。 这一节我就来带大家从HelloWorld开始,先摸清楚SpringBoot的外围脉络,之后再来逐渐剖析透彻SpringBoot,从而精通它。 从搭建HelloWorld入口开始剖析SpringBoot首先咱们从官网的文档中搭建出一个2.2.2 版本的SpringBoot,减少了两个starter,mybatis-plus-boot-starter、spring-boot-starter-web,应用Maven进行我的项目和依赖治理,配置一个本地的mysql。置信这个对你们来说,都比较简单,我就不一一进行赘述了。 通过下面的根本搭建,你就会有相似一个上面的一个SpringBoot HelloWorld级别 的入口。 package org.mfm.learn.springboot;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@MapperScan("org.mfm.learn.springboot.mapper")@SpringBootApplicationpublic class LearnSpringBootApplication { public static void main(String[] args) { SpringApplication.run(LearnSpringBootApplication.class, args); }}通过上一节你晓得SpringBoot定义了一个SpringApplication的web利用启动流程,入口通过一个java -jar的命令,执行main函数启动一个JVM过程,运行外部的tomcat监听一个默认8080的端口,提供web服务。 整个过程中第一个最要害的就是SpringBoot定义的SpringApplication,咱们一起先来看下它是怎么创立new的。 SpringApplication的创立时外围组件图SpringApplication的创立时的代码剖析在下面的示例代码中,main办法执行了 SpringApplication的run办法,如下: public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args);}public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);}run办法外围入参就是main函数所在类+main函数args的参数,之后就间接创立了一个SpringApplication对象。让咱们一起来看看这个SpringBoot定义的概念怎么创立的,创立时的外围组件又有哪些呢? public SpringApplication(Class<?>... primarySources) { this(null, primarySources);}public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass();}SpringApplication的创立时外围脉络SpringApplication的创立外围脉络比较简单: ...

October 9, 2021 · 2 min · jiezi

关于springboot:Kafka成长记1从HelloWorld开始研究Kafka-Producer源码原理

成长记不会介绍太多一些kafka的基础知识,如果有需要的话,之后会有专门的《小白起步营》。成长记的默认大家对kafka的一些概念是熟知的、默认也是会根本Kafka的部署的。当然为了关照一些小白,第一次波及的常识我会简略介绍和解释的,相熟的人就当回顾吧。简略的事件反复做有时也是坏事。 Kafka成长记会间接从三个方面开始摸索,Producer、Broker、Comsumer。过程中,依据场景会应用之前ZK和JDK成长记介绍源码分析方法。话不多说,让咱们间接开始第一节的内容吧! 咱们之前钻研ZK次要是应用的场景法,找到一些外围入口开始剖析的。钻研Kafka的源码时候,咱们也能够参考之前的办法。不过这次咱们不间接从Broker服务端节点动手,先从Producer开始动手钻研。会用到一些新的剖析源码的思维和办法。 要想剖析Kafka Producer的源码原理,首先必定得有一个入口或者下手的中央。很多人应用Kafka必定都是从一个Demo开始的。本人部署一台Kafka,之后发送下音讯,之后在本人生产一条音讯。 KafkaProducerHelloWorld所以咱们就从最简略的一个Kafka Producer的Demo开始,从一个KafkaProducerHelloWorld例子开始Kafka源码原理的摸索。 HelloWorld的代码如下: import org.apache.kafka.clients.producer.Callback;import org.apache.kafka.clients.producer.KafkaProducer;import org.apache.kafka.clients.producer.ProducerRecord;import org.apache.kafka.clients.producer.RecordMetadata;import java.util.Properties;/** * @author fanmao */public class KafkaProducerHelloWorld { public static void main(String[] args) throws Exception { //配置Kafka的一些参数 Properties props = new Properties(); props.put("bootstrap.servers", "192.168.30.1:9092"); // 创立一个Producer实例 KafkaProducer<String, String> producer = new KafkaProducer<>(props); // 封装一条音讯 ProducerRecord<String, String> record = new ProducerRecord<>( "test-topic", "test-key", "test-value"); // 同步形式发送音讯,会阻塞在这里,直到发送实现 // producer.send(record).get(); // 异步形式发送音讯,不阻塞,设置一个监听回调函数即可 producer.send(record, new Callback() { @Override public void onCompletion(RecordMetadata metadata, Exception exception) { if(exception == null) { System.out.println("音讯发送胜利"); } else { System.out.println("音讯发送异样"); } } }); Thread.sleep(5 * 1000); // 退出producer producer.close(); } }下面的代码例子,尽管非常简单,然而也有本人的脉络。 ...

October 9, 2021 · 5 min · jiezi

关于springboot:后台梳理软删除思路

序言在之前开关柜我的项目中一开始没有波及到软删除,因为本人仅仅负责一个用户治理。在开始智慧社区我的项目之后,软删除的问题成了一个不得不解决的问题了。上面将以智慧社区我的项目进行梳理。 1.何为软删除逻辑删除(标记删除),如果逻辑删除不了解,我想标记删除最容易进行了解:应用一个字段,将被删除数据的该字段进行标记,依据该字段能够判断该数据是否曾经进行逻辑删除(仍旧存在于数据库中,然而查问等获取不到了)。举个栗子:一个公司有n集体下班,那么忽然一天这个公司开张了,能够认为该公司被删除了,那么在员工治理的时候,因为该公司被删除了,获取这些员工时后盾便会报错,EntityNotFound,或者报无奈删除,有限制性束缚,如下图: 2.实现过程:1.减少标记字段:两种形式: (1)减少 is_deleted 的布尔型字段(2)增加 deleted_at 的工夫戳字段第(1)种比较简单,初始化的时候间接设置默认值为false,删除的话便将该字段变为true第(2)种为设置删除工夫,如果删除,则该字段值为删除工夫戳;如果未删除,则该字段为null 实现软删除过程中遇到的问题1.继承后,注解不肯定会被继承父类为Distract:如下图子类为Town:如下图 谬误剖析:天真的认为继承之后,那么Distract的@SQLDelete和@Where注解等都会被继承通过测试发现@SQLDelete并没有被继承,只有@Where被继承了 2.理解了NotFound注解作用:Action to do when an element is not found on a association.(在关联上找不到元素时要执行的操作。) 1.默认:org.hibernate.annotations.NotFoundAction.EXCEPTION(The action to perform when an associated entity is not found. By default an exception is thrown:关联实体未找到,抛出异样) 2.NotFoundAction.IGNORE: ignore the element when not found in DB(在数据库中找不到元素时疏忽该元素) 总结:1.本次软删除犯的次要谬误就是想当然了,误以为继承也能够继承注解,实则须要本人尝试去验证本人的想法。2.此次软删除并没有用到NotFound注解,因为应用的是有外键束缚的数据库,所以在删除的时候便会报谬误:不可删除,那么也不存在说关联属性在数据库不存在的状况。

October 9, 2021 · 1 min · jiezi

关于springboot:SpringBoot集成itextpdf动态生成pdf并展示

背景接上文SpringBoot集成markdown实现文档治理,对于表格的反对markdown不是特地敌对,同时外部文档治理须要减少表格局api接口文档的性能,所以决定采纳联合数据库存储与动静生成pdf借助目录构造展现的形式 表结构设计目录表 DROP TABLE IF EXISTS `knowledge_interfacecatalog`;CREATE TABLE `knowledge_interfacecatalog` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `UnitGuid` varchar(50) DEFAULT NULL, `AddDate` datetime DEFAULT NULL, `CataName` varchar(100) DEFAULT NULL, `ParentCataGuid` varchar(50) DEFAULT NULL, `SortNum` int(11) DEFAULT NULL, `DocGuid` varchar(50) DEFAULT NULL, KEY `ID` (`ID`)) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;接口内容表 DROP TABLE IF EXISTS `knowledge_interfaceinfo`;CREATE TABLE `knowledge_interfaceinfo` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `UnitGuid` varchar(50) DEFAULT NULL, `AddDate` datetime DEFAULT NULL, `InterfaceName` varchar(100) DEFAULT NULL, `Description` varchar(500) DEFAULT NULL, `Remark` varchar(500) DEFAULT NULL, `ParamJson` varchar(2000) DEFAULT NULL, `ResponseJson` varchar(2000) DEFAULT NULL, `InterfaceAddress` varchar(500) DEFAULT NULL, `SortNum` int(11) DEFAULT NULL, `CataGuid` varchar(50) DEFAULT NULL, `DocGuid` varchar(50) DEFAULT NULL, KEY `ID` (`ID`)) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4;录入界面 ...

October 9, 2021 · 2 min · jiezi

关于springboot:SpringBoot集成markdown实现文档管理

背景最近在做一个部门外部简略的知识库零碎,便于新人入职理解与一些常见问题的解答,知识库的模式是以文档为主,为了疾速实现文档性能,决定采纳markdown模式录入,生成本地文件后以html形式展示,档次清晰便于查看 表结构设计文档信息表 DROP TABLE IF EXISTS `knowledge_documentinfo`;CREATE TABLE `knowledge_documentinfo` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `UnitGuid` varchar(50) DEFAULT NULL, `AddDate` datetime DEFAULT NULL, `DocName` varchar(50) DEFAULT NULL, `DocType` int(11) DEFAULT NULL, `DocRemark` varchar(500) DEFAULT NULL, `DocTag` varchar(100) DEFAULT NULL, `DocClass` int(11) DEFAULT NULL, `GroupGuid` varchar(50) DEFAULT NULL, `SortNum` int(11) DEFAULT NULL, KEY `ID` (`ID`)) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4;文档内容表 DROP TABLE IF EXISTS `knowledge_documentcontentinfo`;CREATE TABLE `knowledge_documentcontentinfo` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `UnitGuid` varchar(50) DEFAULT NULL, `DocGuid` varchar(50) DEFAULT NULL, `DocClass` int(11) DEFAULT NULL, `DocContent` longtext, KEY `ID` (`ID`)) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4;前端集成前端为了markdown编辑操作便捷,集成了toastui-edit编辑器,具备所见即所得模式,和typora一样的成果。因为咱们前端是本人开发,基于vue+html本地化形式开发,就简略封装了控件应用,同时须要援用zh-cn.js汉化,指定initialEditType模式为wysiwyg ...

October 9, 2021 · 2 min · jiezi

关于springboot:SpringBoot实现用户统一管理与单点登陆

前言最近在开发产品的过程中,须要将业务性能拆分成独立子系统,既能够独自应用也能够集成部署,这里就须要对框架进行扩大,反对用户对立治理与单点登陆。咱们的根底框架应用redis实现token认证,所以只须要所有子系统共享redis数据就能够实现单点登陆,次要的难点是sso对立用户治理,咱们这里抉择的是通过监听sso平台组织架构的变动散发同步到各个子系统,这样子系统只依赖sso的登陆/登出/校验凭据三个接口,组织架构查问还是应用具体业务零碎数据。 方案设计后端设计后端设计如下图所示,次要蕴含利用/服务注册,组织架构同步,日志治理三大块 前端设计前端设计如下图所示,次要包含cookie跨域共享,鉴权流程 计划实现后端实现注:表构造展现省略主键,增加工夫等通用字段 表结构设计SSO_Application 利用信息 序号列名数据类型长度小数位列阐明1AppNamevarchar100 利用名称2AppTagvarchar100 利用标识3AppStatusint11 利用状态4SortNumint11 排序5AppAPIUrlvarchar200 利用接口地址6AppTypeint11 利用类型7Remarkvarchar500 备注8AppWebsiteMainPageUrlvarchar500 利用网站主页地址9AppManageMainPageUrlvarchar500 利用治理主页地址SSO_ApplicationPermission 利用权限信息 序号列名数据类型长度小数位列阐明1AppGuidvarchar50 利用Guid2PermissionTypeint11 权限类型3PermissionGuidvarchar50 权限关联业务GuidSSO_ApplicationService 应用服务 序号列名数据类型长度小数位列阐明1ServiceNamevarchar100 服务名称2ServiceTypeint11 服务类别3ServiceRoutevarchar200 服务路由4ServiceStatusint11 服务状态5AppGuidvarchar50 利用Guid6Remarkvarchar500 备注SSO_SyncDataSQL 同步数据SQL语句 序号列名数据类型长度小数位列阐明1TableNamevarchar300 数据表2SyncStatusint11 同步状态3CommandTypevarchar50 操作类型4CommandTextvarchar1000 操作语句SSO_SyncDataTask 同步数据工作 序号列名数据类型长度小数位列阐明1TaskNamevarchar500 工作名称2TaskStatusint11 工作状态3FinishDatevarchar100 实现工夫4TaskContentvarchar500 工作内容5TaskFinishPeroiddecimal182工作实现时常6TaskSQLJsonntext0 工作SQL语句Json7StartDatevarchar11 开始工夫SSO_SyncDataTaskItem 同步数据工作子表 序号列名数据类型长度小数位列阐明1TaskIDvarchar50 工作ID2TaskItemStatusint11 工作状态3FinishDatevarchar100 实现工夫4AppGuidvarchar50 利用Guid5TaskItemFinishPeroiddecimal182工作实现时长6StartDatevarchar11 开始工夫7AppNamevarchar300 利用名称8ExecuteInfontext0 执行信息SSO_SyncDataLog 同步数据日志 序号列名数据类型长度小数位列阐明1SyncTypeint11 同步类型2SyncContentvarchar500 同步内容3StartDatevarchar100 开始工夫4FinishDatevarchar100 完结工夫5FinishPeroiddecimal182同步时长相干配置配置次要是用于业务零碎灵便切换是否集成sso sso_apptag代表sso零碎惟一标识,每个业务零碎的appTag是不一样的,token生成前缀也是依据appTag来的,能够从后端防止一个浏览器登陆多个零碎(同一个redis库的状况下)导致用户信息笼罩的问题,当开启sso登陆后,业务零碎只认证sso管理系统对应appTag(即配置中的sso_apptag)的凭据,屏蔽自身业务零碎认证性能 #sso #sso状态 0:敞开 1:关上 sso_status: 0 #sso零碎标识 sso_apptag: haopansso #sso对立认证地址(治理) sso_loginpage_manage: http://localhost:8091/haopansso/login.html #sso对立认证地址(网站) sso_loginpage_website: http://localhost:8091/haopansso/login.html #sso对立认证接口地址 sso_api_url: http://localhost:8091/组织架构SQL语句拦挡零碎和数据库交互应用的是mybatis,所以能够通过mybaits的拦截器实现针对指定表对立拦挡 ...

October 9, 2021 · 3 min · jiezi

关于springboot:SpringBoot实现quartz定时任务可视化管理

前言在理论框架或产品开发过程中,springboot中集成quarzt形式根本是以job和trigger的bean对象形式间接硬编码实现的,例如以下代码示例。对于零碎内定义的所有定时工作类型,具体执行类,执行策略,运行状态都没有一个动静全局的治理,所有决定将quartz做成可视化配置管理,便于对立治理,也升高了应用门槛,只须要关怀job类的实现即可 @Bean public JobDetail SMSJobDetail() { return JobBuilder.newJob(SMSJob.class).withIdentity("SMSJob").storeDurably().build(); } // 把jobDetail注册到trigger下来 @Bean public Trigger myJobTrigger() { SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1).repeatForever(); return TriggerBuilder.newTrigger() .forJob(SMSJobDetail()) .withIdentity("myJobTrigger") .withSchedule(scheduleBuilder) .build(); }表构造用于存储quartz配置 DROP TABLE IF EXISTS `f_quartztask`;CREATE TABLE `f_quartztask` ( `TaskID` varchar(50) NOT NULL, `TaskName` varchar(200) DEFAULT NULL, `TaskType` int(11) DEFAULT NULL, `TaskTag` varchar(100) DEFAULT NULL, `JobClassPath` varchar(200) DEFAULT NULL, `ExecutePeroid` int(11) DEFAULT NULL, `ExecuteUnit` int(11) DEFAULT NULL, `CornExpress` varchar(200) DEFAULT NULL, `Enviroment` varchar(50) DEFAULT NULL, `TaskStatus` int(11) DEFAULT NULL, `SortNum` int(11) DEFAULT NULL, `Remark` varchar(500) DEFAULT NULL, PRIMARY KEY (`TaskID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;枚举类public class QuartzEnum { public enum TaskType implements IConvertEnumToCodeItem { Cycle(10, "循环工作"), Corn(20, "Corn表达式工作"); private int _value; private String _name; private TaskType(int value, String name) { set_value(value); set_name((name)); } public int get_value() { return _value; } public void set_value(int _value) { this._value = _value; } public String get_name() { return _name; } public void set_name(String _name) { this._name = _name; } @Override public String toString() { return _name; } @Override public String getCodeName() { return "Quartz工作类别"; } } public enum ExecuteUnit implements IConvertEnumToCodeItem { Second(10, "秒"), Minute(20, "分"), Hour(30, "时"); private int _value; private String _name; private ExecuteUnit(int value, String name) { set_value(value); set_name((name)); } public int get_value() { return _value; } public void set_value(int _value) { this._value = _value; } public String get_name() { return _name; } public void set_name(String _name) { this._name = _name; } @Override public String toString() { return _name; } @Override public String getCodeName() { return "Quartz距离单位"; } } public enum TaskStatus implements IConvertEnumToCodeItem { Open(10, "开启"), Close(20, "敞开"); private int _value; private String _name; private TaskStatus(int value, String name) { set_value(value); set_name((name)); } public int get_value() { return _value; } public void set_value(int _value) { this._value = _value; } public String get_name() { return _name; } public void set_name(String _name) { this._name = _name; } @Override public String toString() { return _name; } @Override public String getCodeName() { return "Quartz工作状态"; } } public enum TaskEnviroment implements IConvertEnumToCodeItem { All("全副", "全副"), Dev("dev", "开发环境"), Pro("pro", "正式环境"); private String _value; private String _name; private TaskEnviroment(String value, String name) { set_value(value); set_name((name)); } public String get_value() { return _value; } public void set_value(String _value) { this._value = _value; } public String get_name() { return _name; } public void set_name(String _name) { this._name = _name; } @Override public String toString() { return _name; } @Override public String getCodeName() { return "Quartz工作执行环境"; } }}QuartzFactory反对Job类注bean入对象 ...

October 9, 2021 · 4 min · jiezi

关于springboot:SpringBoot实现表单重复提交检测

前言在理论开发过程中,web利用常常会呈现网络提早,接口解决工夫略长,用户习惯等起因造成的客户间断屡次点击提交按钮调用接口,导致数据库会呈现反复数据或这接口业务逻辑bug等问题 计划利用redis锁实同一个用户同一个申请2秒内反复提交返回谬误路由 SubmitLock标记须要拦挡的办法 @Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface SubmitLock { int expire() default 2;}RedisLockUtilredis锁校验及写入 @Componentpublic class RedisLockUtil { @Autowired private RedisUtil redisUtil; private int lockDBIndex = 1; public boolean lock(String key,String clientID,int lockExpire){ if(redisUtil.isValid(key,lockDBIndex)){ return false; }else{ redisUtil.redisTemplateSet(key,clientID,lockDBIndex); redisUtil.setExpire(key,lockExpire, TimeUnit.SECONDS,lockDBIndex); return true; } }}RepeatSubmitAspect对立拦挡切面 @Aspect@Component@Order(value = 100)public class RepeatSubmitAspect { private static Logger logger = LoggerFactory.getLogger(RepeatSubmitAspect.class); @Autowired private RedisLockUtil redisLockUtil; /** * 切面点 指定注解 */ @Pointcut("@annotation(com.haopan.frame.common.annotation.SubmitLock) " + "|| @within(com.haopan.frame.common.annotation.SubmitLock)") public void repeatSubmitAspect() { } /** * 拦挡办法指定为 repeatSubmitAspect */ @Around("repeatSubmitAspect()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); SubmitLock submitLock = method.getAnnotation(SubmitLock.class); if (submitLock != null) { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); String token = request.getHeader("token"); if (!StringUtil.isEmpty(token)) { String path = request.getServletPath(); String key = "submitLock|" + token + "|" + path; String clientId = CommonUtil.getNewGuid(); if (redisLockUtil.lock(key, clientId, submitLock.expire())) { // 获取锁胜利 return point.proceed(); } else { System.out.println("tryLock fail, key = ["+key+"]"); return Result.errorResult().setMsg("反复申请,请稍后再试").setCode(-980); } } else { return point.proceed(); } } else { return point.proceed(); } }}

October 9, 2021 · 1 min · jiezi

关于springboot:SpringBoot实现redis切换dbindex

前言在理论springboot集成redis应用过程中,针对不同类型的业务数据,可能存在不同的dbindex中,例如token存储db0,redis全局锁存储dbindex1,须要咱们对RedisTemplate操作进行扩大,反对单次操作不同的dbindex 计划零碎加载时初始化依据redis应用库的dbindex,初始化对应个数的RedisTemplate,调用时依据dbindex获取对应的操作对象实例,本次实现是将15个db全副初始化 RedisRegist初始化redis的Factory,线程池配置及RedisTemplate,StringRedisTemplate的Bean对象 public class RedisRegist implements EnvironmentAware,ImportBeanDefinitionRegistrar { private static final Logger logger = LoggerFactory.getLogger(RedisRegist.class); private static Map<String, Object> registerBean = new ConcurrentHashMap<>(); private Environment environment; private Binder binder; @Override public void setEnvironment(Environment environment) { this.environment = environment; this.binder = Binder.get(this.environment); } @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { RedisEntity redisEntity; try { redisEntity = binder.bind("spring.redis", RedisEntity.class).get(); } catch (NoSuchElementException e) { logger.error("redis not setting."); return; } boolean onPrimary = true; //依据多个库实例化出多个连接池和Template for (int i = 0; i < 15; i++) { int database = i; //单机模式 RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); configuration.setHostName(String.valueOf(redisEntity.getHost())); configuration.setPort(Integer.parseInt(String.valueOf(redisEntity.getPort()))); configuration.setDatabase(database); String password = redisEntity.getPassword(); if (password != null && !"".equals(password)) { RedisPassword redisPassword = RedisPassword.of(password); configuration.setPassword(redisPassword); } //池配置 GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig(); RedisProperties.Pool pool = redisEntity.getLettuce().getPoolEntity(); genericObjectPoolConfig.setMaxIdle(pool.getMaxIdle()); genericObjectPoolConfig.setMaxTotal(pool.getMaxActive()); genericObjectPoolConfig.setMinIdle(pool.getMinIdle()); if (pool.getMaxWait() != null) { genericObjectPoolConfig.setMaxWaitMillis(pool.getMaxWait().toMillis()); } Supplier<LettuceConnectionFactory> lettuceConnectionFactorySupplier = () -> { LettuceConnectionFactory factory = (LettuceConnectionFactory) registerBean.get("LettuceConnectionFactory" + database); if (factory != null) { return factory; } LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder(); Duration shutdownTimeout = redisEntity.getLettuce().getShutdownTimeout(); if(shutdownTimeout == null){ shutdownTimeout = redisEntity.getTimeout(); } if (shutdownTimeout != null) { builder.shutdownTimeout(shutdownTimeout); } LettuceClientConfiguration clientConfiguration = builder.poolConfig(genericObjectPoolConfig).build(); factory = new LettuceConnectionFactory(configuration, clientConfiguration); registerBean.put("LettuceConnectionFactory" + database, factory); return factory; }; LettuceConnectionFactory lettuceConnectionFactory = lettuceConnectionFactorySupplier.get(); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(LettuceConnectionFactory.class, lettuceConnectionFactorySupplier); AbstractBeanDefinition factoryBean = builder.getRawBeanDefinition(); factoryBean.setPrimary(onPrimary); registry.registerBeanDefinition("lettuceConnectionFactory" + database, factoryBean); // StringRedisTemplate GenericBeanDefinition stringRedisTemplate = new GenericBeanDefinition(); stringRedisTemplate.setBeanClass(StringRedisTemplate.class); ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues(); constructorArgumentValues.addIndexedArgumentValue(0, lettuceConnectionFactory); stringRedisTemplate.setConstructorArgumentValues(constructorArgumentValues); stringRedisTemplate.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME); registry.registerBeanDefinition("stringRedisTemplate" + database, stringRedisTemplate); // 定义RedisTemplate对象 GenericBeanDefinition redisTemplate = new GenericBeanDefinition(); redisTemplate.setBeanClass(RedisTemplate.class); redisTemplate.getPropertyValues().add("connectionFactory", lettuceConnectionFactory); redisTemplate.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME); RedisSerializer stringSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); // key采纳String的序列化形式,value采纳json序列化形式 redisTemplate.getPropertyValues().add("keySerializer",new StringRedisSerializer()); redisTemplate.getPropertyValues().add("hashKeySerializer",stringSerializer); redisTemplate.getPropertyValues().add("valueSerializer",jackson2JsonRedisSerializer); redisTemplate.getPropertyValues().add("hashValueSerializer",stringSerializer); //注册Bean registry.registerBeanDefinition("redisTemplate" + database, redisTemplate); //logger.info("Registration redis ({}) !", database); if (onPrimary) { onPrimary = false; } } }}RedisManageRedisTemplate,StringRedisTemplate的Bean对象对立治理定义 ...

October 9, 2021 · 4 min · jiezi

关于springboot:SpringBoot静态资源访问控制和封装集成方案

背景最近在着手公司框架优化及我的项目理论利用,原先计划是springboot+html前后端拆散独自部署,后端人员兼职前端开发,后续产品线业务进行优化,面向企业应用局部由挪动网站人员负责设计开发,外部配置后盾治理还是由后端负责,随着框架不停迭代与应用的我的项目越来越多,我的项目降级框架变得非常麻烦,后端局部能够通过maven私服进行版本迭代,后盾治理页面降级则须要进行各个我的项目拷贝,所以决定对框架进行整合,将后盾治理页面与框架后端代码进行整合公布。 结构设计框架打包后盾治理相干规范资源及页面(框架public文件夹)我的项目应用框架,开发具体业务配置管理页面(我的项目static文件夹)我的项目须要个性化框架页面时,在我的项目static文件夹建设与框架同目录同名称资源文件进行笼罩,拜访时优先级高于框架目录 SpringBoot动态资源拜访自定义拜访门路自定义WebConfig实现WebMvcConfigurer,重写addResourceHandlers办法 @Configurationpublic class WebConfig implements WebMvcConfigurer { @Value("${system.projectName}") private String projectName; /** * 增加动态资源文件,内部能够间接拜访地址 * * @param registry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { //第一个办法设置拜访门路前缀,第二个办法设置资源门路 registry.addResourceHandler("/" + projectName + "/**").addResourceLocations("classpath:/static/","classpath:/public/","file:static/"); }}图标与字体文件夹拜访失败问题将动态文件拷贝到static/public/resource文件夹下拜访时,图标与字体文件会进行过滤导致损坏,须要在pom文件中进行设置 <build> <resources> <resource> <filtering>true</filtering> <directory>src/main/resources</directory> <excludes> <exclude>**/*.woff</exclude> <exclude>**/*.ttf</exclude> <exclude>**/*.ico</exclude> </excludes> </resource> <resource> <filtering>false</filtering> <directory>src/main/resources</directory> <includes> <include>**/*.woff</include> <include>**/*.ttf</include> <include>**/*.ico</include> </includes> </resource> </resources> </build>自定义欢送页面在对动态内目录设置自定义拜访门路替换原有的/**后,无奈找到目录下的index页面,须要建设拦截器手动进行判断,成果为拜访http://localhost:port/project... 会主动跳转到 http://localhost:port/project... @Componentpublic class PageRedirectInterceptor implements HandlerInterceptor { @Value("${system.projectName}") private String projectName; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestURL = request.getRequestURL().toString(); String scheme = request.getScheme(); String servaerName = request.getServerName(); int port = request.getServerPort(); String rootPageURL = scheme + ":" + "//" + servaerName + ":" + port + "/" + projectName; if (requestURL.equals(rootPageURL)) { response.sendRedirect(request.getContextPath() + "/"+projectName + "/index.html"); return false; } return true; }}自定义页面图标在对动态内目录设置自定义拜访门路替换原有的/**后,无奈找到目录下的favcion.ico图标,须要在页面援用对立js对立设置,同时须要在配置文件中敞开默认图标,替换spring的小叶子 ...

October 9, 2021 · 4 min · jiezi

关于springboot:SpringBoot自定义异常处理

全局异样解决@ControllerAdvice@RestControllerpublic class GlobalExceptionInterceptor { //Exception异样 @ExceptionHandler(value = Exception.class) @ResponseBody public Result exceptionHandler(Exception e){ e.printStackTrace(); LogUtil.writeLog("error","log",e.getMessage()+e.getStackTrace()); Result result = Result.errorResult(); if (e.getClass().equals(HttpMediaTypeNotSupportedException.class)){ result.setCode(-981); result.setMsg("申请参数格局谬误"); }else{ result.setCode(-998); result.setMsg("零碎执行产生谬误"); } return result; } //运行时异样 @ExceptionHandler(value = RuntimeException.class) @ResponseBody public Result exceptionHandler(RuntimeException e){ e.printStackTrace(); LogUtil.writeLog("error","log",e.getMessage()+e.getStackTrace()); Result result = Result.errorResult(); result.setCode(-997); result.setMsg("零碎运行产生谬误"); return result; }}全局404拦挡@Componentpublic class ResponseStatusInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if(response.getStatus()==404){ response.sendRedirect(request.getContextPath() + "/frame/error/404"); } return true; }}

October 9, 2021 · 1 min · jiezi

关于springboot:SpringBoot枚举转化代码项统一解决方案

定义构造及接口public class CodeItem { private String _itemText; private Object _itemValue; public CodeItem(){ } public CodeItem(String itemText, Object itemValue){ _itemText = itemText; _itemValue = itemValue; } public String get_itemText() { return _itemText; } public void set_itemText(String _itemText) { this._itemText = _itemText; } public Object get_itemValue() { return _itemValue; } public void set_itemValue(Object _itemValue) { this._itemValue = _itemValue; }}public interface IConvertEnumToCodeItem { String getCodeName();} 规范化枚举定义定义枚举蕴含name和value字段,实现枚举转换接口,返回代码项名称 public enum EnableOrDisable implements IConvertEnumToCodeItem { Enable(100, "启用"), Disabele(300, "禁用"); private int _value; private String _name; private EnableOrDisable(int value, String name) { set_value(value); set_name((name)); } public int get_value() { return _value; } public void set_value(int _value) { this._value = _value; } public String get_name() { return _name; } public void set_name(String _name) { this._name = _name; } @Override public String toString() { return _name; } @Override public String getCodeName() { return "启用禁用"; } }扫描转换枚举类扫描我的项目包下的所有实现IConvertEnumToCodeItem接口须要转化的枚举类,拿到所有枚举项,调用接口的getCodeName办法获取代码项名称,最初组成代码项 ...

October 9, 2021 · 2 min · jiezi