不懂SpringApplication生命周期事件那就等于不会Spring-Boot嘛

学习方法之少废话:吹牛、装逼、叫大哥。作者:A哥(YourBatman)公众号:BAT的乌托邦(ID:BAT-utopia)文末是否有彩蛋:有前言各位小伙伴大家好,我是A哥。本文属总结性文章,对总览Spring Boot生命周期很是重要,建议点在看、转发“造福”更多小伙伴。 我最近不是在写Spring Cloud深度剖析的相关专栏麽,最近有收到小伙伴发过来一些问题,通过这段时间收集到的反馈,总结了一下有一个问题非常集中:那便是对Spring Boot应用SpringApplication的生命周期、事件的理解。有句话我不是经常挂嘴边说的麽,你对Spring Framework有多了解决定了你对Spring Boot有多了解,你对Spring Boot的了解深度又会制约你去了解Spring Cloud,一环扣一环。因此此问题反馈比较集中是在清理之中的~ 为何在Spring Boot中生命周期事件机制如此重要?缘由很简单:Spring Cloud父容器是由该生命周期事件机制来驱动的,而它仅仅是一个典型代表。Spring Cloud构建在Spring Boot之上,它在此基础上构建并添加了一些“Cloud”功能。应用程序事件ApplicationEvent以及监听ApplicationListener是Spring Framework提供的扩展点,Spring Boot对此扩展点利用得非常充分和深入,并且还衍生出了非常多“子”事件类型,甚至自成体系。从ApplicationEvent衍生出来的子事件类型非常多,例如JobExecutionEvent、RSocketServerInitializedEvent、AuditApplicationEvent... 本文并不会对每个子事件分别介绍(也并无必要),而是集中火力主攻Spring Boot最为重要的一套事件机制:SpringApplication生命周期的事件体系。 正文本文将以SpringApplication的启动流程/生命周期各时期发出的Event事件为主线,结合每个生命周期内完成的大事记介绍,真正实现一文让你总览Spring Boot的全貌,这对你深入理解Spring Boot,以及整合进Spring Cloud都将非常重要。 为表诚意,本文一开始便把SpringApplication生命周期事件流程图附上,然后再精细化讲解各个事件的详情。 话外音:赶时间的小伙伴可以拿图走人????,但不建议白嫖哟生命周期事件流程图 版本说明:由于不同版本、类路径下存在不同包时结果会存在差异,不指明版本的文章都是不够负责任的。因此对导包/版本情况作出如下说明: Spring Boot:2.2.2.RELEASE。有且仅导入spring-boot-starter-web和spring-boot-starter-actuatorSpring Cloud:Hoxton.SR1。有且仅导入spring-cloud-context(注意:并非spring-cloud-starter,并不含有spring-cloud-commons哦)总的来说:本例导包是非常非常“干净”的,这样在流程上才更有说服力嘛~ SpringApplicationEvent它是和SpringApplication生命周期有关的所有事件的父类,@since 1.0.0。 public abstract class SpringApplicationEvent extends ApplicationEvent { private final String[] args; public SpringApplicationEvent(SpringApplication application, String[] args) { super(application); this.args = args; } public SpringApplication getSpringApplication() { return (SpringApplication) getSource(); } public final String[] getArgs() { return this.args; }}它是抽象类,扩展自Spring Framwork的ApplicationEvent,确保了事件和应用实体SpringApplication产生关联(当然还有String[] args)。它有如下实现子类(7个): ...

July 5, 2020 · 2 min · jiezi

CKEditor-5-SpringBoot实战二SpringBoot-Application-环境搭建

在本系列的文章中,我将介绍如何在Spring Boot Application中使用CKEditor编辑器。介绍的内容包括基本环境的搭建,文件上传,SpringData JPA数据持久化,CKEditor5的安装,CKEditor图片上传,CKEditor插入视频,获取/设置CKEditor内容等。 项目源码本系列文章的项目源码同步更新至码云 和 Github ,你可以任选其一下载源码到本地。项目地址如下: 码云:https://gitee.com/ramostear/CKEditor5-SpringBootGithub:https://github.com/ramostear/CKEditor5-SpringBoot你也可以通过Git命令行工具下载项目源码,命令如下(二者任选其一): git clone https://gitee.com/ramostear/CKEditor5-SpringBoot.gitgit clone https://github.com/ramostear/CKEditor5-SpringBoot.git需求分析需求分析是项目开始的第一步,经过分析和思考,才能明确我们的设计目标。在本项目中,我们有如下的需求: 使用CKEditor5 Web编辑器在线编辑内容在需要的时候,可以在编辑的内容中插入图片素材除了能插入图片,还需要有插入视频的功能将编辑好的内容保存到数据库在需要的时候,可再次对数据库中存储的内容进行二次编辑能在线浏览数据库中的内容在必要的时候,需要对数据库中存储的内容进行删除(包括上传的图片)经过整理,我们可提炼出这样几个核心功能:添加内容,编辑内容,删除内容,查询内容,上传图片,插入视频和删除图片。下面是项目的用例图,用例图能更直观的帮助我们理解项目需求。 项目依赖通过分析,我们已经明确项目所要实现的功能。接下来,需要考虑项目的类型以及所需要的第三方依赖包和依赖包的管理方式。 首先,我们可以确定该项目是一个Web项目,第三方的依赖包可以通过Maven来进行管理。然后是确定项目所需的依赖包: Web项目,需要Spring MVC依赖包;视图展现,需要Freemarker依赖包;在线编辑内容,使用CKEditor5实现(JavaScript第三方库);连接MySQL数据库,需要MySQL数据库驱动依赖包;数据持久化,需要SpringData JPA依赖包;数据库连接池管理,需要Alibaba Druid依赖包;文件上传,需要commons-fileupload依赖包;JSON数据转换,需要Alibaba FastJSON依赖包。提示:在编码的过程中,为了防止一次性导入所有依赖包导致依赖包下载慢或下载失败,可根据开发进度,按需加入相关的依赖包。 创建项目在开始创建项目之前,我默认你已经在本地机器上安装并配置好了JDK、Maven和MySQL。创建并初始化一个Java Web项目的最好方式是使用SpringBoot,我将使用IntelliJ IDEA提供的Spring Initializr创建SpringBoot应用程序。关于IDE的选择,在此不再赘述,IntelliJ IDEA或Eclipse创建SpringBoot应用程序的步骤大致相同。 IntelliJ IDEA创建SpringBoot Application启动IntelliJ IDEA,依次点击 File > New > Project…按钮,如下图: 在弹出的New Project对话框中,选择“Spring Initializr”选项,然后将“Project SDK”修改为本机安装的SDK,接着点击“Next”进入项目元数据配置对话框。 紧接着,我们需要对项目的元数据进行设置,具体参数可更具自身情况进行调整,也可以与本教程保持一致,元数据设置完毕后,点击“Next”进入依赖配置对话框。 为了快速完成项目项目的创建和初始化工作,在一开始,我们只勾选Spring Web依赖,并将Spring Boot的版本调整到2.2.8,然后点击“Next”进入项目信息确认对话框。 在项目信息确认无误后,点击“Finish”按钮完成项目的创建。 最后,IntelliJ IDEA会弹出一个询问框,你可以选择在当前窗口或新窗口打开项目,根据个人习惯进行选择。选择完毕后,稍等片刻,IntelliJ IDEA将自动从Spring上下载项目基础源码包,并完成项目初始化工作。 打开项目后,请在Settings中找到Maven配置项,并检查其配置是否为本地安装的Maven,如下图: 设置完成后,点击窗口右下角弹框中的“Enable Auto Import”链接,让Maven重新导入项目的依赖(若已设置好Maven,请忽略该步骤),如下图: 下面是初始化完成后的项目工程结构: 至此,整个项目的创建和初始化工作已完成。Ckeditor5SpringBootApplication.java为项目的主类,接下来,我们将通过改类来启动项目,验证项目是否正常可用。 启动项目在“com.ramostear.ckeditor”包中找到并打开Ckeditor5SpringbootApplication.java类,我们将通过此类来启动项目。 ...

July 5, 2020 · 1 min · jiezi

springcloud系列之gateway动态网关

springcloud系列之gateway简介spirngcloud-gateway是Spring全家桶的网关组件。基于webflux异步非阻塞、性能好、详细介绍请绕道gateway官网。本文基于nacos服务注册发现中心,实现网关的配置、动态网关的修改和刷新。打开RouteDefinition类,gateway的路由是有id、PredicateDefinition(断言)、filters、uri(目标地址)、metadata和order组成的项目地址:https://github.com/wotrd/naco... @Validatedpublic class RouteDefinition { private String id; @NotEmpty @Valid private List<PredicateDefinition> predicates = new ArrayList<>(); @Valid private List<FilterDefinition> filters = new ArrayList<>(); @NotNull private URI uri; private Map<String, Object> metadata = new HashMap<>(); private int order = 0;使用方式1、添加依赖 gateway依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>Hoxton.SR6</version> </dependency>注册和配置中心依赖 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2.2.1.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.1.RELEASE</version> </dependency>2、配置文件配置路由1、这里,我们使用nacos配置中心,先创建bootstrap.properties启动配置文件加载远程配置。 #nacos配置中心地址spring.cloud.nacos.config.server-addr=x.x.x.x:8848#配置文件分组spring.cloud.nacos.config.group=GATEWAY_GROUP#配置文件后缀,使用yml文件需要配置spring.cloud.nacos.config.file-extension=yml2、在注册中心创建gate-way-service-dev.yml文件 spring: cloud: gateway: routes:# - id: after_route# uri: https://www.baidu.com/# predicates:# - After=2020-06-29T06:06:06+08:00[Asia/Shanghai] - id: before_route uri: https://www.ailijie.top predicates: - After=2020-06-20T06:06:06+08:00[Asia/Shanghai] - id: myRoute uri: lb://feign-service predicates: - Path=/feign-service/**3、代码配置路由创建RouteConfig配置类 ...

July 2, 2020 · 4 min · jiezi

Springboot-oauth2使用RestTemplate进行后台自动登录

内容不限于登录业务,主要简单介绍RestTemplate的用法,包括 使用RestTemplate进行post请求 postForObject使用RestTemplate带body/form-data进行post请求 MultiValueMap使用RestTemplate带josn进行post请求JSONObject使用RestTemplate带头信息headers进行post请求 HttpHeaders登录流程定义 RestTemplate定义 MultiValueMap,构造 post的body内容定义 HttpHeaders,构造请求的头部信息定义 HttpEntity,发送请求的实体定义 RestTemplate,进行请求。返回数据主要代码 // 构造 post的body内容(要post的内容,按需定义) MultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>(); paramsMap.set("grant_type", "password"); paramsMap.set("username", "yourname"); paramsMap.set("password", "yourpassword"); // 构造头部信息(若有需要) HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "Basic xxxxxx你的认证密钥"); // 设置类型 "application/json;charset=UTF-8" headers.setContentType(MediaType.APPLICATION_JSON); // 构造请求的实体。包含body和headers的内容 HttpEntity<MultiValueMap<String, String>> request = new HttpEntity(paramsMap, headers); // 声明 restTemplateAuth(用作请求) RestTemplate restTemplateAuth = new RestTemplate(); // 进行请求,并返回数据 String authInfo = restTemplateAuth.postForObject("http://localhost:8089/oauth/token", request, String.class);使用josn请求的示例代码Posting JSON with postForObject ...

July 2, 2020 · 1 min · jiezi

个人学习系列-springboot防止重复提交

最近开发项目时候发现,有时候因为网络或者个人问题,会出现重复点击提交按钮的情况,这样有可能会在数据库生成两条数据,造成数据混淆。今天来谈一下如何解决这个问题。搭建springboot项目1. 选择新建项目 2. 选择Spring Initializr 3. 填写相关信息 4. 选择web依赖 5. 选择项目位置 开始代码书写啦1. pom.xml文件依赖添加<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency><dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>29.0-jre</version></dependency>2. 定义一个异常类/** * 返回信息 * * @author zhouzhaodong */public class RestMessage { private int code; private String message; private Object data; public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public RestMessage(int code, String message, Object data) { this.code = code; this.message = message; this.data = data; } public RestMessage(int code, String message) { this.code = code; this.message = message; }}3. 定义一个接口import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * 定义一个注解 * @apiNote @Target(ElementType.METHOD) 作用到方法上 * @apiNote @Retention(RetentionPolicy.RUNTIME) 只有运行时有效 * @author zhouzhaodong */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface NoRepeatSubmit {}4. 定义一个切面import com.google.common.cache.Cache;import com.google.common.cache.CacheBuilder;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.context.annotation.Configuration;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;import java.util.Objects;import java.util.concurrent.TimeUnit;/** * 自定义一个切面类,利用aspect实现切入所有方法 * * @author zhouzhaodong */@Aspect@Configurationpublic class NoRepeatSubmitAop { private final Log logger = LogFactory.getLog(getClass()); /** * 重复提交判断时间为2s */ private final Cache<String, Integer> cache = CacheBuilder.newBuilder().expireAfterWrite(2L, TimeUnit.SECONDS).build(); @Around("execution(* xyz.zhouzhaodong..*Controller.*(..)) && @annotation(nrs)") public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit nrs) { try { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); String sessionId = Objects.requireNonNull(RequestContextHolder.getRequestAttributes()).getSessionId(); assert attributes != null; HttpServletRequest request = attributes.getRequest(); String key = sessionId + "-" + request.getServletPath(); // 如果缓存中有这个url视为重复提交 if (cache.getIfPresent(key) == null) { Object o = pjp.proceed(); cache.put(key, 0); return o; } else { logger.error("重复提交"); return new RestMessage(888, "请勿短时间内重复操作"); } } catch (Throwable e) { e.printStackTrace(); logger.error("验证重复提交时出现未知异常!"); return new RestMessage(889, "验证重复提交时出现未知异常!"); } }}5. 写controller进行测试import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * 测试 * @author zhouzhaodong */@RestController@RequestMapping("/test")public class TestController { /** * 添加防重复提交注解 * @return */ @NoRepeatSubmit @RequestMapping("/one") public RestMessage test(){ return new RestMessage(0, "测试通过"); }}第一次点击返回正常信息:快速点击第二次会出现错误信息: ...

July 2, 2020 · 2 min · jiezi

SpringBoot配置文件详解云图智联

配置文件详解SpringBoot的全局配置文件有两种: application.propertiesapplication.yml配置文件的作用:修改SpringBoot自动配置的默认值. properties配置文件我们之前都有接触,今天就让我们来认识一下yaml配置文件吧。 YAML表示YAML Ain’t Markup Language,在百度百科的解释是: YAML是"YAML Ain't a Markup Language"(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。所以,我们不用在意它是否是一种标记语言,我们只要记得它是一种以数据为中心的语言就可以,语法非常简洁,使用空白,缩进,分行组织数据,从而使得表示更加简洁易读。 YAML基本语法大小写敏感使用缩进表示层级关系缩进时不允许使用Tab键,只允许使用空格。缩进的空格数目不重要,只要相同层级的元素左侧对齐即可所以YAML基本语法其实就是key:(空格)value的形式,其中空格必须要有,以空格的缩进来控制层级关系,只要对齐的一列数据都是同一个层级的,比如: server: port: 8081 path: /exampleYAML支持的数据结构字面量(也有“纯量”一叫法)语法: 字面量:普通的值(整数、浮点数、字符串、布尔、Null值、时间、日期) key: value(字面值直接写上就可以)字符串也默认不需要加上单引号和双引号的(也可以加上引号)单引号:会转义特殊字符,将特殊字符转为一个普通的字符串name: 'xiaowang \n' 打印 xiaowang \n (ps:这里的\n被转成字符串)双引号:不会转义特殊字符,特殊字符还是表达其本身想表示的意思name: 'xiaowang \n' 打印 xiaowang 换行 (ps:这里的\n执行换行操作)示例: name: 张三boolean: TRUE  #true,True都可以float: - 3.14 - 6.8523015e+5  #可以使用科学计数法int: - 123 - 0b1010\_0111\_0100\_1010\_1110    #二进制表示null: nodeName: 'node' parent: ~  #使用~表示nullstring: - 哈哈 - 'Hello world'  #可以使用双引号或者单引号包裹特殊字符 - newline newline2    #字符串可以拆成多行,每一行会被转化成一个空格date: - 2018-02-17    #日期必须使用ISO 8601格式,即yyyy-MM-dd**注意**:在springboot中yaml文件的时间格式 date: yyyy/MM/dd HH:mm:ss对象对象:也可以说是map,也就是键值对的形式key: value形式(对象属性key: value的形式表示,在对象名下一行写属性: 属性值)同一个对象的各个属性前对齐就可以(默认是两个空格)示例: ...

July 1, 2020 · 3 min · jiezi

Springboot快速上手-第二篇-helloWord走起

1 基础工程创建1:创建一个maven工程2:加入parent <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> </parent>3:加入启动依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>4:设置properties <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <start-class>com.cc.Application</start-class> </properties>5:配置springboot 插件 <build> <finalName>springbootstudy</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>6:开发Controller@Controller@RequestMapping("/hello")public class FirstController { @RequestMapping("/abc") @ResponseBody public String abc() { System.out.println("now in FirstController.abc"); return "Hello World!"; }}7:启动类@SpringBootApplicationpublic class App { public static void main(String[] args) { //负责启动引导应 用程序 SpringApplication.run(App.class, args); }}8:启动运行先运行启动类,然后在浏览器输入:<u>http://localhost:8080/hello/abc</u>@SpringBootApplication:开启组件扫描和自动配置,实际 上,@SpringBootApplication将三个有用的注解组合在了一起: Spring的@Configuration:标明该类使用Spring基于Java的配置 Spring的@ComponentScan:启用组件扫描 Spring Boot的@EnableAutoConfiguration:开启Spring Boot自动配置 2 加入数据层1:环境设置(1)加入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.43</version> </dependency>(2)在数据库中创建一个库springbootstudy,同时建一个表tbl_user,有如下字段:uuid、name、age ...

June 30, 2020 · 1 min · jiezi

Springboot快速上手-第四篇-自定义配置

1:概述Spring Boot支持根据应用的实际需要,进行自定义配置, Spring Boot能从多种属性源获得属性,包括如下几处: 2:可调整的属性对于Spring Boot自动配置的Bean,目前提供了上千个用于微调的属性。具体的可以参看官方文档:https://docs.spring.io/spring-boot/docs/2.0.0.M4/reference/htmlsingle/#common-application-properties 3: 自定义属性Spring Boot允许使用properties文件、yml文件或者命令行参数作为外部配置,并提供自定义属性的支持。比如我们可以在application.properties配置一些常量,例如: cc.add.k1=k1vvcc.add.k2=k222然后直接在要使用的地方通过注解@Value(value=”${config.name}”)就可以绑定到你想要的属性上面 1:使用@Value注解,可以直接将属性值注入到你的beans中,也可以通过Spring的Environment抽象或绑定到结构化对象来访问。2:如果属性太多了,一个个绑定到属性字段上太累,官方提倡绑定一个对象的bean,比如:这里我们建一个ConfigBean.java类,顶部需要使用注解@ConfigurationProperties(prefix = “cc.add”)来指明使用哪个,示例如下: @ConfigurationProperties(prefix="cc.add")public class SysConf { private String k1; private String k2; // 省略getter和setter}3:然后在启动类上添加: @EnableConfigurationProperties({SysConf.class})4:然后就可以在要使用的类里面直接注入了:@Autowiredprivate SysConf sc;5:也可以使用 @Autowired private Environment env;然后用env.getProperty("cc.add.k1")去访问 4: 参数间引用在application.properties中的各个参数之间也可以直接引用来使用,使用${paramID},如: cc.add.k1=k1vv cc.add.k2=k222-${cc.add.k1}5: 使用自定义配置文件有时候我们不希望把所有配置都放在application.properties里面,这时候我们可以另外定义一个,比如:my.properties,形如: cc.add.k3=k3vvcc.add.k4=k444-${cc.add.k1}1:然后再写一个配置bean,形如: @Configuration@ConfigurationProperties(prefix = "cc.add") @PropertySource("classpath:my.properties")public class MyConf { private String k3; private String k4;}由于配置了@Configuration,因此不用在启动类中添加了 2:使用方法跟前面使用属性配置类SysConf是一样的 6: 配置随机值可以使用${random} 可以用来生成各种不同类型的随机值,例如: ${random.value}${random.int}${random.long}${random.uuid}${random.int(5)}${random.int[5,10]}7: 配置文件的放置位置自定义的配置文件,有多个可以放置的位置: (1)外置,在相对于应用程序运行目录的/config子目录里(2)外置,在应用程序运行的目录里(3)内置,在config包内(4)内置,在Classpath根目录1:这个列表按照优先级排序。也就是说,/config子目录里的application.properties会覆盖应用程序Classpath里的application.properties中的相同属性。 2:此外,如果你在同一优先级位置同时有application.properties和application.yml,那么application.properties里的属性会覆盖application.yml里的属性(有资料说的相反) 微调自动配置示例简单的给出几个微调自动配置的例子,以学习微调的方法。1:配置嵌入式服务器端口,形如:server.port=9080 配置上下文路径,形如:server.context-path=/myweb 8: 配置日志 默认情况下,Spring Boot会用Logback来记录日志,并用INFO级别输出到控制台。一般来说,你不需要切换日志实现;Logback能很好地满足你的需要。 1:如果决定使用Log4j或者Log4j2,那么你只需要修改依赖,引入对应该日志实现的起步依赖,同时排除掉 Logback。以Maven为例,应排除掉根起步依赖传递引入的默认日志起步依赖,这样就能排除 Logback了,形如: ...

June 30, 2020 · 1 min · jiezi

Springboot快速上手-第三篇-注解流程依赖

1:SpringBootApplication注解简介在启动类上有一个非常重要的注解,就是SpringBootApplication注解,理解它对于理解SpringBoot的启动过程很有帮助。可以查看一下SpringBootApplication的源码,里面有很多注解,其中最重要的有:1:@Configuration,这个是在@SpringBootConfiguration里面用的2:@EnableAutoConfiguration3:@ComponentScan @EnableAutoConfiguration 2:SpringApplication执行流程可以配着看源码 3:起步依赖什么是起步依赖Spring Boot通过提供众多起步依赖来降低项目依赖的复杂度。所谓起步依赖,就是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能好处不用管究竟需要哪些依赖:添加相应的起步依赖,就相当于把一堆需要的依赖都加入了。也不用管具体要依赖什么版本:起步依赖本身的版本是由正在使用的Spring Boot的版本来决定 的,而起步依赖则会决定它们引入的传递依赖的版本。Springboot目前有哪些起步依赖可以参看官方文档:https://docs.spring.io/spring... 调整起步依赖1:需要特定版本的依赖 直接在pom里面添加相应的依赖,并指定需要的版本2:排除部分依赖使用exclusion,例如: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </exclusion> </exclusions></dependency>

June 30, 2020 · 1 min · jiezi

SpringBoot-WEB-系列RestTemplate-之自定义请求头

【WEB 系列】RestTemplate 之自定义请求头上一篇介绍了 RestTemplate 的基本使用姿势,在文末提出了一些扩展的高级使用姿势,本篇将主要集中在如何携带自定义的请求头,如设置 User-Agent,携带 Cookie Get 携带请求头Post 携带请求头拦截器方式设置统一请求头<!-- more --> I. 项目搭建1. 配置借助 SpringBoot 搭建一个 SpringWEB 项目,提供一些用于测试的 REST 服务 SpringBoot 版本: 2.2.1.RELEASE核心依赖: spring-boot-stater-web<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency></dependencies>为了后续输出的日志更直观,这里设置了一下日志输出格式,在配置文件application.yml中,添加 logging: pattern: console: (%msg%n%n){blue}2. Rest 服务添加三个接口,分别提供 GET 请求,POST 表单,POST json 对象,然后返回请求头、请求参数、cookie,具体实现逻辑相对简单,也不属于本篇重点,因此不赘述说明 @RestControllerpublic class DemoRest { private String getHeaders(HttpServletRequest request) { Enumeration<String> headerNames = request.getHeaderNames(); String name; JSONObject headers = new JSONObject(); while (headerNames.hasMoreElements()) { name = headerNames.nextElement(); headers.put(name, request.getHeader(name)); } return headers.toJSONString(); } private String getParams(HttpServletRequest request) { return JSONObject.toJSONString(request.getParameterMap()); } private String getCookies(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies == null || cookies.length == 0) { return ""; } JSONObject ck = new JSONObject(); for (Cookie cookie : cookies) { ck.put(cookie.getName(), cookie.getValue()); } return ck.toJSONString(); } private String buildResult(HttpServletRequest request) { return buildResult(request, null); } private String buildResult(HttpServletRequest request, Object obj) { String params = getParams(request); String headers = getHeaders(request); String cookies = getCookies(request); if (obj != null) { params += " | " + obj; } return "params: " + params + "\nheaders: " + headers + "\ncookies: " + cookies; } @GetMapping(path = "get") public String get(HttpServletRequest request) { return buildResult(request); } @PostMapping(path = "post") public String post(HttpServletRequest request) { return buildResult(request); } @Data @NoArgsConstructor public static class ReqBody implements Serializable { private static final long serialVersionUID = -4536744669004135021L; private String name; private Integer age; } @PostMapping(path = "body") public String postBody(@RequestBody ReqBody body) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); return buildResult(request, body); }}II. 使用姿势最常见的携带请求头的需求,无非是 referer 校验,user-agent 的防爬以及携带 cookie,使用 RestTemplate 可以借助HttpHeaders来处理请求头 ...

June 30, 2020 · 4 min · jiezi

springcloud项目优雅重启六解决方案

问题回到第一章节讲到的几个问题 : 业务项目实例shutdown时,会停止当前未完成的REQUEST请求。某个业务项目实例已经停止了,但是网关仍会转发请求过去,导致请求失败。某个业务项目实例已经重新启动了,但是网关并不会马上向这个实例转发请求;假如项目只有两个实例,如果在第一个节点刚启动完就立刻重启另外一个实例,就会导致服务不可用。第一个问题是因为:springboot-tomcat在系统停止时会粗暴的shutdownNow线程池。第二和第三个问题的原因是一样的,因为ribbon缓存和eureka缓存导致: 项目服务节点停止后,gateway从缓存(ribbon、eureka-client、eureka-server三个都有可能缓存过时的数据)里还能取到该节点信息,还会访问该节点。项目启动后,同样由于gateway从缓存(ribbon、eureka-client、eureka-server三个都有可能缓存过时的数据)里还没有该节点数据,所以不会向该节点请求数据。整体方案所以解决问题的核心在于,在服务停止之前(严格按照顺序) : 注销注册中心信息。清除gateway eureka-client注册信息。让gateway ribbon重新获取服务列表(刷新缓存)。tomcat停止接收请求。等待tomat处理未完成的请求。停止tomcat。在服务启动且eureka完成注册之后(严格按照顺序): tomcat启动完成。注册服务。让gateway eureka-client立即获取服务。让gateway ribbon重新获取服务列表(刷新缓存)。流程图 eureka-client注销1. 注销注册中心信息从《项目优雅重启(三):eureka-client》可以看到,DiscoveryClient有个shutdown方法,并且这个方法是public,而shutdown方法是在接口EurekaClient定义的,所以我们只要EurekaClient.shutdown即可。springboot的EurekaClientAutoConfiguration类里会生产EurekaClient-Bean,所以只需要注入EurekaClient即可。 @Autowiredprivate EurekaClient client;这里需要特别注意的是:eureka-client会调用eureka-server接口来注销信息,假如网络出了问题,或者eureka-client出了问题了,可能会导致请求非常慢,所以我们需要加一层保障,保证在指定的时间内还没完成注销操作,就强行中断并继续下一步。 private void unregisterEurekaData() { Thread shutDownEurakaThread = new Thread(() -> { try { getClient().shutdown(); logger.info(" ============================== shutdown local eureka data success!"); } catch (Exception e) { logger.error(" ============================== shutdown local eureka data error!", e); } }); try { shutDownEurakaThread.start(); shutDownEurakaThread.join(unregisterEurekaShutdownWaitSeconds * 1000); // 不能无限制等待 if (shutDownEurakaThread.isAlive()) { logger.error(" ============================== shutdown local eureka doesn't compelete in allow time!"); shutDownEurakaThread.interrupt(); } } catch (Exception e) { logger.error(" ============================== shutdown local eureka data error!", e); }} 2. 刷新gateway缓存可以通过gateway提供接口,client调用接口的方式来刷新缓存,gateway接口内容后面会讲到,client这边需要做的是: ...

June 30, 2020 · 5 min · jiezi

GoogleGithub账号等登录Web应用

视频演示: https://www.bilibili.com/video/BV1M54y1z7A9/ 需要完成以下步骤:创建工程并引入依赖包 spring-boot-starter-oauth2-clientspring-boot-starter-securityspring-boot-starter-web创建Security配置信息创建Github、Google的client-id和client-secret Oauth2GoogleLoginApplication.javapackage com.deepincoding.oauth2googlelogin;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class Oauth2GoogleLoginApplication { public static void main(String[] args) { SpringApplication.run(Oauth2GoogleLoginApplication.class, args); }}MessageController.javapackage com.deepincoding.oauth2googlelogin;import lombok.extern.log4j.Log4j2;import org.springframework.security.core.annotation.AuthenticationPrincipal;import org.springframework.security.oauth2.core.user.OAuth2User;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController@Log4j2public class MessageController { @GetMapping("/") public String hello(){ return "Hello Google Github."; } @GetMapping("/guest") public String guest(){ return "Hello Guest."; } @GetMapping("/admin") public OAuth2User admin(@AuthenticationPrincipal OAuth2User principal){ return principal; }}SecurityConfig.javapackage com.deepincoding.oauth2googlelogin;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .antMatcher("/**").authorizeRequests() .antMatchers("/", "/guest").permitAll() .anyRequest().authenticated() .and() .oauth2Login(); }}application.ymlspring: security: oauth2: client: registration: github: client-id: d64d35f9f66ea04ba64f client-secret: b84a72f20fcec596deb947860cd4eacaf55b0f5b google: client-id: 195066099347-nj4et113vfl9p0aq9k5cb4hd6kg0c3hh.apps.googleusercontent.com client-secret: Wk5XNm8wv36R0h4zGCxWOfYn本文使用 mdnice 排版 ...

June 29, 2020 · 1 min · jiezi

Springboot快速上手-第一篇-初见

SpringbootSpringBoot是什么Spring Boot是Spring团队提供的、一套全新的、用于简化基于Spring应用开发的开发方式(套件),从而加快Spring应用的开发。本质上,SpringBoot 就是 Spring,它做了那些没有它你也会去做的Spring Bean配置。它使用“习惯优于配置” 的理念让你的项目快速运行起来。 SpringBoot能干什么SpringBoot能对开发基于Spring对应用提供很多的帮助,主要有:1:起步依赖:无需手动管理依赖jar包的版本2:自动配置:无需xml,针对很多Spring应用程序常见的应用功能,Spring Boot能自动提供相关配置3:支持外部化配置4:自带web容器5:可运行的jar包6:提供开发时辅助:devtools7:提供运行期生产特性:深入了解运行期的Spring应用的内部细节8:与Spring其它技术的无缝集成 通过几天的学习,首先最有收获的地方应该是内心沉静下来了,每天面对工作机械的crud,有时候是烦躁的,但是又不知道如何改变现状,疫情期间也不能出去乱逛把之前购买CC老师的课程翻出来,继续学习吧~~哈哈,我给大家来了硬广,618折扣才6800元,课程内容惊喜多多,详情都在私塾的官网,感兴趣的小伙伴,可以一起去 【私塾在线 】 学习

June 29, 2020 · 1 min · jiezi

Spring-Boot整合MyBatis

### 3.1 Spring Boot整合MyBatis MyBatis 是一款优秀的持久层框架,Spring Boot官方虽然没有对MyBatis进行整合,但是MyBatis团队自行适配了对应的启动器,进一步简化了使用MyBatis进行数据的操作 因为Spring Boot框架开发的便利性,所以实现Spring Boot与数据访问层框架(例如MyBatis)的整合非常简单,主要是引入对应的依赖启动器,并进行数据库相关参数设置即可 **基础环境搭建**: **1.数据准备** 在MySQL中,先创建了一个数据库springbootdata,然后创建了两个表t_article和t_comment并向表中插入数据。其中评论表t_comment的a_id与文章表t_article的主键id相关联 ```sql # 创建数据库 CREATE DATABASE springbootdata; # 选择使用数据库 USE springbootdata; # 创建表t_article并插入相关数据 DROP TABLE IF EXISTS t_article; CREATE TABLE t_article ( id int(20) NOT NULL AUTO_INCREMENT COMMENT '文章id', title varchar(200) DEFAULT NULL COMMENT '文章标题', content longtext COMMENT '文章内容', PRIMARY KEY (id) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO t_article VALUES ('1', 'Spring Boot基础入门', '从入门到精通讲解...'); ...

June 29, 2020 · 4 min · jiezi

springboot执行原理

2.4 执行原理 每个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法,在该方法中通过执行SpringApplication.run()即可启动整个Spring Boot程序。 问题:那么SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢? 下面我们查看run()方法内部的源码,核心代码具体如下: ```java @SpringBootApplication public class SpringbootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootDemoApplication.class, args); } } ``` ```java 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); } ``` 从上述源码可以看出,SpringApplication.run()方法内部执行了两个操作,分别是SpringApplication实例的初始化创建和调用run()启动项目,这两个阶段的实现具体说明如下 **1.SpringApplication实例的初始化创建** 查看SpringApplication实例对象初始化创建的源码信息,核心代码具体如下 ```java public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { this.sources = new LinkedHashSet(); this.bannerMode = Mode.CONSOLE; ...

June 29, 2020 · 2 min · jiezi

嘘异步事件这样用真的好么

故事背景今年年初的时候写了一篇文章 《围观:基于事件机制的内部解耦之心路历程》。这篇文章主要讲的是用 ES 数据异构的场景。程序订阅 Mysql Binlog 的变更,然后程序内部使用 Spring Event 来分发具体的事件,因为一个表的数据变更可能会需要更新多个 ES 索引。 为了方便大家理解我把之前方案的图片复制过来了,如下: 上图的方案存在一个问题,就是我们今天文章要聊的内容。 这个问题就是当 MQ Consumer 收到消息后,就直接发布 Event 了,如果是同步的,没有问题。如果某个 EventListener 中处理失败了,那么这条消息将不会 ACK。 如果是异步发布 Event 的场景,发布完消息马上就 ACK 了。就算某个 EventListener 中处理失败了,MQ 也感知不到,不会进行消息的重新投递,这就是存在的问题。 解决方案方案一既然消息已经 ACK 了,那就不利用 MQ 的重试功能了,使用方自己重试是不是也可以呢? 可肯定是可以的,内部处理是否成功肯定是可以知道的,如果处理失败了可以默认重试,或者有一定策略的重试。实在不行还可以落库,保存记录。 这样的问题在于太烦了呀,每个使用的地方都要去做这件事情,而且对于未来接手你代码的程序小哥哥来说,这很有可能让小哥哥头发慢慢脱落啊。。。。 脱落不要紧,关键他还不知道要做这个处理,说不定哪天就背锅了,惨兮兮。。。。 方案二要保证消息和业务处理的一致性,就不能立马进行 ACK 操作。而是要等业务处理完成后再决定是否要 ACK。 如果有处理失败的就不应该 ACK,这样就能复用 MQ 的重试机制了。 分析下来,这就是一个典型的异步转同步的场景。像 Dubbo 中也有这个场景,所以我们可以借鉴 Dubbo 中的实现思路。 创建一个 DefaultFuture 用于同步等待获取任务执行结果。然后在 MQ 消费的地方使用 DefaultFuture。 @Service@RocketMQMessageListener(topic = "${rocketmq.topic.data_change}", consumerGroup = "${rocketmq.group.data_change_consumer}")public class DataChangeConsume implements RocketMQListener<DataChangeRequest> { @Autowired private ApplicationContext applicationContext; @Autowired private CustomApplicationContextAware customApplicationContextAware; @Override public void onMessage(DataChangeRequest dataChangeRequest) { log.info("received message {} , Thread {}", dataChangeRequest, Thread.currentThread().getName()); DataChangeEvent event = new DataChangeEvent(this); event.setChangeType(dataChangeRequest.getChangeType()); event.setTable(dataChangeRequest.getTable()); event.setMessageId(dataChangeRequest.getMessageId()); DefaultFuture defaultFuture = DefaultFuture.newFuture(dataChangeRequest, customApplicationContextAware.getTaskCount(), 6000 * 10); applicationContext.publishEvent(event); Boolean result = defaultFuture.get(); log.info("MessageId {} 处理结果 {}", dataChangeRequest.getMessageId(), result); if (!result) { throw new RuntimeException("处理失败,不进行消息ACK,等待下次重试"); } }}newFuture() 会传入事件参数,超时时间,任务数量几个参数。任务数量是用于判断所有 EventListener 是否全部执行完成。 ...

June 29, 2020 · 2 min · jiezi

自动配置启动流程

### 2.2 自动配置(启动流程) 概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置,我们无需配置或者只需要少量配置就能运行编写的项目 问题:Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置? Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法, @SpringBootApplication能够扫描Spring组件并自动配置Spring Boot 下面,查看@SpringBootApplication内部源码进行分析 ,核心代码具体如下 ```java @SpringBootApplication public class SpringbootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootDemoApplication.class, args); } } ``` ```java @Target({ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中 @Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时 @Documented //表示注解可以记录在javadoc中 @Inherited //表示可以被子类继承该注解 @SpringBootConfiguration // 标明该类为配置类 @EnableAutoConfiguration // 启动自动配置功能 @ComponentScan( // 包扫描器 excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication { ...

June 28, 2020 · 2 min · jiezi

执行原理

2.4 执行原理 每个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法,在该方法中通过执行SpringApplication.run()即可启动整个Spring Boot程序。 问题:那么SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢? 下面我们查看run()方法内部的源码,核心代码具体如下: ```java @SpringBootApplication public class SpringbootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootDemoApplication.class, args); } } ``` ```java 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); } ``` 从上述源码可以看出,SpringApplication.run()方法内部执行了两个操作,分别是SpringApplication实例的初始化创建和调用run()启动项目,这两个阶段的实现具体说明如下 **1.SpringApplication实例的初始化创建** 查看SpringApplication实例对象初始化创建的源码信息,核心代码具体如下 ```java public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { this.sources = new LinkedHashSet(); this.bannerMode = Mode.CONSOLE; ...

June 28, 2020 · 1 min · jiezi

七-SpringBoot起飞之路整合SpringSecurityMybatisJDBC内存

兴趣的朋友可以去了解一下前五篇,你的赞就是对我最大的支持,感谢大家! (一) SpringBoot起飞之路-HelloWorld (二) SpringBoot起飞之路-入门原理分析 (三) SpringBoot起飞之路-YAML配置小结(入门必知必会) (四) SpringBoot起飞之路-静态资源处理 (五) SpringBoot起飞之路-Thymeleaf模板引擎 (六) SpringBoot起飞之路-整合JdbcTemplate-Druid-MyBatis 说明: 这一篇的目的还是整合,也就是一个具体的实操体验,原理性的没涉及到,我本身也没有深入研究过,就不献丑了SpringBoot 起飞之路 系列文章的源码,均同步上传到 github 了,有需要的小伙伴,随意去 down https://github.com/ideal-20/S...才疏学浅,就会点浅薄的知识,大家权当一篇工具文来看啦,不喜勿愤哈 ~(一) 初识 Spring Security(1) 引言权限以及安全问题,虽然并不是一个影响到程序、项目运行的必须条件,但是却是开发中的一项重要考虑因素,例如某些资源我们不想被访问到或者我们某些方法想要满足指定身份才可以访问,我们可以使用 AOP 或者过滤器来实现要求,但是实际上,如果代码涉及的逻辑比较多以后,代码是极其繁琐,冗余的,而有很多开发框架,例如 Spring Security,Shiro,已经为我们提供了这种功能,我们只需要知道如何正确配置以及使用它了 (2) 基本介绍先看一下官网的介绍 Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications. Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于spring的应用程序的实际标准。 Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements ...

June 28, 2020 · 9 min · jiezi

第一章-SpringBoot-初始springboot云图智联

第一章 SpringBoot 初始springboot 1.springboot介绍 基于Spring4.0设计,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决2.springboot产生的环境用来简化新Spring应用的初始搭建以及开发过程、J2EE笨重的开发、繁多的配置、低下的开发效率简化复杂的部署流程为解决第三方技术集成难度大3.springboot的优点快速创建独立运行的Spring项目以及与主流框架集成使用嵌入式的Servlet容器,应用无需打成WAR包starters自动依赖与版本控制大量的自动配置,简化开发,也可修改默认值无需配置XML ,无代码生成,开箱即用准生产环境的运行时应用监控与云计算的天然集成4.为什么使用Springboot? 其实就是简单、快速、方便!平时如果我们需要搭建一个 Spring Web 项目的时候需要怎么做呢?我们回想一下我们写传统的web项目的过程 1. 配置 web.xml,加载 Spring 和 Spring mvc 2. 配置数据库连接、配置 Spring 事务 3. 配置加载配置文件的读取,开启注解 4. 配置日志文件 5. 配置完成之后部署 Tomcat 调试 6. ...... 但是如果使用 Spring Boot 呢?很简单,我仅仅只需要非常少的几个配置就可以迅速方便的搭建起来一套 Web 项目或者是构建一个微服务!使用 Spring Boot 到底有多爽,谁用谁知道。是不是迫不及待的想要创建一个springboot项目呢,接下来就让我们一起去见证springboot的强大之处吧。 5.创建HelloWord在这里我们使用idea开发工具,其他开发工具类似1.新建项目 2.选择spring Initializr (注意:sdk是jdk的安装目录,jdk要求在1.8以上版本) 3.配置项目信息 说明:Group:组织或公司名称,也相当于组名 Artifact:项目在组织中的唯一名称 Type:maven项目即可 Language:语言选择java Packaging:打包方式jar包 javaversion:java版本 Version:项目版本(默认即可) Name:项目名称 Description:项目描述 Pageage:项目的基本包名字 4.选择依赖的模块(本案例值选择web模块即可) 5.选择确定项目位置 6.项目目录结构 第一次下载需要等待maven下载相关依赖 ...

June 28, 2020 · 1 min · jiezi

SpringBoot自定义Starter

背景在做SpringBoot开发时,各种starter (场景启动器) 必不可少,它们就像可插拔式的插件,只要在pom文件中引用 springboot 提供的场景启动器, 再进行少量的配置就可以使用相应的功能,但SpringBoot并不能囊括我们的所有使用场景,这时候就需要我们自定义starter来实现定制化功能。 Spring Boot Starter工作原理1.SpringBoot在启动的时候会扫描项目所依赖的JAR包,寻找包含spring.factories文件的JAR包2.读取spring.factories文件获取配置的自动配置类AutoConfiguration3.将自动配置类下满足条件(@ConditionalOnXxx)的@Bean放入到Spring容器中(Spring Context)自定义starter1.创建一个maven工程pom文件 <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.matins.starter</groupId> <artifactId>hello-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>hello-spring-boot-starter</name> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies></project>2.Proprerty类创建类PersonProperties @ConfigurationProperties注解的作用是告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定,prefix 指定配置文件里的前缀,配置了这个注解后会根据配置文件的所有属性进行一一映射 package com.matins.starter.property;import org.springframework.boot.context.properties.ConfigurationProperties;/** * @Description: * @author: wei.wang * @since: 2019/12/18 23:46 * @history: 1.2019/12/18 created by wei.wang */@ConfigurationProperties(prefix = "spring.person")public class PersonProperties { // 姓名 private String name; // 年龄 private int age; // 性别 private String sex = "M"; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "PersonProperties{" + "name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + '}'; }}3.Service类接口PersonService ...

June 27, 2020 · 3 min · jiezi

SpringBoot结合ShardingJDBC实现分库分表

前言:今天来聊下 SpringBoot 集成 Sharding-JDBC 实现分库分表;为此写了一个小Demo,这个Demo是基于SpringBoot,并集成了 Mybatis、Redis、Swagger(生成在线的接口文档 )、PageHelper(分页工具) 等,当然绝对也集成了 Sharding-JDBC ;以及设计了 RestFul 风格的接口 ,添加了 单元测试 。下面简单介绍下本文的主线: ①、首先介绍下Demo的工程目录,并且介绍下使用的基本环境,如:sql、工程的pom.xml等 ②、然后会着重介绍 SpringBoot 集成 Sharding-JDBC 的过程,及 Sharding-JDBC 基本知识 和 注意事项。 1、项目信息描述: 完整项目在gitHub,地址: https://github.com/leishen6/S...如有需要请自己去 giHub 上拉取代码进行查阅,由于本人水品有限,如有问题请留言提出,谢谢! Demo详解:1、工程目录: 2、工程环境:2.1、pom.xml : <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.7</java.version> <mybatis-spring-boot>1.2.0</mybatis-spring-boot> <mysql-connector>5.1.39</mysql-connector> <fastjson>1.2.41</fastjson> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Spring Boot Mybatis 依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <!-- MySQL 连接驱动依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--druid 连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson}</version> </dependency> <!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- pagehelper分页工具 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.1.6</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <!-- sharding-jdbc --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.0.0-RC1</version> </dependency> <!-- hutool 工具类 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-setting</artifactId> <version>5.2.4</version> </dependency> </dependencies>注意:pom.xml 的内容最好不要改动了,因为如果将里面的一些 依赖版本变动了 ,可能会导致依赖版本兼容性问题出现,最终导致程序运行失败。2.2、sql 环境:①、数据库使用的 Mysql,Demo程序运行前需要提前创建好数据库,由于使用了分库分表,所以需要创建两个库; 数据库名:springboot0、springboot1 。 ...

June 20, 2020 · 3 min · jiezi

理解独立的SpringBoot应用

SpringBoot特性 创建独立的Spring应用。直接嵌入Tomcat、Jetty、Undertow等Web容器。提供starter依赖,简化构建配置。条件满足时,自动装配Spring或第三方类库。提供运维特性,如指标、健康检查、外部化配置。无代码生成,不需要XML配置。命令行方式创建SpringBoot 1.使用Maven Archetype插件 mvn archetype:generate -DgroupId=thinking.in.spring.boot -DartifactId=first-spring-boot-application -Dversion=1.0.0-SNAPSHOT -DinteractiveMode=false 2.增加SpringBoot依赖 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>thinking.in.spring.boot</groupId> <artifactId>first-spring-boot-application</artifactId> <packaging>jar</packaging> <version>1.0.0-SNAPSHOT</version> <name>《SpringBoot编程思想》第一个SpringBoot项目</name> <url>http://maven.apache.org</url> <dependencies> <!-- 增加Spring Boot Web依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.0.2.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies></project>3.调整引导类 @RestController@SpringBootApplicationpublic class App { @RequestMapping("/") public String index() { return "welcome, My Buddy!" } public static void main( String[] args ) { SpringApplication.run(App.class, args); }}4.使用Spring Boot Maven插件引导Spring Boot应用,需要在pom.xml中添加如下代码: ...

June 20, 2020 · 1 min · jiezi

ComponentScan-和-SpringBootApplication-同时使用出现问题

业务场景在项目开发过程中,通常需要导入第三方jar包的时候需要加载到ioc容器中.我们就要在启动类Application上使用@ComponentScan 这个注解来扫描第三方的包,然后就会出现报错,异常就是无法加载本地的某些类,例如某些Dao无法注入. 分析问题本地类都没有被扫到ioc容器中,我们可以知道@SpringBootApplication 这个注解包含了@ComponentScan 它就会把启动类所在的包当作根路径,把下边所有符合的类扫描进ioc容器,那么我们现在知道了,就是这个注解的扫描功能失效了. 官方解决方案可以在@ComponentScan 里边把本地的路径给加上,这样就可以解决问题了,即可以扫描第三方jar包又可以扫描本地的路径 解决例子 @ComponentScan({"cn.aaa","com.bbb.cloud"})@SpringBootApplicationpublic class GatewaydemoApplication { public static void main(String[] args) { SpringApplication.run(GatewaydemoApplication.class, args); }}cn.aaa是引入第三方jar包,com.bbb.cloud是启动类所在的根路径.如果有更优雅的解决方案,可以联系我

June 17, 2020 · 1 min · jiezi

你了解SpringBoot-java-jar-的启动原理吗

电话面试中,面试官问了一个问题:你知道 java -jar 启动 Spring Boot 项目,和传统的 jar 有什么不一样的吗? 问题大概是这样,当时不太清楚怎么回答,面试结束之后知道面试估计是挂了,请教了一下面试官这个问题应该从哪方面去考虑呢? 大概记得面试官说,... 自定义类加载器知道吗? ...(中间一些内容就没听进去了) 我:原来是从这方面去考虑呀,感谢面试官的指点! 事后赶紧学了学,也走读了下启动过程的源码,终于知道他说的自定义类加载器了,也就知道他问这个问题的目的所在了。 凡是你接触过一点点 Spring Boot 项目,你一定知道通过 java -jar xxx.jar 命令便能把一个 Spring Boot 服务启动起来。(如果你还没接触过,这里的内容可以日后再看,先轻微了解一下 Spring Boot 项目的玩法) 一个看似简陋的 java -jar 究竟干了什么,就把咱们手写的应用(咱们的项目可能叫 XXXApplication.java)启动了呢? 这就是本文的目的,解读一下 java -jar 都做了什么。 至少面试的时候能搭上话,能说两句,不会像我一样只能哦哦哦的。。。 先有个概览了解一个技术点,直接扎到源码堆里,云里雾里,很难受,容易让人望而生畏。这时候可以先从整体或者非源码的角度了解一下它的运作机制,心里有个底,如果再感兴趣,就可以找一些细节,慢慢击破,可能效果更好,更能让人坚持下去。 这也是我后面准备学习源码的思路,就写一下。 虽然也是这样劝自己,可是还是看不懂,尴尬了,哈哈哈... 咱们就先拿这个java -jar xxx.jar来说: Spring Boot 在可执行 Fat jar 包中定义了自己的一套规则,比如第三方依赖 jar 包在 /lib目录下,jar 包的 URL 路径使用自定义的规则并且这个规则需要使用 org.springframework.boot.loader.jar.Handler 处理器处理。 Fat jar 的 Main-Class 使用 org.springframework.boot.loader.JarLauncher,也就是 执行java -jar xxx.jar首先会触发 JarLauncher的main方法的执行,而不是咱们的应用的xxx.xxx.xxx.XXXApplication。 ...

June 13, 2020 · 5 min · jiezi

SpringBoot基础回顾10

3. SpringBoot数据访问SpringData是Spring提供的一个用于简化数据库访问、支持云服务的开源框架。它是一个伞形项目,包含了大量关系型数据库及非关系型数据库的数据访问解决方案,其设计目的是使我们可以快速且简单地使用各种数据访问技术。Spring Boot默认采用整合SpringData的方式统一处理数据访问层,通过添加大量自动配置,引入各种数据访问模板xxxTemplate以及统一的Repository接口,从而达到简化数据访问层的操作。 Spring Data提供了多种类型数据库支持,对支持的的数据库进行了整合管理,提供了各种依赖启动器,接下来,通过一张表罗列提供的常见数据库依赖启动器,如表所示。 | 名称 | 描述 | | spring-boot-starter-data-jpa | 使用Spring DataJPA与Hibernate | | spring-boot-starter-data-mongodb | 使用MongoDB和Spring Data MongoDB | | spring-boot-starter-data-neo4j | 使用Neo4j图数据库和Spring Data Neo4j | | spring-boot-starter-data-redis | 使用Redis键值数据存储与Spring Data Redis和Jedis客户端 | 除此之外,还有一些框架技术,Spring Data项目并没有进行统一管理, Spring Boot官方也没有提供对应的依赖启动器,但是为了迎合市场开发需求、这些框架技术开发团队自己适配了对应的依赖启动器,例如,mybatis-spring-boot-starter支持MyBatis的使用 3.1 Spring Boot整合MyBatis MyBatis 是一款优秀的持久层框架,Spring Boot官方虽然没有对MyBatis进行整合,但是MyBatis团队自行适配了对应的启动器,进一步简化了使用MyBatis进行数据的操作 因为Spring Boot框架开发的便利性,所以实现Spring Boot与数据访问层框架(例如MyBatis)的整合非常简单,主要是引入对应的依赖启动器,并进行数据库相关参数设置即可 基础环境搭建: 1.数据准备 在MySQL中,先创建了一个数据库springbootdata,然后创建了两个表t_article和t_comment并向表中插入数据。其中评论表t_comment的a_id与文章表t_article的主键id相关联 #创建数据库 CREATEDATABASE springbootdata; #选择使用数据库 USE springbootdata; #创建表t_article并插入相关数据 DROPTABLE IF EXISTS t_article; CREATE TABLE t_article ( id int(20) NOT NULL AUTO_INCREMENT COMMENT '文章id', title varchar(200) DEFAULT NULL COMMENT '文章标题', content longtext COMMENT '文章内容', PRIMARY KEY (id) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULTCHARSET=utf8; INSERTINTO t_article VALUES ('1', 'Spring Boot基础入门', '从入门到精通讲解...'); INSERTINTO t_article VALUES ('2', 'Spring Cloud基础入门', '从入门到精通讲解...'); # 创建表t_comment并插入相关数据 DROPTABLE IF EXISTS t_comment; CREATETABLE t_comment ( id int(20) NOT NULL AUTO_INCREMENT COMMENT '评论id', content longtext COMMENT '评论内容', author varchar(200) DEFAULT NULL COMMENT '评论作者', a_id int(20) DEFAULT NULL COMMENT '关联的文章id', PRIMARY KEY (id) )ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; INSERTINTO t_comment VALUES ('1', '很全、很详细', 'luccy', '1'); INSERTINTO t_comment VALUES ('2', '赞一个', 'tom', '1'); INSERTINTO t_comment VALUES ('3', '很详细', 'eric', '1'); INSERTINTO t_comment VALUES ('4', '很好,非常详细', '张三', '1'); INSERTINTO t_comment VALUES ('5', '很不错', '李四', '2'); 2. 创建项目,引入相应的启动器 ...

June 11, 2020 · 2 min · jiezi

SpringBoot基础回顾11

Spring Boot整合JPA (1)添加Spring DataJPA依赖启动器。在项目的pom.xml文件中添加SpringData JPA依赖启动器,示例代码如下 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency>(2)编写ORM实体类。 @Entity(name = "t_comment") // 设置ORM实体类,并指定映射的表名public class Comment { @Id // 表明映射对应的主键id @GeneratedValue(strategy = GenerationType.IDENTITY) // 设置主键自增策略 private Integer id; private String content; private String author; @Column(name = "a_id") //指定映射的表字段名 private Integer aId; // 省略属性getXX()和setXX()方法 // 省略toString()方法 }(3)编写Repository接口:CommentRepository public interface CommentRepository extendsJpaRepository<Comment,Integer> { }(4)测试 @Autowired private CommentRepository repository; @Test public void selectComment() { Optional<Comment> optional = repository.findById(1); if(optional.isPresent()){ System.out.println(optional.get()); } System.out.println(); }3.3Spring Boot整合Redis ...

June 11, 2020 · 1 min · jiezi

SpringBoot基础回顾8

2.2 自动配置(启动流程)概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置,我们无需配置或者只需要少量配置就能运行编写的项目 问题:Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置? Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法, @SpringBootApplication能够扫描Spring组件并自动配置Spring Boot 下面,查看@SpringBootApplication内部源码进行分析 ,核心代码具体如下 @SpringBootApplicationpublic class SpringbootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootDemoApplication.class, args); }}@Target({ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中@Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时@Documented //表示注解可以记录在javadoc中@Inherited //表示可以被子类继承该注解@SpringBootConfiguration // 标明该类为配置类@EnableAutoConfiguration // 启动自动配置功能@ComponentScan( // 包扫描器 excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class}), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class})})public @interface SpringBootApplication {...} 从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据信息, 我们主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解,关于这三个核心注解的相关说明具体如下: 1.@SpringBootConfiguration注解 @SpringBootConfiguration注解表示Spring Boot配置类。查看@SpringBootConfiguration注解源码,核心代码具体如下。 @Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Configuration //配置IOC容器public @interface SpringBootConfiguration {} 从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已 ...

June 10, 2020 · 2 min · jiezi

SpringBoot基础回顾9

SpringBoot starter机制 SpringBoot由众多Starter组成(一系列的自动化配置的starter插件),SpringBoot之所以流行,也是因为starter。 starter是SpringBoot非常重要的一部分,可以理解为一个可拔插式的插件,正是这些starter使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Spring Boot自动通过classpath路径下的类发现需要的Bean,并织入相应的Bean。 例如,你想使用Reids插件,那么可以使用spring-boot-starter-redis;如果想使用MongoDB,可以使用spring-boot-starter-data-mongodb 为什么要自定义starter 开发过程中,经常会有一些独立于业务之外的配置模块。如果我们将这些可独立于业务代码之外的功能配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配 自定义starter的命名规则 SpringBoot提供的starter以spring-boot-starter-xxx的方式命名的。官方建议自定义的starter使用xxx-spring-boot-starter命名规则。以区分SpringBoot生态提供的starter 整个过程分为两部分: 自定义starter使用starter首先,先完成自定义starter (1)新建maven jar工程,工程名为zdy-spring-boot-starter,导入依赖: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.2.2.RELEASE</version> </dependency></dependencies>(2)编写javaBean @EnableConfigurationProperties(SimpleBean.class)@ConfigurationProperties(prefix ="simplebean") public class SimpleBean { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "SimpleBean{" + "id=" + id + ", name='" + name +'\'' + '}'; }}(3)编写配置类MyAutoConfiguration ...

June 10, 2020 · 2 min · jiezi

Spring-Cloud-Zuul-实践三-过滤器

在设计系统阶段,一定记住Zuul的核心思想:Zuul是做服务的路由,而不是页面的转发。这是zuul与Nginx根本上的不同。 Zuul没有提供良好的跳转或转发功能,这和它的使用场景是有关的。它的核心功能包括验证服务权限,验证成功或失败,成功或失败后放行还是拦截等等。若拦截,只需返回响应码即可,至于成功或失败的页面显示,则应该由前端负责跳转或转发。请一定遵守标准化的设计,不要让Zuul参与页面跳转(当然,Zuul也可以实现页面跳转,本文后半部分会涉及到,但不推荐由Zuul实现跳转) Zuul的验证功能基本在它的过滤器中实现。 过滤器类型与请求的生命周期每一个请求发送到Zuul,都有其对应的不同阶段,可以称其为“生命周期”,比如初始化阶段,处理请求阶段,结束阶段,出error阶段等等。 在谈下一话题前,请一定分清楚“转发”和“路由”的区别。如果说定义,路由是一种策略,转发是一条路径。说浅显一点,路由是网状结构下的各种路径解的集合,而转发是点对点的一条通信路径。再浅显一些,就像学生年代你上课时给女神小红递纸条,路由是你将纸条给你同桌(代理),你同桌会选择一条消息发送的路径,比如将纸条传递给A,再到B,再到C,最后到小红。由同桌发到小红的整个过程就叫路由。你只需要将纸条给同桌(代理),剩下的不需要你参与,你的同桌会选择最优路径,无论是通过ABC,还是BAC,当纸条传递成功,你的代理会反馈给你结果(同桌会告诉你纸条传递成功,小红看都没看就扔进垃圾桶),你不需要知道中间隔了多少人。这就是路由。而转发,就是你直接把纸条揉成团,扔给小红,小红收到后含羞一笑,你看到后心花怒放。这就是转发。最浅显的概括就是: 路由是多跳的转发,转发的一跳的路由。这一点从英文routing和forwading的区别就可以明白。如果还无法理解,请重新读一遍计算机网络。 对于微服务项目来说,Zuul实现的就是路由功能,往往也被会称做路由转发、网关等等,但意思都是一样的。本文在谈Zuul的拦截器,每一个request在发送到Zuul后,都有几个不同的阶段(生命周期),Zuul的几个拦截器对应request的不同阶段,所以我们先谈一下request的生命周期: 请求刚刚被Zuul接收到,Zuul需要解析请求的地址,以及其请求参数等等。请求即将被路由。Zuul已经确定此请求该发送至哪一个service,并已准备好将其发送过去。请求被路由至目标服务请求在以上三个阶段中的某一个出错。以上四个阶段是request的生命周期,Zuul的四种拦截器分别对应以上的四个request生命周期: PRE, ROUTING, POST, ERROR. 所以,如果想要对request的某个生命周期进行操作(过滤),只需要按需实现这四种拦截器即可。 那么,如何实现这四种拦截器呢?依然超级简单,答案只有一个:继承ZuulFilter类并覆写父类方法即可。区分拦截器的类型在方法filterType()中。闲言少叙,上代码。以下是一个最基本的PRE拦截器。 实现拦截器定义新建一个类,继承ZuulFilter,并覆写其方法。 public class PreRequestFilter extends ZuulFilter { private static final Logger logger = LoggerFactory.getLogger(RouteRequestFilter.class); // 表示此拦截器类型, pre、route、post、error @Override public String filterType() { return "pre"; } // 执行顺序,若有多个同种拦截器,比如有多个pre拦截器,他们的执行顺序是什么 @Override public int filterOrder() { return 0; } // 是否应用这个拦截器,true是应用,false表示这个拦截器是失效的 @Override public boolean shouldFilter() { return false; } // 此方法为核心方法,表示拦截器中执行的逻辑 @Override public Object run() throws ZuulException { System.out.println("pre"); return null; }}注册为Bean在主类中将此拦截器注册为Bean ...

June 9, 2020 · 1 min · jiezi

SpringBoot基础回顾7

## 2. SpringBoot原理深入及源码剖析         传统的Spring框架实现一个Web服务,需要导入各种依赖JAR包,然后编写对应的XML配置文件等,相较而言,Spring Boot显得更加方便、快捷和高效。那么,Spring Boot究竟如何做到这些的呢? 接下来分别针对Spring Boot框架的依赖管理、自动配置和执行流程进行深入分析 ### 2.1 依赖管理       问题:(1)为什么导入dependency时不需要指定版本?         在Spring Boot入门程序中,项目pom.xml文件有两个核心依赖,分别是spring-boot-starter-parent和spring-boot-starter-web,关于这两个依赖的相关介绍具体如下: **1.spring-boot-starter-parent依赖** 在chapter01项目中的pom.xml文件中找到spring-boot-starter-parent依赖,示例代码如下: ```xml <!-- Spring Boot父项目依赖管理 -->       <parent>               <groupId>org.springframework.boot</groupId>               <artifactId>spring-boot-starter-parent<11./artifactId>               <version>2.2.2.RELEASE</version>               <relativePath/> ...

June 9, 2020 · 2 min · jiezi

SpringBoot整合Swagger2轻松加愉快

事情是这样的,前两天同事跟我们分享了一下他在学习SpringBoot的时候,用了Swagger2,生成的接口文档美如画,我甚是心动,于是乎,我也捣鼓了一下,感觉一下子就起飞了。话不多说,开搞~ 添加依赖在SpringBoot项目中加入两个Swagger2相关的依赖,前提是需要加入spring-boot-starter-web依赖,如下: <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>Swagger2 配置@Configuration@EnableSwagger2public class SwaggerConfig { @Bean public Docket swaggerSpringMvcPlugin() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select() .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)).paths(PathSelectors.any()).build() } // 构建 api文档的详细信息函数 private ApiInfo apiInfo() { Contact contact = new Contact("我的团队", "www.my.com", "my@my.com"); return new ApiInfoBuilder().title("RESTful API") // 页面标题 .description("这里是api的描述内容") // 描述 .contact(contact) // 创建人 .version("1.0.1") // 版本号 .build(); }}非常简单的配置,此时启动项目,输入 http://localhost:8080/swagger-ui.html,能够看到如下页面,说明已经配置成功了: 配置接口@RestController@RequestMapping("user")@Api(tags = "用户管理相关接口")public class UserController { @Autowired private UserService userService; @GetMapping("/getUserById") @ApiOperation("获取单个用户的接口") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "用户id", defaultValue = "2", required = true) }) public User getUserById(Integer id) { return this.userService.getUserById(id); } @GetMapping("/getUserList") @ApiOperation("获取全部用户的接口") public List<User> getUserList() { return this.userService.getUserList(); }}这里边涉及到多个 API,分别说明一下: ...

June 9, 2020 · 2 min · jiezi

基于dactor和SpringBoot的多域名的博客系统内置20多套主题

dpress基于多域名的博客系统 基于Halo 博客系统改造 简介: 基于dactor和SpringBoot系统构建。为了方便对不同类型的博客分别管理,想用多域名进行管理,市面上面的博客找了一下,未发现基于Java的多域名博客,所以才开发了此系统。代码正在进行快速迭代,有问题,请及时提出。 项目地址GitHub:https://github.com/allon2/dpress GitEE:https://gitee.com/handyun/dpress dpress1.0 beta版本1:支持多域名 2:模板使用数据库存储 3:可在线配置数据库 4:基于dactor框架开发,前端交易和管理端交易都基于异步进行开发 5:兼容halo模板 6:支持一键安装,在线配置数据库地址 7:支持瞬时高并发 8:支持war方式和Jar方式部署 9:支持MarkDown和Html编辑模式 10:内置20多套主题,总有一套适合你 如果喜欢,请多多分享!!多多Star!! 技术框架框架说明官网Spring Framework轻量级(相对而言)的Java开发框架https://spring.io/projects/sp...Spring BootJava Web开发脚手架https://spring.io/projects/sp...Freemarker视图模板引擎https://freemarker.apache.orgFastJSONJSON解析库FastJsonlombok代码生成器https://projectlombok.orgDruid数据库链接池 Dactor基于协程的简单易用的编程框架https://github.com/allon2/dactorjetcache缓存框架https://github.com/alibaba/je...MybatisORM框架https://mybatis.org/mybatis-3/Vue一套构建用户界面的渐进式框架https://vuejs.org/功能列表仪表盘文章管理页面管理附件管理评论管理主题管理,系统自带两套主题模板主题编辑用户信息系统管理编译源代码 如果你是直接下载项目war包,请跳过此步骤。代码克隆到本地后,你可以使用命令行工具或者IDEA对项目源码进行编译,命令如下: gradle clean build快速开始下载最新的 Dpress 安装包curl -L $(curl -s https://api.github.com/repos/allon2/dpress/releases/latest | grep 'browser_' | cut -d\" -f4) --output dpress.war或者 wget $(curl -s https://api.github.com/repos/allon2/dpress/releases/latest | grep 'browser_' | cut -d\" -f4) -O dpress.war启动 Dpressjava -jar dpress.war安装步骤1:浏览器中输入http://localhost:8090 2:填写数据库信息 3:填写个人信息 4:填写博客信息 5:安装完成后,会自动跳转到管理端页面。6:管理端首页 7:管理端功能

June 9, 2020 · 1 min · jiezi

Springboot-系列五web-开发之静态资源和模版引擎

文章已经收录在 Github.com/niumoo/JavaNotes ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 欢迎关注我的公众号,文章每周更新。前言Spring Boot 天生的适合 web 应用开发,它可以快速的嵌入 Tomcat, Jetty 或 Netty 用于包含一个 HTTP 服务器。且开发十分简单,只需要引入 web 开发所需的包,然后编写业务代码即可。 自动配置原理?在进行 web 开发之前让我再来回顾一下自动配置,可以参考系列文章第三篇。Spring Boot 为 Spring MVC 提供了自动配置,添加了如下的功能: 视图解析的支持。静态资源映射,WebJars 的支持。转换器 Converter 的支持。自定义 Favicon 的支持。等等<!-- more -->在引入每个包时候我们需要思考是如何实现自动配置的,以及我们能自己来配置哪些东西,这样开发起来才会得心应手。 关于 Spring Boot Web 开发的更详细介绍可以参考官方文档。 1. JSON 格式转换Spring Boot 默认使用 Jackson 进行 JSON 化处理,如果想要切换成 FastJson 可以首先从官方文档里查询信息。从这里知道对于 ResponseBody 的渲染主要是通过 HttpMessageConverters, 而首先引入FastJson Pom依赖并排除 Spring Boot 自带的 Jackson。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-json</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions></dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version></dependency>编写转换器处理 json 的日期格式同时处理中文乱码问题。 ...

June 9, 2020 · 3 min · jiezi

SpringBoot基础回顾4

1.6配置文件属性值的注入**使用Spring Boot全局配置文件设置属性时:如果配置属性是Spring Boot已有属性,例如服务端口server.port,那么Spring Boot内部会自动扫描并读取这些配置文件中的属性值并覆盖默认属性。如果配置的属性是用户自定义属性,例如刚刚自定义的Person实体类属性,还必须在程序中注入这些配置属性方可生效。Spring Boot支持多种注入配置文件属性的方式,下面来介绍如何使用注解@ConfigurationProperties和@Value注入属性1.6.1使用@ConfigurationProperties注入属性Spring Boot提供的@ConfigurationProperties注解用来快速、方便地将配置文件中的自定义属性值批量注入到某个Bean对象的多个对应属性中。假设现在有一个配置文件,如果使用@ConfigurationProperties注入配置文件的属性,示例代码如下: @Component @ConfigurationProperties(prefix ="person") public class Person { private int id; // 属性的setXX()方法 public void setId(int id) { this.id = id; }}1234567891011121314151617181920212223上述代码使用@Component和@ConfigurationProperties(prefix= “person”)将配置文件中的每个属性映射到person类组件中。需要注意的是,使用@ConfigurationProperties1.6.2 使用@Value注入属性@Value注解是Spring框架提供的,用来读取配置文件中的属性值并逐个注入到Bean对象的对应属性中,Spring Boot框架从Spring框架中对@Value注解进行了默认继承,所以在Spring Boot框架中还可以使用该注解读取和注入配置文件属性值。使用@Value注入属性的示例代码如下 @Component public class Person { @Value("${person.id}") private int id; }1234567891011上述代码中,使用@Component和@Value注入Person实体类的id属性。其中,@Value不仅可以将配置文件的属性注入Person的id属性,还可以直接给id属性赋值,这点是@ConfigurationProperties不支持的演示@Value注解读取并注入配置文件属性的使用: (1)在com.lagou.pojo包下新创建一个实体类Student,并使用@Value注解注入属性 @Component public class Student { @Value("${person.id}") private int id; @Value("${person.name}") private String name; //名称 //省略toString }1234567891011121314151617181920212223242526Student类使用@Value注解将配置文件的属性值读取和注入。从上述示例代码可以看出,使用@Value注解方式需要对每一个属性注入设置,同时又免去了属性的setXX()方法 (2)再次打开测试类进行测试 @Autowired private Student student; @Test public void studentTest() { System.out.println(student);}123456789101112打印结果:可以看出,测试方法studentTest()运行成功,同时正确打印出了Student实体类对象。需要说明的是,本示例中只是使用@Value注解对实例中Student对象的普通类型属性进行了赋值演示,而@Value注解对于包含Map集合、对象以及YAML文件格式的行内式写法的配置文件的属性注入都不支持,如果赋值会出现错误*学习让人快乐,学习更让人觉得无知!学了1个多月的《Java工程师高薪训练营》,才发现自己对每个技术点的认知都很肤浅,根本深不下去,立个Flag:每天坚持学习一小时,一周回答网上3个技术问题,把自己知道都分享出来,奥利给

June 8, 2020 · 1 min · jiezi

一springboot学习笔记

1.了解springboot什么是springboot为什么要学习springbootspringboot的特点1.1 什么是springbootSpring Boot 推崇约定大于配置的方式以便于你能够尽可能快速的启动并运行程序。人们常把Spring Boot 称为搭建程序的脚手架。其最主要作用就是帮我们快速的构建庞大的spring项目,并且尽可能的减少一切xml配置,做到开箱即用,迅速上手,让我们关注与业务而非配置。 1.2 为什么要学习springbootjava一直被人诟病的一点就是臃肿、麻烦。 复杂的配置 项目各种配置其实是开发时的损耗, 因为在思考 Spring 特性配置和解决业务问题之间需要进行思维切换,所以写配置挤占了写应用程序逻辑的时间。一个是混乱的依赖管理 项目的依赖管理也是件吃力不讨好的事情。决定项目里要用哪些库就已经够让人头痛的了,你还要知道这些库的哪个版本和其他库不会有冲突,这难题实在太棘手。并且,依赖管理也是一种损耗,添加依赖不是写应用程序代码。一旦选错了依赖的版本,随之而来的不兼容问题毫无疑问会是生产力杀手。 而SpringBoot让这一切成为过去!Spring Boot 简化了基于Spring的应用开发,只需要“run”就能创建一个独立的、生产级别的Spring应用。Spring Boot为Spring平台及第三方库提供开箱即用的设置(提供默认设置,存放默认配置的包就是启动器),这样我们就可以简单的开始。多数Spring Boot应用只需要很少的Spring配置。 1.3 springboot的特点springboot的主要特点 为所有的spring开发者提供了一个非常快速的,广泛接受的入门体验。开箱即用(启动器starter-其实就是SpringBoot提供的一个jar包),但通过自己设置参数(.properties),即可快速摆脱这种方式提供了一些大型项目中常见的非功能性特性,如内嵌服务器、安全、指标,健康检测、外部化配置等绝对没有代码生成,也无需 XML 配置2.java配置主要任务:如何去掉xml配置 2.1 回顾历史事实上,在Spring3.0开始,Spring官方就已经开始推荐使用java配置来代替传统的xml配置了 Spring1.0时代 在此时因为jdk1.5刚刚出来,注解开发并未盛行,因此一切Spring配置都是xml格式,想象一下所有的bean都用xml配置,细思极恐啊Spring2.0时代 Spring引入了注解开发,但是因为并不完善,因此并未完全替代xml,此时的程序员往往是把xml与注解进行结合,貌似我们之前都是这种方式Spring3.0及以后 3.0以后Spring的注解已经非常完善了,因此Spring推荐大家使用完全的java配置来代替以前的xml,不过似乎在国内并未推广盛行。然后当SpringBoot来临,人们才慢慢认识到java配置的优雅2.2 尝试java配置java配置主要靠java类和一些注解,比较常用的注解有: @Configuration:声明一个类作为配置类,代替xml文件@Bean:声明在方法上,将方法的返回值加入Bean容器,代替`<bean>`标签@value:属性注入@PropertySource:指定外部属性文件我们接下来用java配置来尝试实现连接池配置:步骤一:首先引入Druid连接池依赖: <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.6</version></dependency>步骤二:创建一个jdbc.properties文件,编写jdbc属性 jdbc.driverClassName=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://127.0.0.1:3306/demojdbc.username=rootjdbc.password=123步骤三:然后编写代码 @Configuration@PropertySource("classpath:jdbc.properties")public class JdbcConfig { @Value("${jdbc.url}") String url; @Value("${jdbc.driverClassName}") String driverClassName; @Value("${jdbc.username}") String username; @Value("${jdbc.password}") String password; @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(url); dataSource.setDriverClassName(driverClassName); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; }}步骤四:测试 ...

June 8, 2020 · 1 min · jiezi

SpringBoot源码解析-LoggingEnvironment启动

SpringBoot深入理解 -- @AliasFor注解的作用SpringBoot源码解析 -- SpringBoot启动过程SpringBoot源码解析 -- AutoConfigure的实现原理SpringBoot源码解析 -- @ComponentScan的实现原理SpringBoot源码解析 -- @Value,@Autowired实现原理SpringBoot源码解析 -- Tomcat,SpringMVC启动SpringBoot源码解析 -- Logging,Environment启动 本文通过阅读SpringBoot源码,分享SpringBoot中Logging,Environment组件的启动过程。 如果大家在使用SpringBoot过程中,遇到日志配置无效,Environment中获取属性错误,希望本文可以给你们一个解决问题的思路。源码分析基于spring boot 2.1 LoggingLogging组件通过ApplicationListener启动,对应的处理类为LoggingApplicationListener(spring-boot.jar中的spring.factories配置了)LoggingApplicationListener#onApplicationStartingEvent private void onApplicationStartingEvent(ApplicationStartingEvent event) { // #1 this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader()); this.loggingSystem.beforeInitialize();}#1 根据应用引入的日志框架,加载对应的日志框架LoggingSystemLoggingSystem#get public static LoggingSystem get(ClassLoader classLoader) { String loggingSystem = System.getProperty(SYSTEM_PROPERTY); if (StringUtils.hasLength(loggingSystem)) { if (NONE.equals(loggingSystem)) { return new NoOpLoggingSystem(); } return get(classLoader, loggingSystem); } return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader)) // #1 .map((entry) -> get(classLoader, entry.getValue())).findFirst() // #2 .orElseThrow(() -> new IllegalStateException("No suitable logging system located")); }#1 LoggingSystem#SYSTEMS中存放了SpringBoot中Logback,Log4j2,Java Util Logging几个日志框架适配器的路径,检查这些类适配器是否存在以判断这些日志框架是否引入#2 构造LoggingSystem,取第一个结果。 ...

June 7, 2020 · 2 min · jiezi

linux性能监控工具WGCLOUD功能使用说明书

一、系统简介WGCLOUD基于java语言开发,是微服务架构的分布式监控系统,核心模块包括:服务器集群监控,ES集群状态监控,CPU监控,内存监控,数据监控,服务心跳检测,应用进程管理,磁盘IO监控,系统负载监控,监控告警信息推送。比起zabbix监控工具,它更轻量,界面更友好,可轻松支持数百台主机监控。 二、主要功能软件登录登录:输入账号和密码登录。 仪表盘登陆后显示主机的监控面板信息,显示信息包括:监控进程数量,数据源个数,监控数据表数量,数据表总数据量,服务接口数量,异常服务接口数量,系统日志数据量。 饼图显示监控主机数量,以及根据性能指标进行分类,柱状图显示最新监控的数据表数量。 主机管理打开主机管理,可以看到所有主机上报的信息,主机不能直接添加,全部通过agent自动上报来收集和处理。 点击删除,可以删除主机,如果agent继续上报信息,则列表还会出现主机信息。 点击备注,可以对主机进行别名处理。 点击系统信息,可以查看系统信息,如cpu个数,型号,系统类型,磁盘信息等。 点击图表,可以查看内存,cpu,系统负载,网络流量的变化趋势折线图。 CPU监控图表在左侧菜单,打开主机管理列表,点击列表右侧的图表按钮,可以查看CPU监控图形报表。 内存监控图表在左侧菜单,打开主机管理列表,点击列表右侧的图表按钮,可以查看内存监控图形报表。 系统负载监控图表在左侧菜单,打开主机管理列表,点击列表右侧的图表按钮,可以查看系统负载监控图形报表。 网络流量监控图表在左侧菜单,打开主机管理列表,点击列表右侧的图表按钮,可以查看网络流量监控图形报表。 网络接收发送包监控图表在左侧菜单,打开主机管理列表,点击列表右侧的图表按钮,可以查看网络接收和发送包数量监控图形报表。 进程列表进程管理,是指对主机上的应用进行监控,可以对应用的使用资源,如内存和cpu,进行实时监控。 点击图表,可以查看进程一段时间的资源占用信息。 点击添加,选择主机,并输入进程id,然后保存。 进程添加在进程列表,点击添加,跳转到添加进程页面,选择监控主机,输入进程id,进程名称等信息,然后点击保存。 数据源列表单击页面的数据监控按钮,可进行相应操作,在数据源列表,可以看到已添加的数据源信息。 数据源添加单击数据源列表页面的添加按钮,可进行添加数据源操作,在如下所示页面需要对其进行相应信息进行输入,包括对数据库类型,用户名,密码,端口,名称等。 数据表列表单击页面的数据表管理按可打开对应页面可对其进行相应信息进行操作,下图为列表。 数据表添加单击页面的数据表管理按可打开对应页面可对其进行相应信息进行操作,添加页面,选择数据源,表名称,表别名,最后输入查询的WHERE条件。 服务接口管理在页面点击服务接口管理,可以添加服务接口,添加成功后,对服务接口进行定时扫描,检测服务接口是否可达。 日志信息在页面点击日志信息,可以查看系统运行中,产生的错误信息,如告警信息,程序错误信息,日志只能查看,无法编辑和删除。 邮件预警单击页面的邮件预警按钮,可对其进行信息查看。邮件预警是用来配置告警信息发送的目标邮箱和发送邮箱,目前只支持邮件发送。 公众看板打开对应的看板页面可对其进行信息查看,公众看板是对游客开放,系统默认是开启看板服务的,可以随时在配置文件关闭,公众看板无需登陆,可以浏览主机的监控信息,但无法进行编辑,只能浏览。 Grafana 主要特性:灵活丰富的图形化选项;可以混合多种风格;支持白天和夜间模式;多个数据源;Graphite 和 InfluxDB 查询编辑器等等。 因为zabbix本身自带的图形比较少,不能满足我们的需求。所以,我们可以安装grafana来配合zabbix出图,让数据更加直观、形象地体现出来。 退出操作完成,可以点击右上角退出按钮,退出系统。

June 6, 2020 · 1 min · jiezi

SpringBoot-starter机制

SpringBoot starter机制 SpringBoot由众多Starter组成(一系列的自动化配置的starter插件),SpringBoot之所以流行,也是因为starter。 starter是SpringBoot非常重要的一部分,可以理解为一个可拔插式的插件,正是这些starter使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Spring Boot自动通过classpath路径下的类发现需要的Bean,并织入相应的Bean。 例如,你想使用Reids插件,那么可以使用spring-boot-starter-redis;如果想使用MongoDB,可以使用spring-boot-starter-data-mongodb 为什么要自定义starter 开发过程中,经常会有一些独立于业务之外的配置模块。如果我们将这些可独立于业务代码之外的功能配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配 这些内容,是从拉勾教育的《Java工程师高薪训练营》里学到的,课程内容非常全面,还有拉勾的内推大厂服务,推荐你也看看。

June 5, 2020 · 1 min · jiezi

自定义starter

自定义starter的命名规则 SpringBoot提供的starter以`spring-boot-starter-xxx`的方式命名的。官方建议自定义的starter使用`xxx-spring-boot-starter`命名规则。以区分SpringBoot生态提供的starter 整个过程分为两部分: - 自定义starter - 使用starter 首先,先完成自定义starter (1)新建maven  jar工程,工程名为zdy-spring-boot-starter,导入依赖: xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.2.2.RELEASE</version> </dependency> </dependencies> (2)编写javaBean java @EnableConfigurationProperties(SimpleBean.class) @ConfigurationProperties(prefix = "simplebean") public class SimpleBean { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "SimpleBean{" + ...

June 5, 2020 · 1 min · jiezi

六-SpringBoot起飞之路整合JdbcTemplateDruidMyBatisRedis

有兴趣的朋友可以去了解一下前五篇,你的赞就是对我最大的支持,感谢大家! (一) SpringBoot起飞之路-HelloWorld (二) SpringBoot起飞之路-入门原理分析 (三) SpringBoot起飞之路-YAML配置小结(入门必知必会) (四) SpringBoot起飞之路-静态资源处理 (五) SpringBoot起飞之路-Thymeleaf模板引擎 说明: SpringBoot 起飞之路 系列文章的源码,均同步上传到 github 了,有需要的小伙伴,随意去 down https://github.com/ideal-20/S...才疏学浅,就会点浅薄的知识,大家权当一篇工具文来看啦,不喜勿愤哈 ~引言前面分别介绍了一下一些入门的配置和基础,同时引入了Thymeleaf模板引擎,而练习一些小 Demo 就要开始涉及到数据了,所以今天来讲一下如何在 SpringBoot 中整合常见数据相关的一些技术:JdbcTemplate、Druid、MyBatis,重点熟悉下后两者,用的也是比较多的 这一篇所介绍的内容,都不是新内容,不涉及太多的语法,关键是整合,关于这三样介绍也就简单提一下 最后开始之前,还有一个需要提及的 SpringBoot 中关于数据库相关的处理,均使用 Spring Data,它是 Spring 全家桶中的一个子项目,能同时支持关系/非关系型数据库的操作,能够极大地简化数据库访问的操作 更多内容,可以去看一下官网: https://spring.io/projects/spring-data (一) 整合 JdbcTemplate虽然叫做 整合 JdbcTemplate,但本质上还是整合 JDBC ,只不过 JdbcTemplate 对原生 JDBC 进行了一些简化 (1) 引入依赖我们首先创建一个 Project 或者 Module,然后初始化一个 SpringBoot 项目,除了基本的 Web 以外,再左侧 SQL 选项中选择 JDBC API ,这也就是引入关于整合 JDBC 的依赖 来看一下 pom,也就是引入了 spring-boot-starter-jdbc 这个启动器,其中一些依赖封装好了 ...

June 5, 2020 · 4 min · jiezi

SpringBoot-案例详解

1.3 SpringBoot 案例实现 案例需求:请求Controller中的方法,并将返回值响应到页面 (1)使用Spring Initializr方式构建Spring Boot项目 本质上说,Spring Initializr是一个Web应用,它提供了一个基本的项目结构,能够帮助我们快速构建一个基础的Spring Boot项目 Project SDK”用于设置创建项目使用的JDK版本,这里,使用之前初始化设置好的JDK版本即可;在“Choose Initializr Service URL(选择初始化服务地址)”下使用默认的初始化服务地址“https://start.spring.io”进行Spring Boot项目创建(注意使用快速方式创建Spring Boot项目时,所在主机须在联网状态下) Spring Boot项目就创建好了。 使用Spring Initializr方式构建的Spring Boot项目会默认生成项目启动类、存放前端静态资源和页面的文件夹、编写项目配置的配置文件以及进行项目单元测试的测试类 (2) 创建一个用于Web访问的Controller com.lagou包下创建名称为controller的包,在该包下创建一个请求处理控制类HelloController,并编写一个请求处理方法 @RestController // 该注解为组合注解,等同于Spring中@Controller+@ResponseBody注解 public class DemoController { @RequestMapping("/demo")   public String demo(){  return "你好 spring Boot"; } } (3)  运行项目 运行主程序启动类SpringbootDemoApplication,项目启动成功后,在控制台上会发现Spring Boot项目默认启动的端口号为8080,此时,可以在浏览器上访问“http://localhost:8080/hello” 页面输出的内容是“hello Spring Boot”,至此,构建Spring Boot项目就完成了 附:解决中文乱码: 解决方法一: @RequestMapping(produces = "application/json; charset=utf-8") 解决方法二: properties 设置响应为utf-8spring.http.encoding.force-response=true 刚学了拉勾教育的《Java工程师高薪训练营》,看到刚学到的点就回答了。希望拉勾能给我推到想去的公司,目标:字节!!

June 4, 2020 · 1 min · jiezi

Springboot-系列二Spring-Boot-配置文件全解析

文章已经收录在 Github.com/niumoo/JavaNotes ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 欢迎关注我的公众号,文章每周更新。注意:本 Spring Boot 系列文章基于 Spring Boot 版本 v2.1.1.RELEASE 进行学习分析,版本不同可能会有细微差别。前言 不管是通过官方提供的方式获取 Spring Boot 项目,还是通过 IDEA 快速的创建 Spring Boot 项目,我们都会发现在 resource 有一个配置文件 application.properties,也有可能是application.yml.这个文件也就是 Spring Boot 的配置文件。 <!-- more --> 1. YAML 文件在 Spring Boot 中,官方推荐使用 properties 或者 YAML 文件来完成配置,对于 YAML 文件格式还不了解的可以查看官方的具体格式,这里只做简单介绍。 YAML 语法规则: 大小写敏感缩进表示层级缩进只能使用空格空格的数量不重要,但是相同层级的元素要左侧对齐 # 开头的行表示注释YAML 支持的数据结构: 1.单纯的变量,不可再分的单个的值,如数字,字符串等。 name: Darcy age: 12 # ~表示NULL值 email: ~ # 多行字符串可以使用|保留换行符,也可以使用>折叠换行。 # +表示保留文字块末尾的换行,-表示删除字符串末尾的换行。 message:|- Hello world2.数组,一组按次序排列的值。 ...

June 4, 2020 · 3 min · jiezi

Springboot-系列四Spring-Boot-日志框架全解析

文章已经收录在 Github.com/niumoo/JavaNotes ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 欢迎关注我的公众号,文章每周更新。注意:本 Spring Boot 系列文章基于 Spring Boot 版本 v2.1.1.RELEASE 进行学习分析,版本不同可能会有细微差别。 前言 Spring 框架选择使用了 JCL 作为默认日志输出。而 Spring Boot 默认选择了 SLF4J 结合 LogBack。那我们在项目中该使用哪种日志框架呢?在对于不同的第三方 jar 使用了不同的日志框架的时候,我们该怎么处理呢?<!-- more --> 1. 日志框架介绍日志对于应用程序的重要性不言而喻,不管是记录运行情况还是追踪线上问题,都离不开对日志的分析,在 Java 领域里存在着多种日志框架,如 JUL, Log4j, Log4j2, Commons Loggin, Slf4j, Logback 等。关于 Log4j, Log4j2 和 Slf4j 直接的故事这里不做介绍,有兴趣可以自行百度。 2. SLF4 的使用在开发的时候不应该直接使用日志实现类,应该使用日志的抽象层。具体参考 SLF4J 官方。下图是 SLF4J 结合各种日志框架的官方示例,从图中可以清晰的看出 SLF4J API 永远作为日志的门面,直接应用与应用程序中。 同时 SLF4 官方给出了简单示例。 import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class HelloWorld { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(HelloWorld.class); logger.info("Hello World"); }}需要注意的是,要为系统导入 SLF4J 的 jar 和 日志框架的实现 jar. 由于每一个日志的实现框架都有自己的配置文件,所以在使用 SLF4 之后,配置文件还是要使用实现日志框架的配置文件。 ...

June 4, 2020 · 2 min · jiezi

Jwt生成解析token封装工具类

Json web token导入jjwt依赖 <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>生成token,使用工具类Jwts的builder()方法,完成用户验证后返回token给客户端public class CreateJwt { public static void main(String[] args) { JwtBuilder jwtBuilder = Jwts.builder().setId("88").setSubject("小白") .setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256, "liang") //添加非私密的其它内容 .claim("companyId","123456") .claim("companyName","腾讯"); String token = jwtBuilder.compact(); System.out.println(token); }}用户发送请求是携带token,解析tokenpublic class ParseJwt { public static void main(String[] args) { String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4OCIsInN1YiI6IuWwj-eZvSIsImlhdCI6MTU5MTE4ODgyNiwiY29tcGFueUlkIjoiMTIzNDU2IiwiY29tcGFueU5hbWUiOiLohb7orq8ifQ.jwJbTI_qCW365JgTtxeGz_jFXyFtQ4be-OCp5ezR4n8"; Claims claims = Jwts.parser().setSigningKey("liang").parseClaimsJws(token).getBody(); System.out.println(claims.getId()); System.out.println(claims.getSubject()); System.out.println(claims.getIssuedAt()); //解析自定义的claim中的内容 String companyId = (String) claims.get("companyId"); String companyName = (String) claims.get("companyName"); System.out.println(companyId); System.out.println(companyName); }}将生成和解析封装成工具类package com.springboot.utils;import io.jsonwebtoken.Claims;import io.jsonwebtoken.JwtBuilder;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import lombok.Getter;import lombok.Setter;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;import java.util.Date;import java.util.Map;@Getter@Setter@ConfigurationProperties("jwt.config")@Componentpublic class JwtUtils { //签名私钥 private String key; //签名有效时间 private long ttl; public String createJwtToken(String id, String name, Map<String,Object> map){ //设置失效时间 //获取当前时间 long now=System.currentTimeMillis(); //当前时间+有效时间=过期时间 long exp=now+ttl; //创建JwtBuilder JwtBuilder jwtBuilder = Jwts.builder().setId(id).setSubject(name) .setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256, key); //根据map设置clamis jwtBuilder.setClaims(map); //设置失效时间 jwtBuilder.setExpiration(new Date(exp)); String token = jwtBuilder.compact(); return token; } public Claims parseToken(String token){ Claims claims = Jwts.parser().setSigningKey("liang").parseClaimsJws(token).getBody(); return claims; }}

June 3, 2020 · 1 min · jiezi

springboot多数据源

springboot多mysql数据源:https://segmentfault.com/a/11...springboot多mongo数据源:https://blog.csdn.net/qq_2833...

June 3, 2020 · 1 min · jiezi

linux性能监控工具WGCLOUD-v30更新日志

WGCLOUD基于java语言开发,是高性能高并发的分布式监控平台,核心模块包括:服务器集群监控,ES集群监控,CPU监控,内存监控,数据监控(mysql,oracle,pg等),服务心跳检测,应用进程管理,磁盘IO监控,系统负载监控,监控告警信息推送。 WGCLOUD-v3.0更新说明,2020-05-311.新增docker模块监控,docker告警信息发送 2.新增数据源连接状态监控,发送告警信息 3.新增,server集群能力,支持负载均衡,从之前的单节点过度到集群模式,做调优和集群后可支持5000+主机监控 4.新增,agent使用go语言编写,不再依赖JDK环境,解压后可直接启动 5.新增,agent上报数据间隔时间,可配置 6.新增,历史监控数据保留时间,可配置,默认保留10天 7.新增,告警执行脚本文件,可配置,具体在.sh或.bat里扩展实现自己的告警逻辑,如短信,微信,钉钉等。系统自带模板脚本文件供参考,在/server/template/下 8.改造,业务数据表监控,改为自定义sql语句编写,更灵活 9.改造,数据库表重新设计,优化存贮结构,提升数据库并发和读写能力 10.改造,网络流量监控,流量统计单位由总KB,改为kb/s,发送接收包也如此 11.新增,系统启动时间,运行时间,运行进程数量 12.优化,主机等资源下线标识改为删除线 13.优化,内存使用率计算公式优化 14.优化,菜单布局及其他bug 开源地址:https://github.com/tianshiyeben/wgcloud 下载http://www.wgstart.com

June 3, 2020 · 1 min · jiezi

springboot项目将第三方jar配置文件打包到jar包外部

springboot项目默认打成一个jar包,在多环境时不友好,需要将依赖的第三方jar及resources目录下的配置文件打包到与jar包同级目录下,方便环境变更,具体操作如下: 打包到外部目录pom.xml需修改 <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <appendAssemblyId>false</appendAssemblyId> <descriptors> <descriptor>src/main/assembly/assembly.xml</descriptor> </descriptors> <outputDirectory>${project.build.directory}/gwall/</outputDirectory> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> <!-- 打包成jar文件,并指定lib文件夹以及resources资源文件夹 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>com.xxl.job.admin.XxlJobAdminApplication</mainClass> <!--依赖前缀 --> <classpathPrefix>lib/</classpathPrefix> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Class-Path>resources/</Class-Path> </manifestEntries> </archive> </configuration> </plugin> </plugins></build>添加配置文件 assembly.xml文件内容如下: <assemblyxmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"><id>distribution</id><!--输出格式 zip 最终结果生成zip --><formats> <format>tar.gz</format></formats><includeBaseDirectory>false</includeBaseDirectory><!--设置需要输出文件 --><fileSets> <fileSet> <directory>src/main/bin</directory> <outputDirectory>/</outputDirectory> <fileMode>0755</fileMode> </fileSet> <fileSet> <directory>src/main/resources/</directory> <outputDirectory>/resources</outputDirectory> <fileMode>0644</fileMode> </fileSet></fileSets><dependencySets> <dependencySet> <!--依赖包的输出目录 --> <outputDirectory>/lib</outputDirectory> <scope>runtime</scope> <excludes> <exclude>${project.groupId}:${project.artifactId}</exclude> </excludes> </dependencySet> <dependencySet> <!--jar包的输出目录 --> <outputDirectory>/</outputDirectory> <includes> <include>${project.groupId}:${project.artifactId}</include> </includes> </dependencySet></dependencySets></assembly>start.sh文件内容如下: ...

June 3, 2020 · 1 min · jiezi

Spring-Cloud-Spring-Security实践一-基本概念及实践

基本使用Spring security需要的基本依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>其他部分不需要任何的增加。 Security的理论是两个核心:认证(我是谁)和鉴权(我能做什么)。 在code中,这两个核心都需要通过继承WebSecurityConfigurerAdapter来实现。 ➡️废话不多说,上代码 首先,确保yml中添加了上面提到的两个依赖。添加后这就是最基本的spring security工程了。 然后,我们可以先添加一些controller。比如IndexController @RestControllerpublic class IndexController{ @RequestMapping(value = "/", method = RequestMethod.GET) public String index(){ return "index"; }}此时启动项目,会发现启动log中夹杂着一句乱码: Using generated security password: 2465a939-a37d-4d3e-9ee1-05d2e51f18fb这个“乱码”就是spring security提供的缺省密码。此时访问项目url,会自动跳转到项目url/login页面。默认username为user, password栏输入刚刚那一句“乱码”。点击signin,发现跳转成功,会访问到我们最初访问的页面。 自定义用户名密码创建@configuration: 新建一个类,继承 WebSecurityConfigurerAdapter, 添加注解@EnableWebSecurity (不用再添加@Configuration注解,因为已被EnableWebSecurity包含)如下所示。 @EnableWebSecuritypublic class SecurityConfiguration extends WebSecurityConfigurerAdapter {}覆写父类方法: 覆写configure(AuthenticationManagerBuilder auth)方法⚠️ 父类中包含多个configure方法,注意选择正确方法。代码如下所示: @Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("user").password(new BCryptPasswordEncoder().encode("123")).roles("USER");}此方法实现了三个功能: 定义了用户名和密码(user/123)加密了密码 - ⚠️ springsecurity强制密码加密,此处必须这样写定义此用户的role为USER - Spring security根据role来做鉴权操作,此处只是认证,暂时忽视即可。此时,重启项目,已经看不到最开始那一串乱码了,使用user/123登陆,即可跳转至正确页面。 ...

June 3, 2020 · 1 min · jiezi

SpringBoot全家桶23篇博客加23个可运行项目让你对它了如指掌

Spring Boot 现在已经成为Java 开发领域的一颗璀璨明珠,它本身是包容万象的,可以跟各种技术集成。本项目对目前Web开发中常用的各个技术,通过和SpringBoot的集成,并且对各种技术通过“一篇博客 + 一个可运行项目”的形式来详细说明。每个子项目都会使用最小依赖,大家拿来即可使用,自己可以根据业务需求自由组合搭配不同的技术构建项目。项目名称:springboot-bucket项目作者:一刀开源许可协议:MIT 项目地址:https://gitee.com/yidao620/sp... 项目简介 子项目列表每个子项目会配有一篇博客文章的详细讲解 环境JDK 1.8Maven latestSpring Boot 2.0.4Intellij IDEAmysql 5.7mongodbgit 版本管理nginx 反向代理redis 缓存rabbitmq 消息队列运行每个子项目都可以单独运行,都是打包成jar包后,通过使用内置jetty容器执行,有3种方式运行。1.在IDEA里面直接运行Application.java的main函数。2.另一种方式是执行mvn clean package命令后传到linux服务器上面,通过命令java -Xms64m -Xmx1024m -jar xxx.jar方式运行3.在linux服务器上面,配置好jdk、maven、git命令后,通过git clone sb-xxx拉取工程后,执行./run.sh start test命令来执行注:每个子项目有自己的README.md文件,告诉你该怎么初始化环境,比如准备好数据库SQL文件等。另外,如果你需要打包成war包放到tomcat容器中运行,可修改pom.xml文件,将打包类型从jar改成war,打包后再放到容器中运行: <modelVersion>4.0.0</modelVersion><artifactId>springboot-cache</artifactId><packaging>war</packaging>点击链接前往项目主页:https://gitee.com/yidao620/sp...,记得给它一个 Star 哦~

June 1, 2020 · 1 min · jiezi

springboot实现邮件任务

导入关键jar包: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> <version>2.2.6.RELEASE</version></dependency>在application.properties中添加邮件配置: #发送者的邮箱spring.mail.username=27553140@qq.com#邮箱秘钥spring.mail.password=nxnoashhoanvdfaf#邮箱主机spring.mail.host=smtp.qq.com#开启qq邮箱的安全认证spring.mail.properties.mail.smtp.ssl.enable=true在测试类中测试简单邮件任务 @SpringBootTestclass SpringbootShrioApplicationTests { //引入邮件实现类 @Autowired JavaMailSenderImpl mailSender; @Test void contextLoads() { SimpleMailMessage mailMessage = new SimpleMailMessage(); mailMessage.setSubject("第一个邮件test"); mailMessage.setText("你好"); mailMessage.setTo("27553140@qq.com"); mailMessage.setFrom("27553140@qq.com"); mailSender.send(mailMessage ); }会在QQ邮箱中收到相应的信息 在测试类中测试复杂邮件任务,比如可以支持附件的传输 @Test void contextLoads2() throws MessagingException { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true); helper.setSubject("复杂邮件"); helper.setText("<p style='color:red'>复杂邮件测试内容</p>",true); helper.addAttachment("奥特.png",new File("E:\\桌面\\奥特.png")); helper.setTo("151442642@163.com"); helper.setFrom("27553140@qq.com"); mailSender.send(mimeMessage); }至此,springboot就简单的使用的邮件功能

May 31, 2020 · 1 min · jiezi

RabbitMQ系列之怎么确保消息不丢失

1.上一篇介绍了在 SpringBoot 中怎么使用 RabbitMQ 来实现 RPC 功能,分享了可能踩到的坑及解决办法; 2.本篇主要介绍消息可能会存在丢失的场景及解决思路,基本上涵盖了可能会遇到的所有的场景。一. 通过设置持久化持久化可以提高 RabbitMQ 的可靠性,以防止在异常情况(比如:重启、关机、宕机等)下的数据丢失。 RabbitMQ 持久化分为三部分:交换机的持久化、队列的持久化、消息的持久化。 1. 什么是交换机持久化交换机持久化是指将交换机的属性数据存储在磁盘上,当 MQ 的服务器发生意外或关闭之后,在重启 RabbitMQ 时不需要重新手动或执行代码去创建交换机了,交换机会自动被创建,相当于一直存在。2. 怎么将交换机持久化在创建交换机的时候将durable参数设置为true即可。 比如,我声明一个类型为 direct 的交换机: /** * 设置交换机,类型为 direct * @return DirectExchange */@BeanDirectExchange myExchange() {return new DirectExchange(QueueConstants.QUEUE\_EXCHANGE\_NAME, true, false);}说明通过将durable参数设置为true,则交换机的元数据会被存储在磁盘上,对于一个长期使用的交换机来说,建议将其设置为持久化。3. 队列持久化如果不将队列设置为持久化,那么在 RabbitMQ 服务重启之后,相关队列的元数据会丢失,数据也会丢失。队列都没有了,消息也找不到地方存储了。4. 怎么将队列持久化同样,在创建队列的时候将durable参数设置为true即可。 /** * 创建队列 */@Beanpublic Queue myQueue() { return new Queue(QueueConstants.RPC_QUEUE1);}说明durable 参数默认为 false,只针对当前连接有效,当 RabbitMQ 服务重启后数据会丢失;队列的持久化能保证其本身的元数据不会因异常情况而丢失,但是并不能保证内部所存储的消息不会丢失;如果要确保消息不会丢失,就需要设置消息的持久化。5. 消息持久化RabbitMQ 的消息是依附于队列存在的,所以要想消息持久化,那么前提是队列也必须设置持久化。 6. 怎么将消息持久化在创建消息的时候,添加一个持久化消息的属性(将delivery_mode设置为 2)。 SpringBoot中怎么设置消息持久化在 SpringBoot 中使用 rabbitTemplate 发送的消息默认就是持久化的,因为默认已经设置为 delivery_mode = 2,下面我们通过查看源码来验证一下。 ...

May 29, 2020 · 2 min · jiezi

一直在用restTemplate你是否真搞清楚了

发送GET请求<T> ResponseEntity<T> getForEntity(URI var1, Class<T> var2)/*client*/@Testpublic void testRest(){ RestTemplate restTemplate = new RestTemplate(); String getUrl = "http://localhost:8083/mock/test/get" + "/" + "helloworld"; ResponseEntity<Student> entity = restTemplate.getForEntity(getUrl,Student.class); log.info(entity.toString()); log.info(entity.getHeaders().toString()); log.info(entity.getBody().toString());}客户端输出日志:2020-05-27 09:14:44 [main] INFO AppTest -<200,Student(name=王杰, age=28),[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Wed, 27 May 2020 01:14:44 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]>2020-05-27 09:14:44 [main] INFO AppTest -[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Wed, 27 May 2020 01:14:44 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]2020-05-27 09:14:44 [main] INFO AppTest -Student(name=王杰, age=28) /*server*/@RestController@Slf4jpublic class Demo { @GetMapping("/test/get/{name}") public JSONObject get(@PathVariable("name") String username){ log.debug("get server被调用"); log.info(username); JSONObject result = new JSONObject(); result.put("name","王杰"); result.put("age",28); return result; }}服务端输出日志:2020-05-27 09:14:44 [http-nio-8083-exec-3] DEBUG com.ai.mock.rests.Demo -get server被调用2020-05-27 09:14:44 [http-nio-8083-exec-3] INFO com.ai.mock.rests.Demo -helloworld ...

May 28, 2020 · 7 min · jiezi

springboot-restful接口-如何接受前端的PUT和DELETE请求

最近项目组有人问我,“springboot怎么接受不到前端提交的PUT和DELETE请求?”于是有了这篇文章,本篇文章主要解决两个问题: js好像只能提交GET、POST请求耶?怎么提交PUT DELETE请求?如何将前端提交的PUT、DELETE请求与server端对接上?问题1解决方案:在ajax中发送POST请求,带上_method参数,_method值为PUT或者DELETE实例: $.ajax({ url:"", type:"POST", data:{ _method:"PUT" }, success:function(data){...} })问题2解决方案:配置HiddenHttpMethodFilter实例: @Configurationpublic class HttpRequestConfig { @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter() { HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter(); hiddenHttpMethodFilter.setBeanName("HiddenHttpMethodFilter"); hiddenHttpMethodFilter.setMethodParam("_method"); return hiddenHttpMethodFilter; }}

May 28, 2020 · 1 min · jiezi

SpringBoot-canal数据同步解决方案

SpringBoot canal数据同步解决方案一、需求微服务多数据库情况下可以使用canal替代触发器,canal是应阿里巴巴跨机房同步的业务需求而提出的,canal基于数据库的日志解析,获取变更进行增量订阅&消费的业务。无论是canal实验需要还是为了增量备份、主从复制和恢复,都是需要开启mysql-binlog日志,数据目录设置到不同的磁盘分区可以降低io等待。canal 工作原理canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )canal 解析 binary log 对象(原始为 byte 流)二、部署环境1、登录mysql查看是否开启binlog,标红的log_bin默认是OFF关mysql> show variables like 'log_%';+----------------------------------------+-------------------------------------------------------+| Variable_name | Value |+----------------------------------------+-------------------------------------------------------+| **log_bin | OFF** || log_bin_basename | || log_bin_index | || log_bin_trust_function_creators | OFF || log_bin_use_v1_row_events | OFF || log_builtin_as_identified_by_password | OFF || log_error | F:\tools\mysql-5.7.28-winx64\Data\DESKTOP-C1LU9IQ.err || log_error_verbosity | 3 || log_output | FILE || log_queries_not_using_indexes | OFF || log_slave_updates | OFF || log_slow_admin_statements | OFF || log_slow_slave_statements | OFF || log_statements_unsafe_for_binlog | ON || log_syslog | ON || log_syslog_tag | || log_throttle_queries_not_using_indexes | 0 || log_timestamps | UTC || log_warnings | 2 |+----------------------------------------+-------------------------------------------------------+19 rows in set (0.03 sec)2、编辑配置文件[mysqld]# 设置3306端口port=3306# 设置mysql的安装目录,按照个人的实际需要改basedir=F:\\tools\\mysql-5.7.28-winx64 # 切记此处一定要用双斜杠\\,单斜杠我这里会出错,不过看别人的教程,有的是单斜杠。自己尝试吧# 设置mysql数据库的数据的存放目录datadir=F:\\tools\\mysql-5.7.28-winx64\\Data # 此处同上# 允许最大连接数max_connections=200# 允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统max_connect_errors=10# 服务端使用的字符集默认为UTF8character-set-server=utf8# 创建新表时将使用的默认存储引擎default-storage-engine=INNODB# 默认使用“mysql_native_password”插件认证default_authentication_plugin=mysql_native_passwordlower_case_table_names=2sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTIONmax_connections=1000#实验重点配置 # 开启 binloglog-bin=mysql-bin# 选择 ROW 模式binlog-format=ROW # 配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复server_id=1 [mysql]# 设置mysql客户端默认字符集default-character-set=utf8[client]# 设置mysql客户端连接服务端时默认使用的端口port=3306default-character-set=utf83、创建MySQL slave 的权限canal账户并且进行远程连接授权CREATE USER canal IDENTIFIED BY 'canal'; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';-- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;FLUSH PRIVILEGES;4、记得重启mysql服务Linux:systemctl restart mysqldWindow:net stop mysql;net start mysql;三、canal快速部署配置1、修改配置conf/example/instance.properties## mysql serverIdcanal.instance.mysql.slaveId = 1234#position info,需要改成自己的数据库信息canal.instance.master.address = 127.0.0.1:3306 canal.instance.master.journal.name = canal.instance.master.position = canal.instance.master.timestamp = #canal.instance.standby.address = #canal.instance.standby.journal.name =#canal.instance.standby.position = #canal.instance.standby.timestamp = #username/password,需要改成自己的数据库信息canal.instance.dbUsername = canal canal.instance.dbPassword = canalcanal.instance.defaultDatabaseName =canal.instance.connectionCharset = UTF-8#table regexcanal.instance.filter.regex = .\*\\\\..\*2、通过启动脚本运行:sh bin/startup.sh3、查看 server 日志和instance 的日志$ tail -f logs/canal/canal.log2020-05-28 13:52:03.037 [main] INFO com.alibaba.otter.canal.deployer.CanalLauncher - ## set default uncaught exception handler2020-05-28 13:52:03.065 [main] INFO com.alibaba.otter.canal.deployer.CanalLauncher - ## load canal configurations2020-05-28 13:52:03.072 [main] INFO com.alibaba.otter.canal.deployer.CanalStarter - ## start the canal server.2020-05-28 13:52:03.444 [main] INFO com.alibaba.otter.canal.deployer.CanalController - ## start the canal server[172.36.58.25(172.36.58.25):11111]2020-05-28 13:52:04.604 [main] INFO com.alibaba.otter.canal.deployer.CanalStarter - ## the canal server is running now ......$ tail -f logs/example/example.log2020-05-28 13:52:04.238 [main] WARN o.s.beans.GenericTypeAwarePropertyDescriptor - Invalid JavaBean property 'connectionCharset' being accessed! Ambiguous write methods found next to actually used [public void com.alibaba.otter.canal.parse.inbound.mysql.AbstractMysqlEventParser.setConnectionCharset(java.lang.String)]: [public void com.alibaba.otter.canal.parse.inbound.mysql.AbstractMysqlEventParser.setConnectionCharset(java.nio.charset.Charset)]2020-05-28 13:52:04.264 [main] INFO c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [canal.properties]2020-05-28 13:52:04.265 [main] INFO c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [example/instance.properties]2020-05-28 13:52:04.568 [main] INFO c.a.otter.canal.instance.spring.CanalInstanceWithSpring - start CannalInstance for 1-example2020-05-28 13:52:04.572 [main] WARN c.a.o.canal.parse.inbound.mysql.dbsync.LogEventConvert - --> init table filter : ^.*\..*$2020-05-28 13:52:04.573 [main] WARN c.a.o.canal.parse.inbound.mysql.dbsync.LogEventConvert - --> init table black filter :2020-05-28 13:52:04.577 [main] INFO c.a.otter.canal.instance.core.AbstractCanalInstance - start successful....2020-05-28 13:52:04.616 [destination = example , address = /127.0.0.1:3306 , EventParser] WARN c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - ---> begin to find start position, it will be long time for reset or first position2020-05-28 13:52:04.616 [destination = example , address = /127.0.0.1:3306 , EventParser] WARN c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - prepare to find start position just show master status2020-05-28 13:52:06.556 [destination = example , address = /127.0.0.1:3306 , EventParser] WARN c.a.o.c.p.inbound.mysql.rds.RdsBinlogEventParserProxy - ---> find start position successfully, EntryPosition[included=false,journalName=mysql-bin.000001,position=4,serverId=1,gtid=<null>,timestamp=1590644973000] cost : 1935ms , the next step is binlog dump四、初步监听实验 <dependency> <groupId>com.alibaba.otter</groupId> <artifactId>canal.client</artifactId> <version>1.1.0</version> </dependency>import java.net.InetSocketAddress;import java.util.List;import com.alibaba.otter.canal.client.CanalConnectors;import com.alibaba.otter.canal.client.CanalConnector;import com.alibaba.otter.canal.common.utils.AddressUtils;import com.alibaba.otter.canal.protocol.Message;import com.alibaba.otter.canal.protocol.CanalEntry.Column;import com.alibaba.otter.canal.protocol.CanalEntry.Entry;import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;import com.alibaba.otter.canal.protocol.CanalEntry.EventType;import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;import com.alibaba.otter.canal.protocol.CanalEntry.RowData;public class SimpleCanalClientExample { public static void main(String args[]) { // 创建链接 CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(AddressUtils.getHostIp(), 11111), "example", "", ""); int batchSize = 1000; int emptyCount = 0; try { connector.connect(); connector.subscribe(".*\\..*"); connector.rollback(); int totalEmptyCount = 120; while (emptyCount < totalEmptyCount) { Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据 long batchId = message.getId(); int size = message.getEntries().size(); if (batchId == -1 || size == 0) { emptyCount++; System.out.println("empty count : " + emptyCount); try { Thread.sleep(1000); } catch (InterruptedException e) { } } else { emptyCount = 0; // System.out.printf("message[batchId=%s,size=%s] \n", batchId, size); printEntry(message.getEntries()); } connector.ack(batchId); // 提交确认 // connector.rollback(batchId); // 处理失败, 回滚数据 } System.out.println("empty too many times, exit"); } finally { connector.disconnect(); } } private static void printEntry(List<Entry> entrys) { for (Entry entry : entrys) { if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) { continue; } RowChange rowChage = null; try { rowChage = RowChange.parseFrom(entry.getStoreValue()); } catch (Exception e) { throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), e); } EventType eventType = rowChage.getEventType(); System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s", entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(), entry.getHeader().getSchemaName(), entry.getHeader().getTableName(), eventType)); for (RowData rowData : rowChage.getRowDatasList()) { if (eventType == EventType.DELETE) { printColumn(rowData.getBeforeColumnsList()); } else if (eventType == EventType.INSERT) { printColumn(rowData.getAfterColumnsList()); } else { System.out.println("-------&gt; before"); printColumn(rowData.getBeforeColumnsList()); System.out.println("-------&gt; after"); printColumn(rowData.getAfterColumnsList()); } } } } private static void printColumn(List<Column> columns) { for (Column column : columns) { System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated()); } }}随便插入数据触发INSERT INTO `demo`.`tb_ad`(`id`, `url`, `status`, `position`, `image`, `start_time`, `end_time`) VALUES (1, 'https://www.baidu.com/', '1', 'web_index_lb', 'https://pics1.baidu.com/feed/c83d70cf3bc79f3d5c30d358deb67a17738b29a6.jpeg?https://kins.oss-cn-shenzhen.aliyuncs.com/yhzb/2020-03-11/ca21b3b17d6f4757b991dd86b8cef3fa-VIP-680.jpeg', '2020-05-22 10:58:08', '2021-06-01 10:58:14');从控制台中看到empty count : 66empty count : 67empty count : 68empty count : 69empty count : 70================&gt; binlog[mysql-bin.000001:355] , name[demo,tb_ad] , eventType : INSERTid : 2 update=trueurl : https://www.baidu.com/ update=truestatus : 1 update=trueposition : web_index_lb update=trueimage : https://pics1.baidu.com/feed/c83d70cf3bc79f3d5c30d358deb67a17738b29a6.jpeg?https://kins.oss-cn-shenzhen.aliyuncs.com/yhzb/2020-03-11/ca21b3b17d6f4757b991dd86b8cef3fa-VIP-680.jpeg update=truestart_time : 2020-05-22 10:58:08 update=trueend_time : 2021-06-01 10:58:14 update=true五、数据监控微服务<!-- 第三方starter快速整合canal https://github.com/NormanGyllenhaal/canal-client--><!-- https://mvnrepository.com/artifact/top.javatool/canal-spring-boot-starter --><dependency> <groupId>top.javatool</groupId> <artifactId>canal-spring-boot-starter</artifactId> <version>1.2.1-RELEASE</version></dependency>订阅数据库的增删改操作import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import top.javatool.canal.client.annotation.CanalTable;import top.javatool.canal.client.handler.EntryHandler;@Component@CanalTable(value = "t_user")public class UserHandler implements EntryHandler<User> { private Logger logger = LoggerFactory.getLogger(UserHandler.class); public void insert(User user) { logger.info("insert message {}", user); } public void update(User before, User after) { logger.info("update before {} ", before); logger.info("update after {}", after); } public void delete(User user) { logger.info("delete {}", user); }}启动数据监控微服务,修改user表,观察控制台输出。2020-05-28 16:23:22.667 INFO 24284 --- [l-client-thread] t.j.c.client.client.AbstractCanalClient : 获取消息 Message[id=23,entries=[header { version: 1 logfileName: "mysql-bin.000001" logfileOffset: 18380 serverId: 1 serverenCode: "UTF-8" executeTime: 1590654201000 sourceType: MYSQL schemaName: "" tableName: "" eventLength: 68}entryType: TRANSACTIONBEGINstoreValue: " \025", header { version: 1 logfileName: "mysql-bin.000001" logfileOffset: 18505 serverId: 1 serverenCode: "UTF-8" executeTime: 1590654201000 sourceType: MYSQL schemaName: "demo" tableName: "t_user" eventLength: 88 eventType: UPDATE props { key: "rowsCount" value: "1" }}entryType: ROWDATAstoreValue: "\b\210\002\020\002P\000b\370\003\n\033\b\000\020\004\032\002id \001(\0000\000B\00221R\aint(11)\n*\b\001\020\f\032\tuser_name \000(\0000\000B\005ZeldaR\fvarchar(255)\n*\b\002\020\372\377\377\377\377\377\377\377\377\001\032\006gender \000(\0000\000B\0010R\ntinyint(4)\n\"\b\003\020\004\032\ncountry_id \000(\0000\000B\0011R\aint(11)\n&\b\004\020[\032\bbirthday \000(\0000\000B\n1998-04-18R\004date\n7\b\005\020]\032\vcreate_time \000(\0000\000B\0231991-01-10 05:45:50R\ttimestamp\022\033\b\000\020\004\032\002id \001(\0000\000B\00221R\aint(11)\022.\b\001\020\f\032\tuser_name \000(\0010\000B\tZelda1111R\fvarchar(255)\022*\b\002\020\372\377\377\377\377\377\377\377\377\001\032\006gender \000(\0000\000B\0010R\ntinyint(4)\022\"\b\003\020\004\032\ncountry_id \000(\0000\000B\0011R\aint(11)\022&\b\004\020[\032\bbirthday \000(\0000\000B\n1998-04-18R\004date\0227\b\005\020]\032\vcreate_time \000(\0000\000B\0231991-01-10 05:45:50R\ttimestamp", header { version: 1 logfileName: "mysql-bin.000001" logfileOffset: 18593 serverId: 1 serverenCode: "UTF-8" executeTime: 1590654201000 sourceType: MYSQL schemaName: "" tableName: "" eventLength: 31}entryType: TRANSACTIONENDstoreValue: "\022\0041574"],raw=false,rawEntries=[]]2020-05-28 16:23:22.668 INFO 24284 --- [xecute-thread-6] t.j.canal.example.handler.UserHandler : update before User{id=null, userName='Zelda', gender=null, countryId=null, birthday=null, createTime=null} 2020-05-28 16:23:22.668 INFO 24284 --- [xecute-thread-6] t.j.canal.example.handler.UserHandler : update after User{id=21, userName='Zelda1111', gender=0, countryId=1, birthday=Sat Apr 18 00:00:00 CST 1998, createTime=Thu Jan 10 05:45:50 CST 1991}

May 28, 2020 · 5 min · jiezi

个人学习系列-SpringBoot解决跨域问题

开发过程中总是会听说到跨域这个问题,一直没有详细的了解过,这里我就详细的梳理一下。源源(origin)就是协议、域名和端口号。 同源策略同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。所以xyz.com下的js脚本采用ajax读取abc.com里面的文件数据是会被拒绝的。 同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。 跨域URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口全部相同,则表示他们同源。否则,只要协议、域名、端口有任何一个不同,就是跨域。 跨域分析 在Spring Boot上解决跨域问题1. 搭建一个8080端口的项目前端页面使其访问8081端口的接口: <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>跨域测试-8080端口</title></head><body> <button id="test">8080 -> 8081</button></body><script src="https://code.jquery.com/jquery-3.4.1.min.js"></script><script> $("#test").click(function () { $.ajax({ url: "http://localhost:8081/hello", type: "post", success:function (res) { alert(res) } }) });</script></html>2. 点击按钮测试火狐浏览器进行跨域请求拦截 3. 搭建一个8081端口的项目创建一个/hello的接口 /** * 跨域8080端口方法 * @author zhouzhaodong */@RestControllerpublic class CrossDomainTwoController { @RequestMapping("/hello") public String hello(){ return "这是一个来自8081端口的接口!"; }}点击后观察浏览器,由于没有进行任何配置,所以依旧报错 4. 解决跨域问题4.1 使用@CrossOrigin注解端口/** * 跨域8080端口方法 * @author zhouzhaodong */@RestControllerpublic class CrossDomainTwoController { @CrossOrigin @RequestMapping("/hello") public String hello(){ return "这是一个来自8081端口的接口!"; }}再次点击按钮,发现正常返回信息。 ...

May 28, 2020 · 1 min · jiezi

SpringBootMyBatis集成多数据库

1 写配数据库置类1dbConfig.DataSourceConfigCommon package XXX.api.dbConfig;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.SqlSessionTemplate;import org.mybatis.spring.annotation.MapperScan;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import javax.sql.DataSource;//表示这个类为一个配置类@Configuration// 配置mybatis的接口类放的地方@MapperScan(basePackages = "XXX.api.mapper.common", sqlSessionFactoryRef = "commonSqlSessionFactory")public class DataSourceConfigCommon { // 将这个对象放入Spring容器中 @Bean(name = "commonDataSource") // 表示这个数据源是默认数据源// @Primary // 读取application.properties中的配置参数映射成为一个对象 // prefix表示参数的前缀 @ConfigurationProperties(prefix = "spring.datasource.common") public DataSource getDateSourceCommon() { return DataSourceBuilder.create().build(); } @Bean(name = "commonSqlSessionFactory") // 表示这个数据源是默认数据源// @Primary // @Qualifier表示查找Spring容器中名字为test1DataSource的对象 public SqlSessionFactory commonSqlSessionFactory(@Qualifier("commonDataSource") DataSource datasource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(datasource); bean.setMapperLocations( // 设置mybatis的xml所在位置 new PathMatchingResourcePatternResolver().getResources("classpath:cn/longmaster/slss/api/mapper/xml/*.xml")); return bean.getObject(); } @Bean("commonSqlSessionTemplate") // 表示这个数据源是默认数据源// @Primary public SqlSessionTemplate commonSqlSessionTemplate( @Qualifier("commonSqlSessionFactory") SqlSessionFactory sessionFactory) { return new SqlSessionTemplate(sessionFactory); }}2 写配数据库置类2dbConfig.DataSourceConfigYLSBGL ...

May 28, 2020 · 2 min · jiezi

SpringBoot源码解析-TomcatSpringMVC启动

本文通过阅读SpringBoot源码,分享SpringBoot中Tomcat,SpringMvc组件的启动过程。源码分析基于spring boot 2.1 Tomcat在解析SpringBoot启动的文章中说过,SERVLET应用使用的ApplicationContext是AnnotationConfigServletWebServerApplicationContext。其父类ServletWebServerApplicationContext,通过ServletWebServerFactory创建并初始化WebServer,WebServer兼容不同的servlet容器(tomcat,jetty,netty),提供统一的start,stop操作。ServletWebServerApplicationContext还负责注册Servlet,Filter,ServletContextListener的工作。ServletWebServerApplicationContext#servletConfig,GenericWebApplicationContext#servletContext都是servlet规范提供的类。 ApplicationContext#run -> AnnotationConfigServletWebServerApplicationContext#refresh -> AbstractApplicationContext#refresh -> AbstractApplicationContext#onRefresh -> ServletWebServerApplicationContext#onRefresh -> ServletWebServerApplicationContext#createWebServer private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); // #1 this.webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null) { try { // #2 getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources();}#1 创建webServer,注意getSelfInitializer()通过方法引用构造了一个ServletContextInitializer,该ServletContextInitializer会调用ServletWebServerApplicationContext#selfInitialize方法#2 webServer已经存在,直接调用ServletWebServerApplicationContext#selfInitialize ...

May 27, 2020 · 2 min · jiezi

SpringBoot别再问我-Starter-该怎么写了

1 前言从前从前,有个面试官问我一个 SpringBoot Starter 的开发流程,我说我没有写过 starter,然后就没有然后了,面试官说我技术深度不够。 我想说这东西不是很简单吗,如果要自己写一个出来也是分分钟的事情。至于就因为我没有写过 starter 就觉得我一点都不会 SpringBoot 吗? 当然我当时确实水平不足,连 Java 的 SPI 都忘了是啥,后来又捡了起来,原来我在大学的时候就用过 Java 的 SPI,悔之晚矣!如果你也不知道什么是 SPI,可以去看看的我另一篇文章「JVM」别再问我什么是双亲委派和 SPI。 2 什么是 SpringBoot starterstarter 是 SpringBoot 的一个重要的组成部分,它相当于一个集成的模块,比如你想用 Mybatis 和 lombok,但是在 pom 文件中需要写两个依赖,如果你将他们集成为一个 starter(或者将更多你需要的依赖集成进去),那么你只需要在 pom 文件中写一个 starter 依赖就可以了,这对于一个可复用模块的开发和维护都极为有利。 同时,在 maven 中引入 starter 依赖之后,SpringBoot 就能自动扫描到要加载的信息并启动相应的默认配置,它遵循“约定大于配置”的理念。 3 如何开发一个 SpringBoot starter3.0 环境说明jdk 1.8.0_151maven 3.6.3IDEA 编译器要开发的 starter 是一个日期格式化工具,它的功能是可以指定如何格式转化日期类型,同时在配置文件中可以开启关闭此功能3.1 创建项目在 IDEA 中新建一个 maven 工程,如下图所示。 Spring 官方建议自定义的 starter 使用 xxx-spring-boot-starter 命名规则,以区分 SpringBoot 生态提供的 starter。然后需要在 pom 文件中加入实现 starter 所需要的依赖。 ...

May 27, 2020 · 3 min · jiezi

SpringBoot-中调用存储过程的方法

1 在 XXXmapper.xml 文件中 <select id="callProOutStock" parameterType="map" statementType="CALLABLE" resultType="int"> { call pro_out_stock( #{deptId, mode=IN, jdbcType=VARCHAR}, #{goodsNo, mode=IN, jdbcType=VARCHAR}, #{storageNo, mode=IN, jdbcType=VARCHAR}, #{amount, mode=IN, jdbcType=NUMERIC}, #{result, mode=OUT, jdbcType=NUMERIC} ) } </select>2 在 XXXmapper.java中 int callProOutStock(@Param("deptId") String deptId, @Param("goodsNo") String goodsNo, @Param("storageNo") String storageNo, @Param("amount") int amount, @Param("result") int result);

May 27, 2020 · 1 min · jiezi

SpringBoot是如何启动的

本文是通过查看SpringBoot源码整理出来的SpringBoot大致启动流程,整体大方向是以简单为出发点,不说太多复杂的东西,内部实现细节本文不深扣因为每个人的思路、理解都不一样,我个人看的理解跟大家看的肯定不一样,到时候表达的出来的云里雾里也没啥用。 首先我将SpringBoot的启动流程整理成以下阶段: SpringApplicaiton初始化 审查ApplicationContext类型加载ApplicationContextInitializer加载ApplicationListenerEnvironment初始化 解析命令行参数创建Environment配置Environment配置SpringApplicationApplicationContext初始化 创建ApplicationContext设置ApplicationContext刷新ApplicationContext运行程序入口省去了一些不影响主流程的细节,在查看SpringBoot源码之前,不得不提一下spring.factories这个文件的使用和功能。 关于spring.factoriesspring.factories是一个properties文件,它位于classpath:/META-INF/目录里面,每个jar包都可以有spring.factories的文件。Spring提供工具类SpringFactoriesLoader负责加载、解析文件,如spring-boot-2.2.0.RELEASE.jar里面的META-INF目录里面就有spring.factories文件: # PropertySource Loadersorg.springframework.boot.env.PropertySourceLoader=\org.springframework.boot.env.PropertiesPropertySourceLoader,\org.springframework.boot.env.YamlPropertySourceLoader# Run Listenersorg.springframework.boot.SpringApplicationRunListener=\org.springframework.boot.context.event.EventPublishingRunListener...关于spring.factories需要知道些什么? spring.factories是一个properties文件spring.factories里的键值对的value是以逗号分隔的完整类名列表spring.factories里的键值对的key是完整接口名称spring.factories键值对的value是key的实现类spring.factories是由SpringFactoriesLoader工具类加载spring.factories位于classpath:/META-INF/目录SpringFactoriesLoader会加载jar包里面的spring.factories文件并进行合并知道spring.factories的概念后,继续来分析SpringBoot的启动。 SpringApplicaiton初始化Java程序的入口在main方法SpringBoot的同样可以通过main方法启动,只需要少量的代码加上@SpringBootApplication注解,很容易的就启动SpringBoot: @SpringBootApplication@Slf4jpublic class SpringEnvApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringEnvApplication.class, args); }}SpringApplicaiton初始化位于SpringApplication的构造函数中: 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的构造函数干了些啥: ...

November 5, 2019 · 3 min · jiezi

Spring-讲解五

Spring 中使用 xml 配置开发和使用注解开发案例1、Spring 中使用 xml 配置开发案例 接口public interface UserDao { void add(User user);}========================================================================================public interface UserService { void add(User user);}接口的实现类public class UserDaoImpl implements UserDao { @Override public void add(User user) { System.out.println("Dao 添加用户信息======>" + user); }}=========================================================================================public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void add(User user) { System.out.println("Service 添加用户信息======>" + user); userDao.add(user); }}=========================================================================================模拟UserAction调用方法public class UserAction { private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } public void save(User user) { System.out.println("UserAction 方法调用"); userService.add(user); }}配置bean.xml 文件<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 1.配置dao--> <bean id="userDao" class="com.example.demo.code.impl.UserDaoImpl"></bean> <!-- 2.配置service --> <bean id="userService" class="com.example.demo.code.impl.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean> <!--3.配置action--> <bean id="userAction" class="com.example.demo.code.UserAction"> <property name="userService" ref="userService"></property> </bean></beans>测试函数 ...

November 5, 2019 · 2 min · jiezi

Java代码生成的设计方案与实践

diboot devtools 2.0 已经发布上线,实现了精简内核+后端开发助理。在此把我们目前代码生成部分的设计思路梳理一下,以便需要的朋友少走一些弯路。1. 编程技术的发展趋势最近观看《美国工厂》的感触:在传统制造业,当生产线的工人成本高昂效率低下的时候,企业管理者便会开始探寻降本增效之道,自动化便是首选的解决方案。 软件工程类似于建筑工程有着其自身的复杂度,但即便是像更复杂的汽车制造,不也一样逐步被自动化么。有人说编程将是最后一个被自动化/AI替代的行业,但不代表程序员可以高枕无忧。如果一个程序员只会CRUD,那他可能是第一批被替代的。因为替代是逐步性的,就像软件行业的自动化可能经过代码生成、轻代码、无代码。 2. 代码生成的设计方案2.1 要盖楼先打好地基有的生成器,无任何基础代码直接生成,结果就像一个项目中无任何高级别开发者做封装,直接交给程序员写的一样,随着代码量的增多,可维护性会差的难以想象。 所以,盖楼前先打好地基,封装一个基础的通用内核,提供常用开发场景的最佳实现,比如CRUD通用处理、各分层的轻量封装、常用工具类等。 2.2 生成器的理想方案通用内核职责范围要明确,职责范围内的功能做到最佳实践,避免设计成大而臃肿。通用内核解决CRUD、关联查询等通用场景,实现代码最简化,降低上手门槛和生成器的实现难度。使用简单易上手,最好可以在开发环境启动,直接将代码生成在本地IDE项目中。界面化操作代码生成,随用随生,灵活应对需求(数据结构)变更场景。数据结构驱动代码,实现代码与数据结构的联动同步。支持重复性相似功能的通用生成。支持参数配置实现差异化代码生成。如果受众广,需要基于主流技术及数据库。3. Java代码生成的实践基于Spring Boot 2.x,最主流高效的开发框架基于Mybatis-plus实现CRUD的通用解决方案封装内核实现关联查询的无SQL解决方案及其他常用场景生成器封装成spring boot starter,实现依赖启动提供操作界面实现数据结构与代码同步配置参数实现Lombok、Swagger等开关提取数据库差异,支持MySQL、MariaDB、PostgreSQL、Oracle、SQL Server深入交流请加微信: wx20201024diboot v2 githubdiboot 简单高效的轻代码开发框架 (欢迎star)

November 5, 2019 · 1 min · jiezi

微服务架构案例04中间件集成公共服务封装

本文源码:GitHub·点这里 || GitEE·点这里 更新进度(共6节): 01:项目技术选型简介,架构图解说明02:业务架构设计,系统分层管理03:数据库选型,业务数据设计规划04:中间件集成,公共服务管理一、中间件简介中间件是基础软件的一类, 属于复用性极高的软件。处于操作系统软件与应用程序的之间。是一种独立的系统软件,也可以是公共的服务程序,分布式架构系统借助中间件,可以在不同的技术之间共享资源,或者不同的服务直接传递信息。中间件位操作系统之上,管理计算机资源和网络通讯。是连接两个独立应用程序或独立系统的软件,例如:消息队列中间件,在两个服务之间进行异步的消息传递;数据缓存中间件,缓存整合系统的热点数据,提高程序的响应速度;Nginx中间件,提供负载均衡,服务代理,等功能;二、公共服务简介公共服务,顾名思义就是系统内通用的服务,例如用户身份验证,消息发送,监控预警,网关服务等。 该案例的中间件和公共服务,都是基于Feign接口统一的方式提供服务。 三、中间件集成1、消息中间件RocketMq简介RocketMq 是一款分布式、队列模型的消息中间件,有两个核心角色:消息生产者和消息消费者。作为高并发系统的核心组件之一,能够帮助业务系统解构提高系统稳定性。 应用流程消息生产者@Componentpublic class MsgSendService { @Resource private ProducerConfig producerConfig ; public void sendMsg (MsgWrap msgWrap) { producerConfig.sendMsg(msgWrap.getGroup(),msgWrap.getTopic(), msgWrap.getTag(),msgWrap.getContent()); }}消息消费者@Component@Consumer(group = MsgRoute.husky_group_1, topic = MsgRoute.husky_topic_1 , tag = MsgRoute.husky_tag_1)public class UserSearchListener implements MsgReadService { @Resource private BookEsAnalyFeign bookEsAnalyFeign ; @Override public void readMsg(String msg) throws Exception { LOGGER.info("【用户搜索消息监听 Msg】:{}",msg) ; // 转发请求数据分析服务 bookEsAnalyFeign.sendBookEsMsg(msg); }}提供Feign接口@RestControllerpublic class UserSearchController implements UserSearchFeign { @Resource private SendMsgService sendMsgService ; @Override public void sendBookSearch(String msgContent) { MsgWrap msgWrap = new MsgWrap() ; msgWrap.setContent(msgContent); msgWrap.setGroup(MsgRoute.husky_group_1); msgWrap.setTopic(MsgRoute.husky_topic_1); msgWrap.setTag(MsgRoute.husky_tag_1); sendMsgService.sendMsg(msgWrap); }}2、缓存中间件Redis简介Redis 是一个基于内存的高性能key-value数据库。对高并发系统提供各种场景的支撑:热点数据缓存,计数器,流量削峰等。 ...

November 5, 2019 · 3 min · jiezi

springboot整合token

写在前面在前后端交互过程中,为了保证信息安全,我们往往需要加点用户验证。本文介绍了用springboot简单整合token。springboot版本2.2.0。另外主要用到了jjwt,redis。阅读本文,你大概需要花费7-10分钟时间整合token1. 导入相关依赖pom.xml文件中 <!-- jwt 加密解密工具类--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>2.TokenUtil.java实现生成/解析tokenpackage com.dbc.usermanager.util;import com.dbc.usermanager.service.RedisService;import io.jsonwebtoken.Claims;import io.jsonwebtoken.JwtBuilder;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import org.springframework.beans.factory.annotation.Autowired;import javax.crypto.spec.SecretKeySpec;import javax.xml.bind.DatatypeConverter;import java.security.Key;import java.util.Date;public class TokenUtil { /** * 签名秘钥,可以换成 秘钥 注入 */ public static final String SECRET = "DaTiBao";//注意:本参数需要长一点,不然后面剪切的时候很可能长度为0,就会报错 /** * 签发地 */ public static final String issuer = "dtb.com"; /** * 过期时间 */ public static final long ttlMillis = 3600*1000*60; /** * 生成token * * @param id 一般传入userName * @return */ public static String createJwtToken(String id,String subject) { return createJwtToken(id, issuer, subject, ttlMillis); } public static String createJwtToken(String id) { return createJwtToken(id, issuer, "", ttlMillis); } /** * 生成Token * * @param id 编号 * @param issuer 该JWT的签发者,是否使用是可选的 * @param subject 该JWT所面向的用户,是否使用是可选的; * @param ttlMillis 签发时间 (有效时间,过期会报错) * @return token String */ public static String createJwtToken(String id, String issuer, String subject, long ttlMillis) { // 签名算法 ,将对token进行签名 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 生成签发时间 long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); // 通过秘钥签名JWT byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET); String str=signatureAlgorithm.getJcaName(); Key signingKey = new SecretKeySpec(apiKeySecretBytes, str); // 让我们设置JWT声明 JwtBuilder builder = Jwts.builder().setId(id) .setIssuedAt(now) .setSubject(subject) .setIssuer(issuer) .signWith(signatureAlgorithm, signingKey); // if it has been specified, let's add the expiration if (ttlMillis >= 0) { //过期时间 long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); } // 构建JWT并将其序列化为一个紧凑的url安全字符串 return builder.compact(); } /** * Token解析方法 * @param jwt Token * @return */ public static Claims parseJWT(String jwt) { // 如果这行代码不是签名的JWS(如预期),那么它将抛出异常 Claims claims = Jwts.parser() .setSigningKey(DatatypeConverter.parseBase64Binary(SECRET)) .parseClaimsJws(jwt).getBody(); return claims; } public static void main(String[] args) { String token = TokenUtil.createJwtToken("2","ltz"); System.out.println(TokenUtil.createJwtToken("2","ltz")); Claims claims = TokenUtil.parseJWT(token); System.out.println(claims); }}3.新增登录验证的注解@LoginRequiredpackage com.dbc.usermanager.util;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;//加入此注解,就需要token@Target({ElementType.METHOD, ElementType.TYPE})// 表明此注解可用在方法名上@Retention(RetentionPolicy.RUNTIME)// 运行时有效public @interface LoginRequired { boolean required() default true;}4.测试 @PostMapping(value = "test") @ApiOperation(value="生成token") public ResultJson test(@RequestBody JSONObject requestJson){ String token= TokenUtil.createJwtToken("1","dtb"); redisService.set(token,"1"); return new ResultJson(0,"测试成功",null); } @GetMapping(value = "getToken") @LoginRequired @ApiOperation("") public ResultJson getToken(String token){ if(redisService.exists(token)){ System.out.println(redisService.get(token)); } return new ResultJson(0,"测试成功",null); }最后实体类User.java等相关文件就不贴出来了,大家可以用自己写好的实体类去编写。很多步骤与思想都在代码中体现,代码中也加了很多注释,你可以根据自己的需求进行增删查改。

November 5, 2019 · 2 min · jiezi

springboot整合redistoken验证登录

写在前面redis是一种可基于内存也可基于持久话的日志型、key-value数据库。因为性能高,存储数据类型丰富等优势常被用作数据缓存。本文介绍了springboot2.2.0整合redis的常规步骤。阅读本文,你大概需要5分钟左右的时间整合redis一. 安装redis根据你的操作系统选择redis版本下载并安装redis点击下载下载文件重命名为redis->打开该文件->cmd到当前文件夹下->redis-server.exe redis.windows.conf 即可启动redis可以为redis设置环境变量,这个大家都懂的。注意:防火墙应该为redis打开!二.引入redis依赖在pom.xml文件下引入redis相关依赖 <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <!-- 1.5的版本默认采用的连接池技术是jedis 2.0以上版本默认连接池是lettuce, 在这里采用jedis,所以需要排除lettuce的jar --> <exclusions> <exclusion> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </exclusion> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <!-- 添加jedis客户端 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <!--spring2.0集成redis所需common-pool2--> <!-- 必须加上,jedis依赖此 --> <!-- spring boot 2.0 的操作手册有标注 大家可以去看看 地址是:https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/htmlsingle/--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.5.0</version> </dependency>3. 全局配置单服务器redis在properties文件中,加入以下基本配置 spring.redis.port=6379spring.redis.host=127.0.0.1#我的redis连接不需要密码#spring.redis.password=123spring.redis.jedis.pool.max-active=100spring.redis.jedis.pool.max-idle=5spring.redis.jedis.pool.max-wait=60000spring.redis.database=0spring.redis.timeout=10000#若开启redis方式的session存储 type值应为redisspring.session.store-type=redisspring.session.timeout=10server.servlet.session.timeout=104.新建RedisServer.java。创建一个对redis的基本操作的类package com.dbc.usermanager.service;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.ValueOperations;import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Servicepublic class RedisService { @Autowired protected StringRedisTemplate redisTemplate; /** * 写入redis缓存(不设置expire存活时间) * @param key * @param value * @return */ public boolean set(final String key, String value){ boolean result = false; try { ValueOperations operations = redisTemplate.opsForValue(); operations.set(key, value); result = true; } catch (Exception e) { System.out.println("写入redis缓存失败!错误信息为:" + e.getMessage()); } return result; } /** * 写入redis缓存(设置expire存活时间) * @param key * @param value * @param expire * @return */ public boolean set(final String key, String value, Long expire){ boolean result = false; try { ValueOperations operations = redisTemplate.opsForValue(); operations.set(key, value); redisTemplate.expire(key, expire, TimeUnit.SECONDS); result = true; } catch (Exception e) { System.out.println("写入redis缓存(设置expire存活时间)失败!错误信息为:" + e.getMessage()); } return result; } /** * 读取redis缓存 * @param key * @return */ public Object get(final String key){ Object result = null; try { ValueOperations operations = redisTemplate.opsForValue(); result = operations.get(key); } catch (Exception e) { System.out.println("读取redis缓存失败!错误信息为:" + e.getMessage()); } return result; } /** * 判断redis缓存中是否有对应的key * @param key * @return */ public boolean exists(final String key){ boolean result = false; try { result = redisTemplate.hasKey(key); } catch (Exception e) { System.out.println("判断redis缓存中是否有对应的key失败!错误信息为:" + e.getMessage()); } return result; } /** * redis根据key删除对应的value * @param key * @return */ public boolean remove(final String key){ boolean result = false; try { if(exists(key)){ redisTemplate.delete(key); } result = true; } catch (Exception e) { System.out.println("redis根据key删除对应的value失败!错误信息为:" + e.getMessage()); } return result; } /** * redis根据keys批量删除对应的value * @param keys * @return */ public void remove(final String... keys){ for(String key : keys){ remove(key); } }}

November 5, 2019 · 2 min · jiezi

SpringBoot2X使用JWTJSONWebToken

一、跨域认证遇到的问题由于多终端的出现,很多的站点通过 web api restful 的形式对外提供服务,采用了前后端分离模式进行开发,因而在身份验证的方式上可能与传统的基于 cookie 的 Session Id 的做法有所不同,除了面临跨域提交 cookie 的问题外,更重要的是,有些终端可能根本不支持 cookie。 JWT(JSON Web Token) 是一种身份验证及授权方案,简单的说就是调用端调用 api 时,附带上一个由 api 端颁发的 token,以此来验证调用者的授权信息。 一般流程是下面这样: 用户向服务器发送用户名和密码。服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。服务器向用户返回一个 session_id,写入用户的 Cookie。用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。这种模式的问题在于扩展性不好。单机没有问题,如果是服务器集群、跨域的服务导向架构或者用户禁用了 cookie ,就不行了。 二、解决方案 1. 单机和分布式应用下登录校验,session 共享 单机和多节点 tomcat 应用登录检验 ①、单机 tomcat 应用登录,sesssion 保存在浏览器和应用服务器会话之间,用户登录成功后,服务端会保证一个 session,也会给客户端一个 sessionId,客户端会把 sessionId 保存在 cookie 中,用户每次请求都会携带这个 sessionId。②、多节点 tomcat 应用登录,开启 session 数据共享后,每台服务器都能够读取 session。缺点是每个 session 都是占用内存和资源的,每个服务器节点都需要同步用户的数据,即一个数据需要存储多份到每个服务器,当用户量到达百万、千万级别的时,占用资源就严重,用户体验特别不好!! 分布式应用中 session 共享 ①、真实的应用不可能单节点部署,所以就有个多节点登录 session 共享的问题需要解决。tomcat 支持 session 共享,但是有广播风暴;用户量大的时候,占用资源就严重,不推荐②、Reids 集群,存储登陆的 token,向外提供服务接口,Redis 可设置过期时间(服务端使用 UUID生成随机 64 位或者 128 位 token ,放入 Redis 中,然后返回给客户端并存储)。 ...

November 5, 2019 · 2 min · jiezi

SpringBoot2X实现文件上传三

使用 SpringBoot 项目完成单个、多个文件的上传处理,并将上传的文件保存到指定目录下。代码演示案例所有的 HTML 页面文件index.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>选择上传文件类型</title></head><script language="javascript"> function single() { document.form1.action = "/singlefile"; document.form1.submit(); } function multi() { document.form1.action = "/multifile"; document.form1.submit(); }</script><body><form name="form1" method="post"> <input type="button" name="btn1" value="单个文件上传" onclick="single();"> <input type="button" name="btn2" value="多个文件上传" onclick="multi();"></form></body></html>multifile.html<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"/> <title>多文件上传</title></head><body><h1 th:inlines="text">多文件上传</h1><form action="/multiFileUpload" method="post" enctype="multipart/form-data"> <p>选择文件1: <input type="file" name="fileName"/></p> <p>选择文件2: <input type="file" name="fileName"/></p> <p>选择文件3: <input type="file" name="fileName"/></p> <p><input type="submit" value="提交"/></p></form></body></html>singlefile.html<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"/> <title>单文件上传</title></head><body><h1 th:inlines="text">单文件上传</h1><form action="/singleFile" method="post" enctype="multipart/form-data"> <p>文件:<input type="file" name="head_img"/></p> <p><input type="submit" value="上传"/></p></form></body></html>逻辑代码定义结果集@Getter@Setter@ToStringpublic class Result implements Serializable { private boolean flag; //是否成功 private Integer code; //返回码 private String message;//返回信息 public Result(boolean flag, Integer code, String message) { this.flag = flag; this.code = code; this.message = message; }}定义错误码public class StatusCode { public static final int OK = 2000; //成功 public static final int ERROR = 4000; //失败}逻辑代码@Controller@Slf4jpublic class FileController { @Value("${file.path}") private String filePath; // 获取 singlefile.html 页面 @RequestMapping(value = "/singlefile", method = RequestMethod.POST) public String single() { return "singlefile"; } // 单文件上传 @RequestMapping(value = "singleFile") @ResponseBody public Result uploadFile(@RequestParam("head_img") MultipartFile file, HttpServletRequest request) { if (file.isEmpty()) { return new Result(false, StatusCode.ERROR, "上传的文件大小为空,请检查!!"); } //获取文件名称、后缀名、大小 String fileName = file.getOriginalFilename(); String suffixName = fileName.substring(fileName.lastIndexOf(".")); long size = file.getSize(); log.info("上传的文件名称为:[{}],文件后缀为:[{}],文件大小为:[{}]!!", fileName, suffixName, size); // 存储转换后文件名称 fileName = UUID.randomUUID() + suffixName; log.info("转换后的文件名为:[{}]!!", fileName); File dest = new File(filePath + fileName); //判断父目录是否存在 if (!dest.getParentFile().exists()) { dest.getParentFile().mkdir(); } try { file.transferTo(dest); return new Result(true, StatusCode.OK, "上传成功!!"); } catch (IOException e) { log.error("上传文件过程中发生异常!", e); } return new Result(true, StatusCode.ERROR, "上传失败!!"); } // 获取 multifile.html 页面 @RequestMapping("/multifile") public String multi() { return "multifile"; } // 多文件上传 @PostMapping(value = "multiFileUpload") @ResponseBody public Result multiFileUpload(HttpServletRequest request) { List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("fileName"); for (MultipartFile file : files) { if (file.isEmpty()) { return new Result(false, StatusCode.ERROR, "上传多个文件时,某个文件大小为空,请检查!!"); } else { String fileName = file.getOriginalFilename(); String suffixName = fileName.substring(fileName.lastIndexOf(".")); long size = file.getSize(); log.info("上传的文件名称为:[{}],文件后缀为:[{}],文件大小为:[{}]!!", fileName, suffixName, size); fileName = UUID.randomUUID() + suffixName; log.info("转换后的文件名为:[{}]!!", fileName); File dest = new File(filePath + fileName); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdir(); } try { file.transferTo(dest); } catch (IOException e) { log.error("上传文件过程中发生异常!!", e); } } } return new Result(true, StatusCode.OK, "上传成功!!"); }}application.properties# 端口server.port=8082# 配置单个文件、多个文件大小spring.servlet.multipart.max-file-size=100MBspring.servlet.multipart.max-request-size=100MB# 文件上传保存路径file.path=E:/test/# 取消模板文件缓存spring.thymeleaf.cache=false文件 结构目录

November 5, 2019 · 2 min · jiezi

SpringBoot2X对web的开发支持二

Spring-Boot-2-X-对-web-的开发支持(二)Spring Boot 2.X 对 web 的支持开发上章节的 Spring Boot 的入门案例,我们感受到 Spring Boot 简单的配置即可运行项目。今天了解 Spring Boot 对 web 的支持。Spring Boot 对 Web 开发的支持很全面,包括开发、测试和部署阶段都做了支持。spring-boot-starter-web是 Spring Boot 对 Web 开发提供支持的组件,主要包括 RESTful,参数校验、使用 Tomcat 作为内嵌容器器等功能。Spring Boot 2.X 常用注解说明get: 查询一些信息 post:提交一些需要服务器保存的信息put: 更新,更新一些用户信息 delete:删除信息@GetMapping = @RequestMapping(method = RequestMethod.GET)@PostMapping = @RequestMapping(method = RequestMethod.POST)@PutMapping = @RequestMapping(method = RequestMethod.PUT)@DeleteMapping = @RequestMapping(method = RequestMethod.DELETE) eg: @RequestMapping(value="/user",method = RequestMethod.GET)等同于 @GetMapping(value = "/user") 如果指定以 Post 的方式去请求,然后使用 Get 的方式(或其他非 post 请求方式)去请求的话,则会报 405 不允许访问的错误。如果不进⾏设置默认两种方式的请求都支持。Spring Boot 对 JSON的支持以及常用 JSON 注解使用JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。现在大部分的数据交互方式都采用 JSON。 而 Spring Boot 对 JSON 支持很完善,在 Web 层仅需要一个注解即可。性能:Jackson > FastJson > Gson > Json-lib 同个结构。jackson处理相关自动(在实体类字段上使用以下注解)指定字段不返回:@JsonIgnore指定日期格式:@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")空字段不返回:@JsonInclude(Include.NON_NUll) --->对于字符串类型的不返回,int类型的返回0指定别名: @JsonProperty("XXXX")Spring Boot 常见 web 中的传递参数方式① 使用 URL 进行传参:@PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中,如 URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到操作方法的入参中。 ...

November 5, 2019 · 2 min · jiezi

springboot入门一

1、Spring Boot 2.0 更新了什么基础环境升级 Spring Boot 2.0 要求 Java 8 作为最低版本。Spring Boot 2.0 通过了在 JDK 9 下的测试,可以在 JDK 9 下正常运行,同时 Spring Boot 2.0 宣布不再⽀支持Java 6 和 7,最低 JDK 8,支持 JDK 9。依赖组件升级 本次 Spring Boot 2.0 的升级,同时也升级了部分其依赖的第三方组件,主要有以下几个:Jetty 9.4,Jetty 是⼀个开源的 Servlet 容器器,它是基于 Java 的 Web 内容,例如 JSP 和 Servlet 提供运行环境。Jetty 是使用 Java 语言编写的,它的 API 以一组 JAR 包的形式发布。Tomcat 8.5,Apache Tomcat 8.5.x 旨在取代 8.0.x,完全⽀持 Java 9。Flyway 5,Flyway 是独⽴于数据库的应用、管理并跟踪数据库变更的数据库版本管理工具。用通俗的话讲,Flyway 可以像 SVN 管理不同人的代码那样,管理不同人的 SQL 脚本,从而做到数据库同步。Hibernate 5.2,Hibernate 是⼀一款非常流行的 ORM 框架。Gradle 3.4,Spring Boot 的 Gradle 插件在很⼤大程度上已被重写,有了了重⼤大的改进。Thymeleaf 3.0,Thymeleaf 3 相对于 Thymeleaf 2 有非常大的性能提升。默认软件替换和优化 ...

November 5, 2019 · 3 min · jiezi

springbootplus是易于使用快速高效功能丰富开源的spring-boot-脚手架

spring-boot-plus是一套集成spring boot常用开发组件的后台快速开发框架Spring-Boot-Plus是易于使用,快速,高效,功能丰富,开源的spring boot 脚手架.前后端分离,专注于后端服务 目标每个人都可以独立、快速、高效地开发项目!版本库GITHUB | GITEE官网springboot.plus主要特性集成spring boot 常用开发组件集、公共配置、AOP日志等集成mybatis plus快速dao操作快速生成后台代码: entity/param/vo/controller/service/mapper/xml集成swagger2,可自动生成api文档集成jwt、shiro/spring security权限控制集成redis、spring cache、ehcache缓存集成rabbit/rocket/kafka mq消息队列集成druid连接池,JDBC性能和慢查询检测集成spring boot admin,实时检测项目运行情况使用assembly maven插件进行不同环境打包部署,包含启动、重启命令,配置文件提取到外部config目录项目架构 项目环境中间件版本备注JDK1.8+JDK1.8及以上 MySQL5.7+5.7及以上 Redis3.2+ 技术选型技术版本备注Spring Boot2.2.0.RELEASE最新发布稳定版 Spring Framework5.2.0.RELEASE最新发布稳定版 Mybatis3.5.2持久层框架 Mybatis Plus3.2.0mybatis增强框架 Alibaba Druid1.1.20数据源 Fastjson1.2.62JSON处理工具集 swagger22.6.1api文档生成工具 commons-lang33.9常用工具包 commons-io2.6IO工具包 commons-codec1.13加密解密等工具包 commons-collections44.4集合工具包 reflections0.9.11反射工具包 hibernate-validator6.0.17.Final后台参数校验注解 Shiro1.4.1权限控制 JWT3.8.3JSON WEB TOKEN hutool-all5.0.3常用工具集 lombok1.18.10注解生成Java Bean等工具 mapstruct1.3.1.Final对象属性复制工具 CHANGELOGCHANGELOG.mdJava DocsJava Api Docs使用克隆 spring-boot-plusgit clone https://github.com/geekidea/spring-boot-plus.gitcd spring-boot-plusMaven 构建默认使用local环境,对应配置文件:application-local.ymlmvn clean package -Plocal5分钟完成增删改查1. 创建数据库表-- ------------------------------ Table structure for foo_bar-- ----------------------------DROP TABLE IF EXISTS `foo_bar`;CREATE TABLE `foo_bar`( `id` bigint(20) NOT NULL COMMENT '主键', `name` varchar(20) NOT NULL COMMENT '名称', `foo` varchar(20) DEFAULT NULL COMMENT 'Foo', `bar` varchar(20) NOT NULL COMMENT 'Bar', `remark` varchar(200) DEFAULT NULL COMMENT '备注', `state` int(11) NOT NULL DEFAULT '1' COMMENT '状态,0:禁用,1:启用', `version` int(11) NOT NULL DEFAULT '0' COMMENT '版本', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT NULL COMMENT '修改时间', PRIMARY KEY (`id`)) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT ='FooBar';-- ------------------------------ Records of foo_bar-- ----------------------------INSERT INTO foo_bar (id, name, foo, bar, remark, state, version, create_time, update_time) VALUES (1, 'FooBar', 'foo', 'bar', 'remark...', 1, 0, '2019-11-01 14:05:14', null);INSERT INTO foo_bar (id, name, foo, bar, remark, state, version, create_time, update_time) VALUES (2, 'HelloWorld', 'hello', 'world', null, 1, 0, '2019-11-01 14:05:14', null);2.使用代码生成器生成增删改查代码修改数据库信息修改组件名称/作者/数据库表名称/主键id ...

November 3, 2019 · 3 min · jiezi

Spring-MVC-统一响应格式源码分析以及问题解决

快速上手一般我们在写服务的时候,会有统一的返回格式,在这篇文章中用RespMsg表示。 比如根据用户的id要获取用户的信息。 第一版代码如下 @PostMapping(value = "user/{id}")public RespMsg getUserById(@PathVariable Long id) { try { return RespMsg.success(userService.getUserById(id)); } catch (BusinessException e) { log.error(e.getMessage(), e); return RespMsg.failed(e.getErrCode(), e.getMessage()); }}看了上一篇你真的了解spring boot全局异常处理吗,就可以改写为第二版 @PostMapping(value = "user/{id}")public RespMsg getUserById(@PathVariable Long id) throws BusinessException { return RespMsg.success(userService.getUserById(id));}看了这篇,就可以改写为第三版 @PostMapping(value = "user/{id}")public User getUserById(@PathVariable Long id) throws BusinessException { return userService.getUserById(id);}实现方式也很简单,通过实现ResponseBodyAdvice搭配@ControllerAdvice即可 @ControllerAdvicepublic class ResponseWrapperAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { //如果已经是RespMsg类型的则直接返回 if (body instanceof RespMsg) { return body; } //如果不是,则封装 return RespMsg.success(body); }}supports方法用于判断是否支持,如果不支持则不会调用beforeBodyWrite。具体可见RequestResponseBodyAdviceChain的processBody方法。既然是统一,这里都返回true了。beforeBodyWrite方法用于改写响应。然后就大功告成了,so easy。 ...

November 3, 2019 · 2 min · jiezi

Exception-in-monitor-thread-while-connecting-to-server-localhost

项目没有使用 MongoDB 却每次启动时会出现如下异常信息: 10:41:41.288 [main] INFO o.m.d.cluster - Cluster created with settings {hosts=[localhost:27017], mode=SINGLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500}10:41:41.361 [cluster-ClusterId{value='5dae6c654d4299250bde1b44', description='null'}-localhost:27017] INFO o.m.d.cluster - Exception in monitor thread while connecting to server localhost:27017com.mongodb.MongoSocketOpenException: Exception opening socket at com.mongodb.internal.connection.SocketStream.open(SocketStream.java:67) at com.mongodb.internal.connection.InternalStreamConnection.open(InternalStreamConnection.java:126) at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:117) at java.lang.Thread.run(Thread.java:748)Caused by: java.net.ConnectException: 拒绝连接 (Connection refused) at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:589) at com.mongodb.internal.connection.SocketStreamHelper.initialize(SocketStreamHelper.java:64) at com.mongodb.internal.connection.SocketStream.open(SocketStream.java:62) ... 3 common frames omitted解决方法// 在 Application 主类上添加注解// 方式一:@EnableAutoConfiguration(exclude={MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})// 方式二:@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})相关文档: ...

October 22, 2019 · 1 min · jiezi

Spring-Security-实战干货必须掌握的一些内置-Filter

1. 前言上一文我们使用 Spring Security 实现了各种登录聚合的场面。其中我们是通过在 UsernamePasswordAuthenticationFilter 之前一个自定义的过滤器实现的。我怎么知道自定义过滤器要加在 UsernamePasswordAuthenticationFilter 之前。我在这个系列开篇说了 Spring Security 权限控制的一个核心关键就是 过滤器链 ,这些过滤器如下图进行过滤传递,甚至比这个更复杂!这只是一个最小单元。 Spring Security 内置了一些过滤器,他们各有各的本事。如果你掌握了这些过滤器,很多实际开发中的需求和问题都很容易解决。今天我们来见识一下这些内置的过滤器。 2. 内置过滤器初始化在 Spring Security 初始化核心过滤器时 HttpSecurity 会通过将 Spring Security 内置的一些过滤器以 FilterComparator 提供的规则进行比较按照比较结果进行排序注册。 2.1 排序规则FilterComparator 维护了一个顺序的注册表 filterToOrder 。 FilterComparator() { Step order = new Step(INITIAL_ORDER, ORDER_STEP); put(ChannelProcessingFilter.class, order.next()); put(ConcurrentSessionFilter.class, order.next()); put(WebAsyncManagerIntegrationFilter.class, order.next()); put(SecurityContextPersistenceFilter.class, order.next()); put(HeaderWriterFilter.class, order.next()); put(CorsFilter.class, order.next()); put(CsrfFilter.class, order.next()); put(LogoutFilter.class, order.next()); filterToOrder.put( "org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter", order.next()); filterToOrder.put( "org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter", order.next()); put(X509AuthenticationFilter.class, order.next()); put(AbstractPreAuthenticatedProcessingFilter.class, order.next()); filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order.next()); filterToOrder.put( "org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter", order.next()); filterToOrder.put( "org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter", order.next()); put(UsernamePasswordAuthenticationFilter.class, order.next()); put(ConcurrentSessionFilter.class, order.next()); filterToOrder.put( "org.springframework.security.openid.OpenIDAuthenticationFilter", order.next()); put(DefaultLoginPageGeneratingFilter.class, order.next()); put(DefaultLogoutPageGeneratingFilter.class, order.next()); put(ConcurrentSessionFilter.class, order.next()); put(DigestAuthenticationFilter.class, order.next()); filterToOrder.put( "org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter", order.next()); put(BasicAuthenticationFilter.class, order.next()); put(RequestCacheAwareFilter.class, order.next()); put(SecurityContextHolderAwareRequestFilter.class, order.next()); put(JaasApiIntegrationFilter.class, order.next()); put(RememberMeAuthenticationFilter.class, order.next()); put(AnonymousAuthenticationFilter.class, order.next()); filterToOrder.put( "org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter", order.next()); put(SessionManagementFilter.class, order.next()); put(ExceptionTranslationFilter.class, order.next()); put(FilterSecurityInterceptor.class, order.next()); put(SwitchUserFilter.class, order.next()); }这些就是所有内置的过滤器。 他们是通过下面的方法获取自己的序号: ...

October 22, 2019 · 3 min · jiezi

SpringBoot-unit-1th

第一单元SpringBoot入门1【授课重点】1)SpringBoot空项目搭建,2)SpringBoot发展历史,3)热部署,4)spring-boot-starter-web,2【考核要求】1)SpringBoot空项目搭建,2)RestController与Controller讲解,3)SpringBoot jsp页面4)在boot项目里使用jstl标签3【教学内容】3.1课程导入1、为什么要有SpringBoot项目2、SpringBoot和微服务的关系(没有boot项目就没有微服务)3、公司新建项目采用的全是微服务4、Springboot项目的稳定版本和最新版本(1.5,2.x)5、SpringBoot的微服务比dubbo的微服务在开发和设计上更先进3.2SpringBoot介绍Spring Boot是一个基于Java的开源框架,用于创建微服务。它由Pivotal Team开发,用于构建独立的生产就绪Spring应用。 本章将介绍Spring Boot,并熟悉基本概念。3.2.1微服务是什么?微服务(Micro Service)是一种允许开发人员独立开发和部署服务的体系结构。每个运行的服务都有自己的流程,这实现了轻量级模型以支持业务应用程序。 例如--员工微服务--部门微服务--教师微服务--学生微服务--班级微服务----学生查询的时候,会调用班级微服务----员工查询的时候,也会调用部门微服务----思考在dubbo学习的时候,周考时,是不是让我们建立两个微服务(provider,server)?两个微服务可以分开部署,性能会大大增加。优点:Spring Boot为其开发人员提供以下优势 - 易于理解和开发Spring应用提高生产力缩短开发时间设计目标Spring Boot的设计目标如下 - 避免在Spring中进行复杂的XML配置以更简单的方式开发生产就绪的Spring应用程序隐藏和默认的方式使用Spring4减少开发时间并独立运行应用程序提供一种更简单的应用程序入门方式【前提是会用spring4,其实boot项目是易用,难精通,表面上是使用了boot项目,实际对boot,spring4,和springmvc和spring容器之间的关系难以掌握】,这里理论的内容又往往是面试的重点要想学会SpringBoot项目,一定要深刻理解Spring4,对每个注解都要充分理解。每个注解在项目里有什么功能 3.2.2为什么选择Spring Boot?选择Spring Boot,因为它提供的功能和优点如下 - 它提供了一种灵活的方法来配置Java Bean,XML配置和数据库事务。它提供强大的批处理和管理REST端点。(现在流行的rest请求,而不是我们过去学习的socket的请求方式,或者是httpclient的请求方式)在Spring Boot中,一切都是自动配置的; 无需手动配置。它提供基于注释的spring应用程序。简化依赖管理。它包括嵌入式Servlet容器。支持filter和interceptor。3.2.3Spring Boot是如何工作的?Spring Boot会根据使用@EnableAutoConfiguration批注添加到项目中的依赖项自动配置应用程序。 例如,如果MySQL数据库在类路径上,但尚未配置任何数据库连接,则Spring Boot会自动配置内存数据库。【boot 启动类】重点spring boot应用程序的入口点是包含@SpringBootApplication注释和main方法的类。Spring Boot使用@ComponentScan注释自动扫描项目中包含的所有组件。@ComponentScan(package name)扫描多个包用逗号分隔,Scan,扫描的意思3.2.4Spring Boot Starters处理依赖管理对于大项目来说是一项艰巨的任务。 Spring Boot通过提供一组依赖项来解决此问题,以方便开发人员。每一个模块,在boot项目里,都有一个对应的starter。例如,如果要使用Spring和JPA进行数据库访问,则在项目中包含spring-boot-starter-data-jpa依赖项就足够了。请注意,所有Spring Boot启动程序都遵循相同的命名模式spring-boot-starter-,其中表示它是应用程序的一种类型。例子请看下面的Spring Boot启动器,以便更好地理解 -Actuator 监控boot项目运行时占用内存的情况】Spring Boot Starter Actuator依赖关系用于监视和管理应用程序。 其代码如下所示 -<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency>XMLSpring Boot Starter Security依赖项用于Spring Security。 其代码如下所示 -<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>XMLSpring Boot Starter Web依赖项用于编写Rest端点。 其代码如下所示 -【Web starter 是非常常用的starter】<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>--这个starter非常常用--一般管这个starter叫springboot的web starterXMLSpring Boot Starter Thyme Leaf依赖项用于创建Web应用程序。 其代码如下所示 -<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>这个是springboot推荐的开发html页面的新技术。XMLSpring Boot Starter Test依赖项用于编写测试用例。 其代码如下所示 -<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test<artifactId></dependency> ...

October 18, 2019 · 1 min · jiezi

SpringBoot-unit-2th关于spring4的那些事面试重点

第二单元SpringBoot配置1【授课重点】1)Application.properties2)Application.yml3)自定义配置文件4)引入xml配置5)@Configuration2【考核要求】1)SpringBoot有配置文件的项目2)Configuration和Bean3)树形配置文件4)传统的配置文件5)Xml方式配置Bean3【教学内容】3.1课程导入1、SpringBoot有灵活的配置方式2、实际开发中选择一种简单使用的配置方式即可3、兼容传统的spring配置方式3.2创建项目pom.xml 文件的内容如下所示 - Spring Boot提供了许多Starters来在类路径中添加jar。 例如,要编写Rest Endpoint,需要在类路径中添加spring-boot-starter-web依赖项。请遵守下面显示的代码以便更好地理解 -【编写web项目必须有的starter web】 3.2.1Main方法Main方法应该是编写Spring Boot Application类。 该类应使用@SpringBootApplication进行注释。这是启动Spring启动应用程序的入口点。以在src/java/main目录下找到主类文件。在此示例中,主类文件位于src/java/main目录中,其默认包为com.yiibai.demo。 请观察此处显示的代码以便更好地理解 -【启动类有两个注解: @SpringBootApplication, @SpringApplication 】 3.2.2编写一个Rest端点【就是写一个helloworld】要在Spring Boot Application主类文件本身中编写一个简单的Hello World Rest 端点,请按照以下步骤操作 - 首先,在类的顶部添加@RestController注释。使用@RequestMapping注释编写Request URI方法。Request URI方法应该返回Hello World字符串。现在,Spring Boot Application类文件将如下面的代码所示 - 3.3创建一个可执行的JAR 创建一个可执行的JAR文件,在命令提示符下使用Maven和Gradle命令运行Spring Boot应用程序,如下所示 - 使用maven命令mvn clean install,如下所示 - 执行命令后,可以在命令提示符下看到 BUILD SUCCESS 的消息,如下所示 - 用Java运行Hello World创建可执行JAR文件后,可以在以下目录中找到它。对于Maven,可以在目标目录下找到JAR文件,如下所示 现在,使用命令java -jar <JARFILE>运行JAR文件。 请注意,在上面的示例中,JAR文件名为demo-0.0.1-SNAPSHOT.jar 运行jar文件后,可以在控制台窗口中看到输出,如下所示 现在,看一下控制台,Tomcat在端口8080(http)上启动。 现在,转到Web浏览器并点击URL => http://localhost:8080/ 3.4SpringBoot构建系统在Spring Boot中,选择构建系统是一项重要任务。建议使用Maven或Gradle,因为它们可以为依赖关系管理提供良好的支持。 Spring不支持其他构建系统。3.5依赖管理Spring Boot团队提供了一个依赖项列表,以支持每个版本的Spring Boot版本。无需在构建配置文件中提供依赖项版本。Spring Boot会根据发行版自动配置依赖项版本。 请记住,升级Spring Boot版本时,依赖项也会自动升级。注 - 如果要指定依赖项的版本,可以在配置文件中指定它。 但是,Spring Boot团队强烈建议不要指定依赖项的版本。3.6Maven依赖对于Maven配置,应该继承Spring Boot Starter父项目来管理Spring Boot Starters依赖项。 因此只需在pom.xml 文件中继承启动父级,如下所示。 ...

October 18, 2019 · 2 min · jiezi

SpringBoot-unit-1th-请深刻理解教案里的提问

第一单元SpringBoot入门1【授课重点】1)SpringBoot空项目搭建,2)SpringBoot发展历史,3)热部署,4)spring-boot-starter-web,2【考核要求】1)SpringBoot空项目搭建,2)RestController与Controller讲解,3)SpringBoot jsp页面4)在boot项目里使用jstl标签3【教学内容】3.1课程导入1、为什么要有SpringBoot项目2、SpringBoot和微服务的关系(没有boot项目就没有微服务)3、公司新建项目采用的全是微服务4、Springboot项目的稳定版本和最新版本(1.5,2.x)5、SpringBoot的微服务比dubbo的微服务在开发和设计上更先进3.2SpringBoot介绍Spring Boot是一个基于Java的开源框架,用于创建微服务。它由Pivotal Team开发,用于构建独立的生产就绪Spring应用。 本章将介绍Spring Boot,并熟悉基本概念。3.2.1微服务是什么?微服务(Micro Service)是一种允许开发人员独立开发和部署服务的体系结构。每个运行的服务都有自己的流程,这实现了轻量级模型以支持业务应用程序。 例如--员工微服务--部门微服务--教师微服务--学生微服务--班级微服务----学生查询的时候,会调用班级微服务----员工查询的时候,也会调用部门微服务----思考在dubbo学习的时候,周考时,是不是让我们建立两个微服务(provider,server)?两个微服务可以分开部署,性能会大大增加。优点:Spring Boot为其开发人员提供以下优势 - 易于理解和开发Spring应用提高生产力缩短开发时间设计目标Spring Boot的设计目标如下 - 避免在Spring中进行复杂的XML配置以更简单的方式开发生产就绪的Spring应用程序隐藏和默认的方式使用Spring4减少开发时间并独立运行应用程序提供一种更简单的应用程序入门方式【前提是会用spring4,其实boot项目是易用,难精通,表面上是使用了boot项目,实际对boot,spring4,和springmvc和spring容器之间的关系难以掌握】,这里理论的内容又往往是面试的重点要想学会SpringBoot项目,一定要深刻理解Spring4,对每个注解都要充分理解。每个注解在项目里有什么功能 3.2.2为什么选择Spring Boot?选择Spring Boot,因为它提供的功能和优点如下 - 它提供了一种灵活的方法来配置Java Bean,XML配置和数据库事务。它提供强大的批处理和管理REST端点。(现在流行的rest请求,而不是我们过去学习的socket的请求方式,或者是httpclient的请求方式)在Spring Boot中,一切都是自动配置的; 无需手动配置。它提供基于注释的spring应用程序。简化依赖管理。它包括嵌入式Servlet容器。支持filter和interceptor。3.2.3Spring Boot是如何工作的?Spring Boot会根据使用@EnableAutoConfiguration批注添加到项目中的依赖项自动配置应用程序。 例如,如果MySQL数据库在类路径上,但尚未配置任何数据库连接,则Spring Boot会自动配置内存数据库。【boot 启动类】重点spring boot应用程序的入口点是包含@SpringBootApplication注释和main方法的类。Spring Boot使用@ComponentScan注释自动扫描项目中包含的所有组件。@ComponentScan(package name)扫描多个包用逗号分隔,Scan,扫描的意思3.2.4Spring Boot Starters处理依赖管理对于大项目来说是一项艰巨的任务。 Spring Boot通过提供一组依赖项来解决此问题,以方便开发人员。每一个模块,在boot项目里,都有一个对应的starter。例如,如果要使用Spring和JPA进行数据库访问,则在项目中包含spring-boot-starter-data-jpa依赖项就足够了。请注意,所有Spring Boot启动程序都遵循相同的命名模式spring-boot-starter-,其中表示它是应用程序的一种类型。例子请看下面的Spring Boot启动器,以便更好地理解 -Actuator 监控boot项目运行时占用内存的情况】Spring Boot Starter Actuator依赖关系用于监视和管理应用程序。 其代码如下所示 -<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency>XMLSpring Boot Starter Security依赖项用于Spring Security。 其代码如下所示 -<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>XMLSpring Boot Starter Web依赖项用于编写Rest端点。 其代码如下所示 -【Web starter 是非常常用的starter】<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>--这个starter非常常用--一般管这个starter叫springboot的web starterXMLSpring Boot Starter Thyme Leaf依赖项用于创建Web应用程序。 其代码如下所示 -<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>这个是springboot推荐的开发html页面的新技术。XMLSpring Boot Starter Test依赖项用于编写测试用例。 其代码如下所示 -<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test<artifactId></dependency> ...

October 18, 2019 · 1 min · jiezi

SpringBootVue-unit-1th-请深刻理解教案里的提问

第一单元SpringBoot入门1【授课重点】1)SpringBoot空项目搭建,2)SpringBoot发展历史,3)热部署,4)spring-boot-starter-web,2【考核要求】1)SpringBoot空项目搭建,2)RestController与Controller讲解,3)SpringBoot jsp页面4)在boot项目里使用jstl标签3【教学内容】3.1课程导入1、为什么要有SpringBoot项目2、SpringBoot和微服务的关系(没有boot项目就没有微服务)3、公司新建项目采用的全是微服务4、Springboot项目的稳定版本和最新版本(1.5,2.x)5、SpringBoot的微服务比dubbo的微服务在开发和设计上更先进3.2SpringBoot介绍Spring Boot是一个基于Java的开源框架,用于创建微服务。它由Pivotal Team开发,用于构建独立的生产就绪Spring应用。 本章将介绍Spring Boot,并熟悉基本概念。3.2.1微服务是什么?微服务(Micro Service)是一种允许开发人员独立开发和部署服务的体系结构。每个运行的服务都有自己的流程,这实现了轻量级模型以支持业务应用程序。 例如--员工微服务--部门微服务--教师微服务--学生微服务--班级微服务----学生查询的时候,会调用班级微服务----员工查询的时候,也会调用部门微服务----思考在dubbo学习的时候,周考时,是不是让我们建立两个微服务(provider,server)?两个微服务可以分开部署,性能会大大增加。优点:Spring Boot为其开发人员提供以下优势 - 易于理解和开发Spring应用提高生产力缩短开发时间设计目标Spring Boot的设计目标如下 - 避免在Spring中进行复杂的XML配置以更简单的方式开发生产就绪的Spring应用程序隐藏和默认的方式使用Spring4减少开发时间并独立运行应用程序提供一种更简单的应用程序入门方式【前提是会用spring4,其实boot项目是易用,难精通,表面上是使用了boot项目,实际对boot,spring4,和springmvc和spring容器之间的关系难以掌握】,这里理论的内容又往往是面试的重点要想学会SpringBoot项目,一定要深刻理解Spring4,对每个注解都要充分理解。每个注解在项目里有什么功能 3.2.2为什么选择Spring Boot?选择Spring Boot,因为它提供的功能和优点如下 - 它提供了一种灵活的方法来配置Java Bean,XML配置和数据库事务。它提供强大的批处理和管理REST端点。(现在流行的rest请求,而不是我们过去学习的socket的请求方式,或者是httpclient的请求方式)在Spring Boot中,一切都是自动配置的; 无需手动配置。它提供基于注释的spring应用程序。简化依赖管理。它包括嵌入式Servlet容器。支持filter和interceptor。3.2.3Spring Boot是如何工作的?Spring Boot会根据使用@EnableAutoConfiguration批注添加到项目中的依赖项自动配置应用程序。 例如,如果MySQL数据库在类路径上,但尚未配置任何数据库连接,则Spring Boot会自动配置内存数据库。【boot 启动类】重点spring boot应用程序的入口点是包含@SpringBootApplication注释和main方法的类。Spring Boot使用@ComponentScan注释自动扫描项目中包含的所有组件。@ComponentScan(package name)扫描多个包用逗号分隔,Scan,扫描的意思3.2.4Spring Boot Starters处理依赖管理对于大项目来说是一项艰巨的任务。 Spring Boot通过提供一组依赖项来解决此问题,以方便开发人员。每一个模块,在boot项目里,都有一个对应的starter。例如,如果要使用Spring和JPA进行数据库访问,则在项目中包含spring-boot-starter-data-jpa依赖项就足够了。请注意,所有Spring Boot启动程序都遵循相同的命名模式spring-boot-starter-,其中表示它是应用程序的一种类型。例子请看下面的Spring Boot启动器,以便更好地理解 -Actuator 监控boot项目运行时占用内存的情况】Spring Boot Starter Actuator依赖关系用于监视和管理应用程序。 其代码如下所示 -<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency>XMLSpring Boot Starter Security依赖项用于Spring Security。 其代码如下所示 -<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>XMLSpring Boot Starter Web依赖项用于编写Rest端点。 其代码如下所示 -【Web starter 是非常常用的starter】<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>--这个starter非常常用--一般管这个starter叫springboot的web starterXMLSpring Boot Starter Thyme Leaf依赖项用于创建Web应用程序。 其代码如下所示 -<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>这个是springboot推荐的开发html页面的新技术。XMLSpring Boot Starter Test依赖项用于编写测试用例。 其代码如下所示 -<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test<artifactId></dependency> ...

October 18, 2019 · 1 min · jiezi

Spring-Security-实战干货玩转自定义登录

1. 前言前面的关于 Spring Security 相关的文章只是一个预热。为了接下来更好的实战,如果你错过了请从 Spring Security 实战系列 开始。安全访问的第一步就是认证(Authentication),认证的第一步就是登录。今天我们要通过对 Spring Security 的自定义,来设计一个可扩展,可伸缩的 form 登录功能。 2. form 登录的流程下面是 form 登录的基本流程: 只要是 form 登录基本都能转化为上面的流程。接下来我们看看 Spring Security 是如何处理的。 3. Spring Security 中的登录昨天 Spring Security 实战干货:自定义配置类入口WebSecurityConfigurerAdapter 中已经讲到了我们通常的自定义访问控制主要是通过 HttpSecurity 来构建的。默认它提供了三种登录方式: formLogin() 普通表单登录oauth2Login() 基于 OAuth2.0 认证/授权协议openidLogin() 基于 OpenID 身份认证规范以上三种方式统统是 AbstractAuthenticationFilterConfigurer 实现的, 4. HttpSecurity 中的 form 表单登录启用表单登录通过两种方式一种是通过 HttpSecurity 的 apply(C configurer) 方法自己构造一个 AbstractAuthenticationFilterConfigurer 的实现,这种是比较高级的玩法。 另一种是我们常见的使用 HttpSecurity 的 formLogin() 方法来自定义 FormLoginConfigurer 。我们先搞一下比较常规的第二种。 ...

October 18, 2019 · 4 min · jiezi

一文读懂Spring事务管理器

为什么需要事务管理器如果没有事务管理器的话,我们的程序可能是这样: Connection connection = acquireConnection();try{ int updated = connection.prepareStatement().executeUpdate(); connection.commit();}catch (Exception e){ rollback(connection);}finally { releaseConnection(connection);}也有可能是这样"优雅的事务": execute(new TxCallback() { @Override public Object doInTx(Connection var1) { //do something... return null; }});public void execute(TxCallback txCallback){ Connection connection = acquireConnection(); try{ txCallback.doInTx(connection); connection.commit(); }catch (Exception e){ rollback(connection); }finally { releaseConnection(connection); }}# lambda版execute(connection -> { //do something... return null;});但是以上两种方式,针对一些复杂的场景是很不方便的。在实际的业务场景中,往往有比较复杂的业务逻辑,代码冗长,逻辑关联复杂,如果一个大操作中有全是这种代码的话我想开发人员可能会疯把。更不用提定制化的隔离级别,以及嵌套/独立事务的处理了。 Spring 事务管理器简介Spring作为Java最强框架,事务管理也是其核心功能之一。Spring为事务管理提供了统一的抽象,有以下优点: 跨不同事务API(例如Java事务API(JTA),JDBC,Hibernate,Java持久性API(JPA)和Java数据对象(JDO))的一致编程模型。支持声明式事务管理(注解形式)与JTA之类的复杂事务API相比, 用于程序化事务管理的API更简单和Spring的Data层抽象集成方便(比如Spring - Hibernate/Jdbc/Mybatis/Jpa...)使用方式事务,自然是控制业务的,在一个业务流程内,往往希望保证原子性,要么全成功要么全失败。 所以事务一般是加载@Service层,一个Service内调用了多个操作数据库的操作(比如Dao),在Service结束后事务自动提交,如有异常抛出则事务回滚。 这也是Spring事务管理的基本使用原则。 下面贴出具体的使用代码: 注解在被Spring管理的类头上增加@Transactional注解,即可对该类下的所有方法开启事务管理。事务开启后,方法内的操作无需手动开启/提交/回滚事务,一切交给Spring管理即可。 @Service@Transactionalpublic class TxTestService{ @Autowired private OrderRepo orderRepo; public void submit(Order order){ orderRepo.save(order); }}也可以只在方法上配置,方法配置的优先级是大于类的 ...

October 17, 2019 · 2 min · jiezi

MongoDB系列轻松应对面试中遇到的MongonDB索引index问题

索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中( 索引存储在特定字段或字段集的值),而且是使用了B-tree结构。索引可以极大程度提升MongoDB查询效率。 如果没有索引,MongoDB必须执行全集合collections扫描,即扫描集合中的每个文档,选取符合查询条件的文档document。 如果查询时存在适当的索引,MongoDB可以使用索引来限制它必须查询的文档document的数量,特别是在处理大量数据时,所以选择正确的索引是很关键的、重要的。 创建索引,需要考虑的问题: 每个索引至少需要数据空间为8kb;添加索引会对写入操作会产生一些性能影响。 对于具有高写入率的集合Collections,索引很昂贵,因为每个插入也必须更新任何索引;索引对于具有高读取率的集合Collections很有利,不会影响没索引查询;处于索引处于action状态时,每个索引都会占用磁盘空间和内存,因此需要对这种情况进行跟踪检测。索引限制: 索引名称长度不能超过128字段;复合索引不能超过32个属性;每个集合Collection不能超过64个索引;不同类型索引还具有各自的限制条件。1. 索引管理1.1 索引创建索引创建使用createIndex()方法,格式如下: db.collection.createIndex(<key and index type specification>,<options>)createIndex() 接收可选参数,可选参数列表如下: ParameterTypeDescriptionbackgroundBoolean建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false。uniqueBoolean建立的索引是否唯一。指定为true创建唯一索引。默认值为false.namestring索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。dropDupsBoolean3.0+版本已废弃。在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false.sparseBoolean对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false.expireAfterSecondsinteger指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。vindex version索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。weightsdocument索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。default_languagestring对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语language_overridestring对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language.1.2 查看索引查看Collection中所有索引,格式如下: db.collection.getIndexes()1.3 删除索引删除Collection中的索引:格式如下: db.collection.dropIndexes() //删除所有索引db.collection.dropIndex() //删除指定的索引 1.4 索引名称索引的默认名称是索引键和索引中每个键的value1或-1,形式index_name+1/-1,比如: db.products.createIndex( { item: 1, quantity: -1 } )----索引名称为item_1_quantity_-1也可以指定索引名称: db.products.createIndex( { item: 1, quantity: -1 } , { name: "inventory" } ) ----索引名称为inventory1.5 查看索引创建过程以及终止索引创建方法解析db.currentOp()查看索引创建过程db.killOp(opid)终止索引创建,其中-opid为操作id1.6 索引使用情况形式解析&dollar;indexStats获取索引访问信息explain()返回查询情况:在executionStats模式下使用db.collection.explain()或cursor.explain()方法返回有关查询过程的统计信息,包括使用的索引,扫描的文档数以及查询处理的时间(以毫秒为单位)。Hint()控制索引,例如要强制MongoDB使用特定索引进行db.collection.find()操作,请使用hint()方法指定索引1.7 MongoDB度量标准MongoDB提供了许多索引使用和操作的度量标准,在分析数据库的索引使用时可能需要考虑这些度量标准,如下所示: 形式解析metrics.queryExecutor.scanned在查询和查询计划评估期间扫描的索引项的总数metrics.operation.scanAndOrder返回无法使用索引执行排序操作的已排序数字的查询总数collStats.totalIndexSize所有索引的总大小。 scale参数会影响此值。如果索引使用前缀压缩(这是WiredTiger的默认值),则返回的大小将反映计算总计时任何此类索引的压缩大小。collStats.indexSizes指定集合collection上每个现有索引的键和大小。 scale参数会影响此值dbStats.indexes包含数据库中所有集合的索引总数的计数。dbStats.indexSize在此数据库上创建的所有索引的总大小1.8 后台索引操作 在密集(快达到数据库最大容量)Collection创建索引:在默认情况下,在密集的Collection(快达到数据库最大容量)时创建索引,会阻止其他操作。在给密集的Collection(快达到数据库最大容量)创建索引时,索引构建完成之前,保存Collection的数据库不可用于读取或写入操作。 任何需要对所有数据库(例如listDatabases)进行读或写锁定的操作都将等待不是后台进程的索引构建完成。 ...

October 17, 2019 · 6 min · jiezi

Spring-Boot-十五-优雅的使用-API-文档工具-Swagger2

1. 引言各位在开发的过程中肯定遇到过被接口文档折磨的经历,由于 RESTful 接口的轻量化以及低耦合性,我们在修改接口后文档更新不及时,导致接口的调用方(无论是前端还是后端)经常抱怨接口与文档不一致。程序员的特点是特别不喜欢写文档,但是又同时特别不喜欢别人不写文档。所以 API 文档工具这时就应运而生了,本篇文章我们将会介绍 API 文档工具 Swagger2 。 2. 快速上手既然 Swagger2 是一个 API 文档工具,我们就在代码中看一下这个文档工具在 Spring Boot 中是如何使用的吧。 2.1 引入依赖代码清单:spring-boot-swagger/pom.xml <!-- swagger工具包 --><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${swagger.version}</version></dependency><!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui --><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${swagger.version}</version></dependency>这里选用的版本是 2.9.2 ,同时也是目前最新的一个版本。 2.2 配置类 SwaggerConfig代码清单:spring-boot-swagger/src/main/java/com/springboot/springbootswagger/config/SwaggerConfig.java @Configuration@EnableSwagger2public class SwaggerConfig { @Value("${swagger.show}") private boolean swaggerShow; @Bean public Docket swaggerSpringMvcPlugin() { return new Docket(DocumentationType.SWAGGER_2) .enable(swaggerShow) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.springboot.springbootswagger")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("Swagger2 演示接口RESTful APIs") .version("1.0") .build(); }}由于 Swagger 是一个 API 文档工具,我们肯定不能在生产环境中开启,所以笔者这里在配置中增加了 swagger.show ,在不同环境的配置文件中配置不同的值,或者如果有配置中心,这个配置可以添加到配置中心中,笔者这里示例简单起见就添加在 application 配置文件中了。这样,我们就可以优雅的开启或者关闭 Swagger 的功能。 ...

October 17, 2019 · 2 min · jiezi

使用JustAuth在第三方登录中如何实现校验state

前言本文利用到的JustAuth的传送门。 本文纯属菜鸡视角。在开发者相当简略的官方使用文档的基础上,进入源码查看文档中使用的函数的具体实现,同时通过QQ第三方登录这一特例,工具开发者非常规范的命名和注释,推测整个工具的实现逻辑。 绝大部分第三方登录采用OAuth2.0协议,其流程符合如下流程图:关于OAuth2.0流程复杂化了(用户授权登录后,服务器不能直接拿到可以唯一标识用户的id)登录流程,到底在安全性上如何提供了好处,请自行谷歌。 A阶段跳转到QQ的授权登录网页必需参数 response_type client_id redirect_uri state其中response_type为一定值 B阶段用户授权登录后,腾讯那边带上必要的数据以GET参数的模型通过GET访问我们设定的返回地址。得到的数据 code state并要校验发回的state与A阶段的state是否相同 正文 准备阶段 // 官方文档中并未有此函数,只是我自用的。 private AuthQqRequest getAuthQqRequest(){ String client_id = 填入你自己的client_id; String redirect_uri = 填入你自己的redirect_url; String client_secret = 填入你自己的client_secret; AuthConfig build = AuthConfig.builder() .clientId(client_id) .clientSecret(client_secret) .redirectUri(redirect_uri) .build(); return new AuthQqRequest(build); } A阶段/** * 官方伪代码 */@RequestMapping("/render/{source}")public void renderAuth(@PathVariable("source") String source, HttpServletResponse response) throws IOException { AuthRequest authRequest = getAuthRequest(source); String authorizeUrl = authRequest.authorize(AuthStateUtils.createState()); response.sendRedirect(authorizeUrl);} /** * 我的具体到QQ上的实现 * 因为我胸无大志只想着QQ所以不需要用{source}来确定我在用谁的(是微信啊,还是QQ啊还是gitee啊)的第三方登录功能。 */ @RequestMapping("/render") public void render(HttpServletResponse resp) throws IOException { AuthQqRequest authQqRequest = getAuthQqRequest(); resp.sendRedirect(authQqRequest.authorize(AuthStateUtils.createState()); } } ...

October 16, 2019 · 3 min · jiezi

Spring-Boot-十四-响应式编程以及-Spring-Boot-Webflux-快速入门

1. 什么是响应式编程在计算机中,响应式编程或反应式编程(英语:Reactive programming)是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。 例如,在命令式编程环境中,a=b+c 表示将表达式的结果赋给 a,而之后改变 b 或 c 的值不会影响 a 。但在响应式编程中,a 的值会随着 b 或 c 的更新而更新。 响应式编程是基于异步和事件驱动的非阻塞程序,只需要在程序内启动少量线程扩展,而不是水平通过集群扩展。 设想一个场景,从底层数据库驱动,经过持久层、服务层、MVC层中的model,到用户的前端界面的元素,全部都采用声明式的编程范式,从而搭建一条能够传递变化的管道,这样我们只要更新一下数据库中的数据,用户的界面上就相应的发生变化,从而无需前端轮询才能获取到最新的数据。 简单来讲,我们以前写的程序是阻塞式的,当一个请求任务过来时,线程会阻塞,等到这个任务完成后再返回出去。而响应式编程则是一个请求任务过来时,会有其他的线程去做处理,当任务执行结束后再异步的通知回去。 2. 为什么要使用响应式编程在如今互联网时代的大背景下,Web应用通常要面对高并发、海量数据的挑战,性能从来都是必须要考量的核心因素。 阻塞便是性能杀手之一。 多数人不认为阻塞是一个比较大的问题,至少觉得除了网络I/O之外,读写文件和数据库还是很快的,许多人也一直在写阻塞的代码。 那么 I/O 操作具体有多慢? 2.1 CPU眼中的时间以下内容来源 https://blog.csdn.net/get_set...CPU绝对称得上是“闪电侠”,因为它们做事都有自己的一套时钟。我们故事的主人公是一个主频为2.5GHz的CPU,如果它的世界也有“秒”的概念,并且它的时钟跳一下为一秒,那么在CPU(CPU的一个核心)眼中的时间概念是啥样的呢? CPU先生所在的组是硬件部计算组。对它来说,与其一起紧密合作的几个小伙伴还能跟的上它的节奏: CPU先生很利索,只需要一秒就可以完成一个指令,复杂的动作可能需要多个指令。好在“贴身秘书”一级缓存反应比较快,能够秒懂CPU先生的意思。来自“秘书组”的二级缓存虽然要十几秒才能“get”到CPU先生的点,但也不算太迟钝。和内存组的合作已经习以为常了,跟内存请求的数据通常要4-5分钟才能找到(内存寻址),不过也还好啦,毕竟一级缓存那里能拿到80%想要的数据,其余的二级缓存也能搞定一大部分,不怎么耽误事儿。CPU先生是典型的工作狂,任务多的时候,通宵达旦也毫无怨言,但是有什么事情让它等,那简直要他命了。恰恰一起共事的其他组(尤其是I/O组的磁盘和网卡)相对来说那效率是低的离谱啊: 关于I/O组的同事,CPU先生已经抱怨很久了,每次找SSD要东西,都要花费4-5天才能找到(寻址),等到数据传送过来,几周都过去了。机械磁盘更是过分地离谱,跟他要个数据,竟然要平均花费10个月才能找到,如果要读取1M的数据,竟然要20个月!这种员工怎么还不下岗?!关于网卡,CPU先生知道它们也尽力了,毕竟万兆网络成本颇高。与机房内的其他小伙伴们用千兆网络互相沟通也算顺畅,给另一台机器的CPU朋友发送1K的书信,最快七八个小时就可以送过去了。但是1K的书信经过层层包裹,实际也写不了多少话。更要命的是,网卡们的沟通手续繁杂,每次网络沟通前的 “你好能听到我吗?——我能听到,你那边能听到我吗?——我也能听到你,那我们开始吧!” 这样的握手确认都要花掉很长的时间,不过不能当面沟通,也只能这样了。这还好,最恐怖的是与其他城市的小伙伴沟通,有时候传递消息要花费好几年呢!由此可见,对于CPU先生来说,想要让工作充实起来实在不容易,不过多亏了内存组的小伙伴帮忙分批缓存往返于I/O组的数据,矛盾才有所缓解。 这个图只能明显看出涉及I/O的时间条,我们转换成对数刻度的图看一下: 这个图并不是直观的比例,横轴上每个刻度是一个数量级,可见I/O的速度与CPU和内存相比是要差几个数量级的。由此可见,对于大型高并发场景下的Web应用,缓存有多重要,更高的缓存命中率就意味着性能。 并行化:使用更多的线程和硬件资源;异步化:基于现有的资源来提高执行效率。3. 基础概念在介绍主题之前先普及几个概念: 3.1 Backpressure(背压)背压是一种常用策略,使得发布者拥有无限制的缓冲区存储元素,用于确保发布者发布元素太快时,不会去压制订阅者。 3.2 Reactive Streams(响应式流)一般由以下组成: 发布者:发布元素到订阅者订阅者:消费元素订阅:在发布者中,订阅被创建时,将与订阅者共享处理器:发布者与订阅者之间处理数据3.3 Mono 和 FluxMono:实现发布者,并返回 0 或 1 个元素Flux:实现发布者,并返回 N 个元素4. Spring WebfluxSpring Boot Webflux 是基于 Reactor 实现的。Spring Boot 2.0 包括一个新的 spring-webflux 模块。该模块包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对 REST,HTML 和 WebSocket 交互等程序的支持。一般来说,Spring MVC 用于同步处理,Spring Webflux 用于异步处理。 ...

October 16, 2019 · 1 min · jiezi

Spring-Boot-十三-Spring-Boot-整合-RabbitMQ

1. 前言RabbitMQ 是一个消息队列,说到消息队列,大家可能多多少少有听过,它主要的功能是用来实现应用服务的异步与解耦,同时也能起到削峰填谷、消息分发的作用。 消息队列在比较主要的一个作用是用来做应用服务的解耦,消息从消息的生产者传递到消息队列,消费者从消息队列中获取消息并进行消费,生产者不需要管是谁在消费消息,消费者也无需关注消息是由谁来生产的。在分布式的系统中,消息队列也会被用在其他地方,比如分布式事务的支持,代表如阿里开源的 RocketMQ 。 当然,我们本篇文章的主角还是 RabbitMQ 。 2. RabbitMQ 介绍RabbitMQ 是实现 AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 RabbitMQ 主要是为了实现系统之间的双向解耦而实现的。当生产者大量产生数据时,消费者无法快速消费,那么需要一个中间层。保存这个数据。 AMQP,即 Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。AMQP 的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 RabbitMQ 是一个开源的 AMQP 实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP 等,支持 AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 3. 概念介绍在普通的消息队列的设计中,一般会有这么几个概念:生产者、消费者和我们的队列。但是在 RabbitMQ 中,中间增加了一层,叫交换机(Exchange),这样,消息的投递就不由生产者来决定投递至哪个队列,而消息是直接投递至交换机的,由交换机根据调度策略来决定这个消息需要投递到哪个队列。如图: 左侧的 P 代表消息的生产者紫色的 X 代表交换机右侧红色的代表队列4. 交换机(Exchange)那么为什么我们需要 Exchange 而不是直接将消息发送至队列呢? AMQP 协议中的核心思想就是生产者和消费者的解耦,生产者从不直接将消息发送给队列。生产者通常不知道是否一个消息会被发送到队列中,只是将消息发送到一个交换机。先由 Exchange 来接收,然后 Exchange 按照特定的策略转发到 Queue 进行存储。 Exchange 收到消息时,他是如何知道需要发送至哪些 Queue 呢?这里就需要了解 Binding 和 RoutingKey 的概念: Binding 表示 Exchange 与 Queue 之间的关系,我们也可以简单的认为队列对该交换机上的消息感兴趣,绑定可以附带一个额外的参数 RoutingKey。Exchange 就是根据这个 RoutingKey 和当前 Exchange 所有绑定的 Binding 做匹配,如果满足匹配,就往 Exchange 所绑定的 Queue 发送消息,这样就解决了我们向 RabbitMQ 发送一次消息,可以分发到不同的 Queue。RoutingKey 的意义依赖于交换机的类型。 ...

October 15, 2019 · 3 min · jiezi

Spring-Security-实战干货路径Uri中的-Ant-风格

1. 前言我们经常在读到一些文章会遇到uri 支持 Ant 风格 ,而且这个东西在 Spring MVC 和 Spring Security 中经常被提及。这到底是什么呢?今天我们来学习了解一下。这对我们学习 Spring MVC 和 Spring Security 十分必要。 2. Ant 风格说白了 Ant 风格就是一种路径匹配表达式。主要用来对uri的匹配。其实跟正则表达式作用是一样的,只不过正则表达式适用面更加宽泛,Ant仅仅用于路径匹配。 3. Ant 通配符Ant 中的通配符有三种: ? 匹配任何单字符* 匹配0或者任意数量的 字符** 匹配0或者更多的 目录这里注意了单个* 是在一个目录内进行匹配。 而** 是可以匹配多个目录,一定不要迷糊。 3.1 Ant 通配符示例通配符示例说明?/ant/p?ttern匹配项目根路径下 /ant/pattern 和 /ant/pXttern,但是不包括/ant/pttern*/ant/*.html匹配项目根路径下所有在ant路径下的.html文件*/ant/*/path/ant/path、/ant/a/path、/ant/bxx/path 都匹配,不匹配 /ant/axx/bxx/path**/ant/**/path/ant/path、/ant/a/path、/ant/bxx/path 、/ant/axx/bxx/path都匹配3.2 最长匹配原则从 3.1 可以看出 * 和 ** 是有冲突的情况存在的。为了解决这种冲突就规定了最长匹配原则(has more characters)。 一旦一个uri 同时符合两个Ant匹配那么走匹配规则字符最多的。为什么走最长?因为字符越长信息越多就越具体。比如 /ant/a/path 同时满足 /**/path 和 /ant/*/path 那么走/ant/*/path 4. Spring MVC 和 Spring Security 中的 Ant 风格接下来我们来看看 Spring MVC 和 Spring Security 下的 Ant风格。 ...

October 15, 2019 · 1 min · jiezi

微人事-star-数超-10k如何打造一个-star-数超-10k-的开源项目

看了下,微人事(https://github.com/lenve/vhr)... star 数超 10k 啦,松哥第一个 star 数过万的开源项目就这样诞生了。 两年前差不多就是现在这个时候,松哥所在的公司业绩下滑严重,关门倒闭已成定局,很多同事在谋划的新的出路,松哥则被公司留下来善后,在一段并不太忙碌的日子里,做了两个 Spring Boot + Vue 的前后端分离开源项目,以期能给自己来年找工作增加一点筹码,没想到这两个项目后来受到很多关注,也帮助了很多人。有不少小伙伴在公司使用微人事项目做脚手架开发项目,也有国内 top20 的高校研究生借鉴微人事做毕设,我自己也因此收到一些大厂的橄榄枝,可以说还是收获满满。 关于这个项目诞生的故事,松哥之前写过一篇文章,感兴趣的小伙伴可以看看: 公司倒闭 1 年了,而我当年的项目上了 GitHub 热榜今天,我想和小伙伴们聊聊如何从零开始打造一个 star 数过万的开源项目。松哥把这些经验总结为三点: 文档详细项目有料适当宣传这些经验不是什么惊世骇俗的大道理,都很普通,关键在于执行。 1. 文档详细其实在做微人事和 V 部落之前,松哥在 GitHub 上已经做过多个开源项目了,比较有意思的一个是一个 Android 上的自定义控件,我做了一个歌词展示的控件,这个控件引入到自己的项目中以后,可以根据当前歌曲的播放进度动态滚动歌词,效果如如下: 还有一个比较好玩的就是 Android 上自动抢红包的 App。不过这些开源工具和项目最终都石沉大海了。 究其原因,我觉得是自己对待这些项目不够认真,项目开源之后基本上都没有再继续维护了,一个项目提交次数一般都是个位数,项目做完之后,写一篇博客介绍下就算完事了。一个自己都不怎么重视的项目,其实很难引起别人的重视。 所以在 V 部落和微人事中,我就吸取教训,尽量把项目的文档写的详细一些,让不懂前后端分离开发的小伙伴看到我写的开发文档后,就能够快速理清项目的思路。就这样,我每写一个功能点,就写一篇技术文档,微人事项目前前后后一共写了 30 多篇文档: 同时我考虑到很多小伙伴第一次接触到这个项目,一个庞然大物不好处理,因此我在每一次项目提交之前,都会对项目打一个 tag,这样大家通过 git clone 命令获取到项目之后,就可以通过 tag 非常方便的定位到项目的任意时刻,例如只想看登录设计的,可以根据文档介绍回到 v20180107 这个版本: 小伙伴也可以点击 GitHub 上的 release 下载不同时期的项目。我一开始担心有的小伙伴不熟悉 Git 上的 tag 操作,还针对此写了个教程,就是上面文档的第 17 篇。 ...

October 15, 2019 · 1 min · jiezi

springbootplus-CORS跨域处理

CORS跨域处理CORS:Cross-Origin Resource SharingCORS是一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本请求访问的机制,通常由于同域安全策略(the same-origin security policy)浏览器会禁止这种跨域请求。处理方法后台设置允许的请求源/请求头等信息后台配置CorsFilter Bean配置使用 Spring 提供的 CorsFilter 过滤器实现跨域配置io.geekidea.springbootplus.core.config.SpringBootPlusCorsConfig/** * CORS跨域设置 * * @return */@Beanpublic FilterRegistrationBean corsFilter(SpringBootPlusCorsProperties corsProperties) { log.debug("corsProperties:{}", corsProperties); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration corsConfiguration = new CorsConfiguration(); // 跨域配置 corsConfiguration.setAllowedOrigins(corsProperties.getAllowedOrigins()); corsConfiguration.setAllowedHeaders(corsProperties.getAllowedHeaders()); corsConfiguration.setAllowedMethods(corsProperties.getAllowedMethods()); corsConfiguration.setAllowCredentials(corsProperties.isAllowCredentials()); corsConfiguration.setExposedHeaders(corsProperties.getExposedHeaders()); corsConfiguration.setMaxAge(corsConfiguration.getMaxAge()); source.registerCorsConfiguration(corsProperties.getPath(), corsConfiguration); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean.setOrder(Ordered.HIGHEST_PRECEDENCE); bean.setEnabled(corsProperties.isEnable()); return bean;}配置文件配置文件类:io.geekidea.springbootplus.core.properties.SpringBootPlusCorsPropertiesapplication.ymlspring-boot-plus: ############################ CORS start ############################ # CORS跨域配置,默认允许跨域 cors: # 是否启用跨域,默认启用 enable: true # CORS过滤的路径,默认:/** path: /** # 允许访问的源 allowed-origins: '*' # 允许访问的请求头 allowed-headers: x-requested-with,content-type,token # 是否允许发送cookie allow-credentials: true # 允许访问的请求方式 allowed-methods: OPTION,GET,POST # 允许响应的头 exposed-headers: token # 该响应的有效时间默认为30分钟,在有效时间内,浏览器无须为同一请求再次发起预检请求 max-age: 1800 ############################ CORS end ##############################参考HTTP访问控制(CORS)

October 15, 2019 · 1 min · jiezi

springbootplus-XSS跨站脚本攻击处理

XSS跨站脚本攻击处理XSS:Cross Site Scripting跨站脚本攻击(XSS),是目前最普遍的Web应用安全漏洞。这类漏洞能够使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中,当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户的目的。处理方法将参数中的特殊字符进行转换例如 input参数值,用户输入为:<script>alert(1);</script>处理后为:&lt;script&gt;alert(1);&lt;/script&gt;后台处理pom.xml依赖使用 commons-text包中的StringEscapeUtils.escapeHtml4();方法<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-text</artifactId> <version>1.8</version></dependency>XssHttpServletRequestWrapper对HttpServletRequest 对象的请求参数进行处理public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); } @Override public String getQueryString() { String value = super.getQueryString(); return StringEscapeUtils.escapeHtml4(value); } @Override public String getParameter(String name) { String value = super.getParameter(name); return StringEscapeUtils.escapeHtml4(value); } @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (ArrayUtils.isEmpty(values)) { return values; } int length = values.length; String[] escapeValues = new String[length]; for (int i = 0; i < length; i++) { String value = values[i]; escapeValues[i] = StringEscapeUtils.escapeHtml4(value); } return escapeValues; }}XssFilter使用WebFilter注解,拦截所有请求,过滤请求参数@Slf4j@WebFilter(filterName = "xssFilter", urlPatterns = "/*", asyncSupported = true)public class XssFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; XssHttpServletRequestWrapper xssHttpServletRequestWrapper = new XssHttpServletRequestWrapper(request); filterChain.doFilter(xssHttpServletRequestWrapper, servletResponse); }}启动类添加@ServletComponentScan注解扫描使用servlet注解的类,启用 XssFilter@ServletComponentScanJSON字符串请求参数处理实现Jackson反序列化方法,将参数值转义处理public class XssJacksonDeserializer extends JsonDeserializer<String> { @Override public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { return StringEscapeUtils.escapeHtml4(jsonParser.getText()); }}JSON字符串响应结果处理实现Jackson序列化方法,将参数值转义处理@Slf4jpublic class XssJacksonSerializer extends JsonSerializer<String> { @Override public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeString(StringEscapeUtils.escapeHtml4(s)); }}重点,Jackson配置Xss@Configurationpublic class JacksonConfig implements WebMvcConfigurer { @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { // code... // XSS序列化 simpleModule.addSerializer(String.class, new XssJacksonSerializer()); simpleModule.addDeserializer(String.class, new XssJacksonDeserializer()); // code... }}总结实现字符串转义的核心方法:org.apache.commons.text.StringEscapeUtilsStringEscapeUtils.escapeHtml4();

October 15, 2019 · 1 min · jiezi

SpringBoot系列教程web篇之自定义异常处理HandlerExceptionResolver

关于Web应用的全局异常处理,上一篇介绍了ControllerAdvice结合@ExceptionHandler的方式来实现web应用的全局异常管理; 本篇博文则带来另外一种并不常见的使用方式,通过实现自定义的HandlerExceptionResolver,来处理异常状态 上篇博文链接: SpringBoot系列教程web篇之全局异常处理本篇原文: SpringBoot系列教程web篇之自定义异常处理HandlerExceptionResolver<!-- more --> I. 环境搭建首先得搭建一个web应用才有可能继续后续的测试,借助SpringBoot搭建一个web应用属于比较简单的活; 创建一个maven项目,pom文件如下 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7</version> <relativePath/> <!-- lookup parent from update --></parent><properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> <java.version>1.8</java.version></properties><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.45</version> </dependency></dependencies><build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </pluginManagement></build><repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository></repositories>II. HandlerExceptionResolver1. 自定义异常处理HandlerExceptionResolver顾名思义,就是处理异常的类,接口就一个方法,出现异常之后的回调,四个参数中还携带了异常堆栈信息 @NullableModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);我们自定义异常处理类就比较简单了,实现上面的接口,然后将完整的堆栈返回给调用方 public class SelfExceptionHandler implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { String msg = GlobalExceptionHandler.getThrowableStackInfo(ex); try { response.addHeader("Content-Type", "text/html; charset=UTF-8"); response.getWriter().append("自定义异常处理!!! \n").append(msg).flush(); } catch (Exception e) { e.printStackTrace(); } return null; }}// 堆栈信息打印方法如下public static String getThrowableStackInfo(Throwable e) { ByteArrayOutputStream buf = new ByteArrayOutputStream(); e.printStackTrace(new java.io.PrintWriter(buf, true)); String msg = buf.toString(); try { buf.close(); } catch (Exception t) { return e.getMessage(); } return msg;}仔细观察上面的代码实现,有下面几个点需要注意 ...

October 14, 2019 · 2 min · jiezi

Spring-Boot-十二-Spring-Boot-邮件服务

最早我们发邮件的时候是使用 JavaMail 来发送邮件,而在 Spring Boot 中, Spring Boot 帮我们将 JavaMail 封装好了,是可以直接拿来使用的。 1. 依赖文件 pom.xml代码清单:spring-boot-mail/pom.xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies>spring-boot-starter-thymeleaf 引入这个模版引擎是因为我们在发送邮件的时候,各种格式使用 HTML 的方式更容易实现,同样我们也可以使用 freeMark , Spring Boot 同样为我们提供了依赖包。2. 配置文件 application.yml代码清单: server: port: 8080spring: application: name: spring-boot-mail mail: host: smtp.qq.com username: 136736247 password: xxxxxx default-encoding: UTF-8 fromAddr: 136736247@qq.com nickName: inwsy这里我使用 QQ 邮箱作为邮件的发送方,其中的 password 并不是我们的 QQ 密码,这个密码需要我们在 QQ 邮箱的设置里面自己申请的。如下图: ...

October 14, 2019 · 2 min · jiezi

Spring-Boot-必问面试题精选

1.简介自Spring Boot诞生以来,就引起了业界轰动,目前越来越多的公司技术选型选择拥抱Spring Boot。所以Spring Boot也成为面试必问的问题之一。接下来小胖哥总结了一些来开阔你的思路。 2.问题好了接下来直奔主题说说经常在面试中问到的 Spring Boot 面试题 Q1。Spring和Spring Boot有什么区别?Spring Framework提供了多种功能,使Web应用程序的开发更加容易。这些功能包括依赖注入,数据绑定,面向方面的编程,数据访问等等。随着Spring社区的壮大,Spring慢慢变得越来越复杂,不再像开始宣称的那么轻量级。 开发应用程序的配置量越来越大令开发者头疼。这时Spring Boot就派上用场了 - 它采用“约定大于配置”的思想简化了配置,对Spring提供的功能和配置而且将一些功能抽象成为“Starter”开箱即用、按需引用。极大地简化了开发。 Q2。我们如何使用Maven设置Spring Boot应用程序?我们可以像在任何其他库中一样在Maven项目中包含Spring Boot。但是,最好的方法是从spring-boot-starter-parent项目继承并声明依赖于Spring Boot启动器。这样做可以让我们的项目重用Spring Boot的默认设置。继承spring-boot-starter-parent项目非常简单 - 我们只需要在 pom.xml 中指定一个 parent 元素: 我们可以在Maven 中央仓库找到最新版本的 spring-boot-starter-parent。上面的方式很方便但是并不一定符合实际需要。例如公司要求所有项目依赖构建从一个标准BOM开始,我们就不能按上面的方式进行。在这种情况下,我们可以进行如下引用: 然后在 dependencies 标签下引用Spring Boot 的starters 就行了。 Q3。Spring boot 中的starter是什么?依赖管理对于项目至关重要。当项目足够复杂时,管理依赖项可能会变成一场噩梦,因为涉及的组件太多了。这就是Spring Boot 的 starter 就派上用场了。每个starter都可以为我们提供所需要的Spring技术的一站式服务。并且以一致的方式传递和管理其他所需的依赖关系。所有官方starter都在 org.springframework.boot 组下,其名称以 spring-boot-starter- 开头 。非官方的starter的名称在前,如 mybatis-spring-boot-starter。这种命名模式使得查找启动器变得很容易,尤其是在使用支持按名称搜索依赖关系的IDE时。但是这个不是绝对的,有些开发者可能不遵从这种契约。目前大概有超过50种官方starter。最常用的是: spring-boot-starter: 核心启动器,包括自动配置支持,日志记录和YAMLspring-boot-starter-aop: 使用Spring AOP和AspectJ进行面向方面编程的初学者spring-boot-starter-data-jpa: 使用Spring Data JPA和Hibernate的启动器spring-boot-starter-jdbc: 用于将JDBC与HikariCP连接池一起使用的启动器spring-boot-starter-security: 使用Spring Security的启动器spring-boot-starter-test: 用于测试Spring Boot应用程序的启动器spring-boot-starter-web: 使用Spring MVC构建Web的启动器,包括RESTful应用程序其他starter 可去spring.io查询 Q4。如何禁用特定的自动配置?如果我们要禁用特定的自动配置,我们可以使用@EnableAutoConfiguration注解的exclude属性来指示它。如下禁用了数据源自动配置DataSourceAutoConfiguration: ...

October 14, 2019 · 1 min · jiezi

Spring-security五完美权限管理系统授权过程分析

1. 权限管理相关概念 权限管理是一个几乎所有后台系统的都会涉及的一个重要组成部分,主要目的是对整个后台管理系统进行权限的控制。常见的基于角色的访问控制,其授权模型为“用户-角色-权限”,简明的说,一个用户拥有多个角色,一个角色拥有多个权限。其中, 用户: 不用多讲,大家也知道了;角色: 一个集合的概念,角色管理是确定角色具备哪些权限的一个过程 ;权限:1).页面权限,控制你可以看到哪个页面,看不到哪个页面; 2). 操作权限,控制你可以在页面上进行哪些操作(查询、删除、编辑等); 3).数据权限,是控制你可以看到哪些数据。 实质是: 权限(Permission) = 资源(Resource) + 操作(Privilege) 角色(Role) = 权限的集合(a set of low-level permissions) 用户(User) = 角色的集合(high-level roles) 权限管理过程: 鉴权管理,即权限判断逻辑,如菜单管理(普通业务人员登录系统后,是看不到【用户管理】菜单的)、功能权限管理(URL访问的管理)、行级权限管理等授权管理,即权限分配过程,如直接对用户授权,直接分配到用户的权限具有最优先级别、对用户所属岗位授权,用户所属岗位信息可以看作是一个分组,和角色的作用一样,但是每个用户只能关联一个岗位信息等。 在实际项目中用户数量多,逐一的为每个系统用户授权,这是极其繁琐的事,所以可以学习linux文件管理系统一样,设置group模式,一组有多个用户,可以为用户组授权相同的权限,简便多了。这样模式下: 每个用户的所有权限=用户个人的权限+用户组所用的权限 用户组、用户、与角色三者关系如下: 再结合权限管理的页面权限、操作权限,如菜单的访问、功能模块的操作、按钮的操作等等,可把功能操作与资源统一管理,即让它们直接与权限关联起来,关系图如下: 2. 授权过程分析### 2.1 授权访问权限工作流程: FilterSecurityInterceptor doFilter()->invoke() ->AbstractSecurityInterceptor beforeInvocation() ->SecurityMetadataSource 获取ConfigAttribute属性信息(从数据库或者其他数据源地方) getAttributes() ->AccessDecisionManager() 基于AccessDecisionVoter实现授权访问 Decide() ->AccessDecisionVoter 受AccessDecisionManager委托实现授权访问 vote()默认授权过程会使用这样的工作流程,接下来来分析各个组件的功能与源码。 ### 2.2 AbstractSecurityInterceptor分析 FilterSecurityInterceptor为授权拦截器, 在FilterSecurityInterceptor中有一个封装了过滤链、request以及response的FilterInvocation对象进行操作,在FilterSecurityInterceptor,主要由invoke()调用其父类AbstractSecurityInterceptor的方法。 invoke()分析: public void invoke(FilterInvocation fi) throws IOException, ServletException { ..... // 获取accessDecisionManager权限决策后结果状态、以及权限属性 InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.finallyInvocation(token); } super.afterInvocation(token, null); }} AbstractSecurityInterceptor 的授权过滤器主要方法beforeInvocation(),afterInvocation()以及authenticateIfRequired(),其最主要的方法beforeInvocation() 分析如下: ...

October 14, 2019 · 4 min · jiezi

springdatarediscache-使用及源码走读

预期读者准备使用 spring 的 data-redis-cache 的同学了解 @CacheConfig,@Cacheable,@CachePut,@CacheEvict,@Caching 的使用深入理解 data-redis-cache 的实现原理文章内容说明如何使用 redis-cache自定义 keyGenerator 和过期时间源码解读自带缓存机制的不足快速入门maven 加入 jar 包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>配置 redis spring.redis.host=127.0.0.1开启 redis-cache @EnableCaching@CacheConfig,@Cacheable,@CachePut,@CacheEvict,@Caching 的功能 @Cacheable 会查询缓存中是否有数据,如果有数据则返回,否则执行方法@CachePut 每次都执行方法,并把结果进行缓存@CacheEvict 会删除缓存中的内容@Caching 相当于上面三者的综合,用于配置三者的行为@CacheConfig 配置在类上,用于配置当前类的全局缓存配置详细配置经过上面的配置,就已经可以使用 redis-cache 了,但是还是有些问题需要问自己一下,比如 存储在 redis 的 key 是什么样子的,我可以自定义 key 吗存储到 redis 的 value 是怎么序列化的存储的缓存是多久过期并发访问时,会不会直接穿透从而不断的修改缓存内容过期时间,序列化方式由此类决定 RedisCacheConfiguration,可以覆盖此类达到自定义配置。默认配置为RedisCacheConfiguration.defaultCacheConfig() ,它配置为永不过期,key 为 String 序列化,并加上了一个前缀做为命名空间,value 为 Jdk 序列化,所以你要存储的类必须要实现 java.io.Serializable 。 存储的 key 值的生成由 KeyGenerator 决定,可以在各缓存注解上进行配置,默认使用的是 SimpleKeyGenerator 其存储的 key 方式为 SimpleKey [参数名1,参数名2],如果在同一个命名空间下,有两个同参数名的方法就公出现冲突导致反序列化失败。 并发访问时,确实存在多次访问数据库而没有使用缓存的情况 https://blog.csdn.net/clementad/article/details/52452119 Srping 4.3提供了一个sync参数。是当缓存失效后,为了避免多个请求打到数据库,系统做了一个并发控制优化,同时只有一个线程会去数据库取数据其它线程会被阻塞。自定义存储 key根据上面的说明 ,很有可能会存在存储的 key 一致而导致反序列化失败,所以需要自定义存储 key ,有两种实现办法 ,一种是使用元数据配置 key(简单但难维护),一种是全局设置 keyGenerator ...

October 13, 2019 · 3 min · jiezi

Spring-Boot-九-微服务应用监控-Spring-Boot-Actuator-详解

1. 引言在当前的微服务架构方式下,我们会有很多的服务部署在不同的机器上,相互是通过服务调用的方式进行交互,一个完整的业务流程中间会经过很多个微服务的处理和传递,那么,如何能知道每个服务的健康状况就显得尤为重要。 万幸的是 Spring Boot 为我们提供了监控模块 Spring Boot Actuator ,本篇文章将和大家一起探讨一些 Spring Boot Actuator 一些常见用法方便我们在日常的使用中对我们的微服务进行监控治理。 Spring Boot Actuator 帮我们实现了对程序内部运行情况监控,比如监控状况、Bean加载情况、环境变量、日志信息、线程信息等。 2. Actuator 的使用2.1 工程依赖使用 Spring Boot Actuator 需要加入如下依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency>注意: 因 Spring Boot Actuator 会暴露我们服务的详细信息,为了保障安全性,建议添加安全控制的相关依赖 spring-boot-starter-security ,这样,在访问应用监控端点时,都需要输入验证信息。所需依赖如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>2.2 工程配置配置文件 application.yml 如下: 代码清单:spring-boot-actuator/src/main/resources/application.yml server: port: 8080info: app: name: spring-boot-actuator version: 1.0.0spring: security: user: name: admin password: admin现在先启动工程,打开浏览器访问:http://localhost:8080/actuator ,可以看到页面显示如下 json : { "_links":{ "self":{ "href":"http://localhost:8080/actuator", "templated":false }, "health":{ "href":"http://localhost:8080/actuator/health", "templated":false }, "health-component-instance":{ "href":"http://localhost:8080/actuator/health/{component}/{instance}", "templated":true }, "health-component":{ "href":"http://localhost:8080/actuator/health/{component}", "templated":true }, "info":{ "href":"http://localhost:8080/actuator/info", "templated":false } }}这些是默认支持的链接,只有: ...

October 9, 2019 · 3 min · jiezi

Spring-Boot-2-集成log4j2日志框架

前言Log4j2是 Log4j 的进化版本,并提供了许多 Logback 可用的改进,同时解决了 Logback 体系结构中的一些固有问题。而且日志处理中我们会用到kafka作为日志管道。而kafka客户端依赖与Logback的兼容不是很完美,你可以选择排除依赖冲突或者使用Log4j2 。<!-- more --> 排除Logback依赖Spring Boot 2.x默认使用Logback日志框架,要使用 Log4j2必须先排除 Logback。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <!--排除logback--> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions></dependency>引入Log4j2依赖<!--log4j2 依赖--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId></dependency>上面的 log4j2 已经适配了slf4j日志门面,所以我们的代码无需替换,只需要替换具体的日志框架以及对应的配置文件。 配置Log4j2创建log4j2.xml文件,放在工程resources目录里。这样就可以不加任何配置。如果你需要指定配置文件需要在Spring boot 配置文件application.yml中指定 logging.config 属性。下面是一份比较详细的 log4j2 配置文件 : <?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--> <configuration status="WARN" monitorInterval="30"> <!--先定义所有的appender--> <appenders> <!--这个输出控制台的配置--> <console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式--> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> </console> <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用--> <File name="log" fileName="log/test.log" append="false"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/> </File> <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log" filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> </RollingFile> <RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log" filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log"> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 --> <DefaultRolloverStrategy max="20"/> </RollingFile> <RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log" filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log"> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> </RollingFile> </appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--> <loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息--> <logger name="org.springframework" level="INFO"/> <logger name="org.mybatis" level="INFO"/> <root level="all"> <appender-ref ref="Console"/> <appender-ref ref="RollingFileInfo"/> <appender-ref ref="RollingFileWarn"/> <appender-ref ref="RollingFileError"/> </root> </loggers> </configuration>基本上你拿上面的配置根据你自己的需要更改一下即可生效。 windows 下 ${sys:user.home} 会将日志打印到用户目录下 ...

October 9, 2019 · 2 min · jiezi

MongoDBSpring-Data-MongoDB详细的操作手册增删改查

github:https://github.com/Ccww-lx/Sp... 模块:spring-boot-base-mongodb 在NoSQL盛行的时代,App很大可能会涉及到MongoDB数据库的使用,而也必须学会在Spring boot使用Spring Data连接MongoDB进行数据增删改查操作,如下为详细的操作手册。 1. 依赖直接导入spring-data-mongodb包或者使用Spring Boot starter <dependencies> <!-- other dependency elements omitted --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-mongodb</artifactId> <version>2.2.0.RELEASE</version> </dependency></dependencies><!--spring 框架使用最新的 --><spring.framework.version>5.2.0.RELEASE</spring.framework.version><!--用一即可--><!--使用Spring Boot starter--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId></dependency>2. 属性文件application.properties#mongodb连接地址,集群用“;”隔开spring.mongo.mongoDatabaseAddress=10.110.112.165:27092;10.110.112.166:27092#mongo数据名spring.mongo.dbname=mongodb#mongo用户spring.mongo.username=mongodbopr#mongo密码spring.mongo.password=123456#mongo最大连接数spring.mongo.connectionsPerHost=503. mongodb 配置注册Mongo实例配置: @Configurationpublic class MongodbConfig { public static final String COMMA = ";"; public static final String COLON = ":"; @Value("${spring.mongo.mongoDatabaseAddress}") private String mongoDatabaseAddress; @Value("${spring.mongo.username}") private String username; @Value("${spring.mongo.dbname}") private String dbname; @Value("${spring.mongo.password}") private String password; @Value("${spring.mongo.connectionsPerHost}") private String connectionsPerHost; /** * 获取mongodb的地址 * * @return */ private List<ServerAddress> getMongoDbAddress() { List<ServerAddress> serverAddrList = new ArrayList<ServerAddress>(); //如果有多个服务器的话 if (this.mongoDatabaseAddress.indexOf(COMMA) > 0) { String[] addressArrays = mongoDatabaseAddress.split(COMMA); String[] hostPort; for (String address : addressArrays) { hostPort = address.split(COLON); ServerAddress serverAdress = new ServerAddress(hostPort[0], Integer.valueOf(hostPort[1])); serverAddrList.add(serverAdress); } } else { String[] hostPort = mongoDatabaseAddress.split(COLON); ServerAddress serverAdress = new ServerAddress(hostPort[0], Integer.valueOf(hostPort[1])); serverAddrList.add(serverAdress); } return serverAddrList; } /** * 设置连接参数 */ private MongoClientOptions getMongoClientOptions() { MongoClientOptions.Builder builder = MongoClientOptions.builder(); // todo 添加其他参数配置 //最大连接数 builder.connectionsPerHost(Integer.valueOf(connectionsPerHost)); MongoClientOptions options = builder.readPreference(ReadPreference.nearest()).build(); return options; } /** * * @return */ @Bean public MongoClient mongoClient() { //使用数据库名、用户名密码登录 MongoCredential credential = MongoCredential.createCredential(username, dbname, password.toCharArray()); //创建Mongo客户端 return new MongoClient(getMongoDbAddress(), credential, getMongoClientOptions()); } /** * 注册mongodb操作类 * @param mongoClient * @return */ @Bean @ConditionalOnClass(MongoClient.class) public MongoTemplate mongoTemplate(MongoClient mongoClient) { MongoTemplate mongoTemplate = new MongoTemplate(new SimpleMongoDbFactory(mongoClient, dbname)); return mongoTemplate; }}4. mongodb操作使用MongoTemplate类进行增删改查 ...

October 9, 2019 · 3 min · jiezi

SpringBoot注解梳理

一、注解(annotations)列表@SpringBootApplication:包含了@ComponentScan、@Configuration和@EnableAutoConfiguration注解。其中@ComponentScan让spring Boot扫描到Configuration类并把它加入到程序上下文。 @Configuration 等同于spring的XML配置文件;使用Java代码可以检查类型安全。 @EnableAutoConfiguration 自动配置。 @ComponentScan 组件扫描,可自动发现和装配一些Bean。 @Component可配合CommandLineRunner使用,在程序启动后执行一些基础任务。 @RestController注解是@Controller和@ResponseBody的合集,表示这是个控制器bean,并且是将函数的返回值直 接填入HTTP响应体中,是REST风格的控制器。 @Autowired自动导入。 @PathVariable获取参数。 @JsonBackReference解决嵌套外链问题。 @RepositoryRestResourcepublic配合spring-boot-starter-data-rest使用。   二、注解(annotations)详解@SpringBootApplication:申明让spring boot自动给程序进行必要的配置,这个配置等同于:@Configuration ,@EnableAutoConfiguration 和 @ComponentScan 三个配置。 import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScanpublic class Application {    public static void main(String[] args) {        SpringApplication.run(Application.class, args);    }}@ResponseBody:表示该方法的返回结果直接写入HTTP response body中,一般在异步获取数据时使用,用于构建RESTful的api。在使用@RequestMapping后,返回值通常解析为跳转路径,加上@responsebody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中。比如异步获取json数据,加上@responsebody后,会直接返回json数据。该注解一般会配合@RequestMapping一起使用。示例代码: @RequestMapping(“/test”)@ResponseBodypublic String test(){    return”ok”;}@Controller:用于定义控制器类,在spring 项目中由控制器负责将用户发来的URL请求转发到对应的服务接口(service层),一般这个注解在类中,通常方法需要配合注解@RequestMapping。示例代码: @Controller@RequestMapping(“/demoInfo”)publicclass DemoController {    @Autowired    private DemoInfoService demoInfoService;     @RequestMapping("/hello")    public String hello(Map<String,Object> map){        System.out.println("DemoController.hello()");        map.put("hello","from TemplateController.helloHtml");        //会使用hello.html或者hello.ftl模板进行渲染显示.        return"/hello";    }}@RestController:用于标注控制层组件(如struts中的action),@ResponseBody和@Controller的合集。示例代码: import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController; @RestController@RequestMapping(“/demoInfo2”)publicclass DemoController2 {     @RequestMapping("/test")    public String test(){        return"ok";    }}@RequestMapping:提供路由信息,负责URL到Controller中的具体函数的映射。 @EnableAutoConfiguration:Spring Boot自动配置(auto-configuration):尝试根据你添加的jar依赖自动配置你的Spring应用。例如,如果你的classpath下存在HSQLDB,并且你没有手动配置任何数据库连接beans,那么我们将自动配置一个内存型(in-memory)数据库”。你可以将@EnableAutoConfiguration或者@SpringBootApplication注解添加到一个@Configuration类上来选择自动配置。如果发现应用了你不想要的特定自动配置类,你可以使用@EnableAutoConfiguration注解的排除属性来禁用它们。 @ComponentScan:表示将该类自动发现扫描组件。个人理解相当于,如果扫描到有@Component、@Controller、@Service等这些注解的类,并注册为Bean,可以自动收集所有的Spring组件,包括@Configuration类。我们经常使用@ComponentScan注解搜索beans,并结合@Autowired注解导入。可以自动收集所有的Spring组件,包括@Configuration类。我们经常使用@ComponentScan注解搜索beans,并结合@Autowired注解导入。如果没有配置的话,Spring Boot会扫描启动类所在包下以及子包下的使用了@Service,@Repository等注解的类。 @Configuration:相当于传统的xml配置文件,如果有些第三方库需要用到xml文件,建议仍然通过@Configuration类作为项目的配置主类——可以使用@ImportResource注解加载xml配置文件。 @Import:用来导入其他配置类。 @ImportResource:用来加载xml配置文件。 @Autowired:自动导入依赖的bean @Service:一般用于修饰service层的组件 @Repository:使用@Repository注解可以确保DAO或者repositories提供异常转译,这个注解修饰的DAO或者repositories类会被ComponetScan发现并配置,同时也不需要为它们提供XML配置项。 @Bean:用@Bean标注方法等价于XML中配置的bean。 @Value:注入Spring boot application.properties配置的属性的值。示例代码: @Value(value = “#{message}”)private String message;@Inject:等价于默认的@Autowired,只是没有required属性; @Component:泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。 @Bean:相当于XML中的,放在方法的上面,而不是类,意思是产生一个bean,并交给spring管理。 @AutoWired:自动导入依赖的bean。byType方式。把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。当加上(required=false)时,就算找不到bean也不报错。 @Qualifier:当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定。与@Autowired配合使用。@Qualifier限定描述符除了能根据名字进行注入,但能进行更细粒度的控制如何选择候选者,具体使用方式如下: @Autowired@Qualifier(value = “demoInfoService”)private DemoInfoService demoInfoService;@Resource(name=”name”,type=”type”):没有括号内内容的话,默认byName。与@Autowired干类似的事。   三、JPA注解@Entity:@Table(name=”“):表明这是一个实体类。一般用于jpa这两个注解一般一块使用,但是如果表名和实体类名相同的话,@Table可以省略 @MappedSuperClass:用在确定是父类的entity上。父类的属性子类可以继承。 @NoRepositoryBean:一般用作父类的repository,有这个注解,spring不会去实例化该repository。 @Column:如果字段名与列名相同,则可以省略。 @Id:表示该属性为主键。 @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = “repair_seq”):表示主键生成策略是sequence(可以为Auto、IDENTITY、native等,Auto表示可在多个数据库间切换),指定sequence的名字是repair_seq。 @SequenceGeneretor(name = “repair_seq”, sequenceName = “seq_repair”, allocationSize = 1):name为sequence的名称,以便使用,sequenceName为数据库的sequence名称,两个名称可以一致。 ...

October 8, 2019 · 1 min · jiezi

Spring5源码解析6ConfigurationClassParser-解析配置类

ConfigurationClassParser在ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中创建了ConfigurationClassParser对象并调用其parse方法。该方法就是在负责解析配置类、扫描包、注册BeanDefinition,源码如下: //ConfigurationClassParser#parseSet<BeanDefinitionHolder>) 方法源码public void parse(Set<BeanDefinitionHolder> configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { // 根据不同的 BeanDefinition 实例对象 调用不同的 parse 方法 // 底层其实都是在调用 org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } //执行DeferredImportSelector this.deferredImportSelectorHandler.process();}在该方法内部根据不同的BeanDefinition实例对象,调用了不同的parse方法,而这些parse方法底层,实际上都是调用了ConfigurationClassParser#processConfigurationClass方法。 ...

October 8, 2019 · 5 min · jiezi

springbootplus集成SpringBootShiroJWT权限管理

SpringBoot+Shiro+JWT权限管理ShiroApache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。三个核心组件:Subject, SecurityManager 和 Realms. Subject代表了当前用户的安全操作,即“当前操作用户”。SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。ShiroBasicArchitecture ShiroArchitecture JWTJSON Web Token(JWT)是目前最流行的跨域身份验证解决方案JSON Web令牌是一种开放的行业标准 RFC 7519方法,用于在双方之间安全地表示声明。JWT 数据结构eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJodHRwczovL3NwcmluZ2Jvb3QucGx1cyIsIm5hbWUiOiJzcHJpbmctYm9vdC1wbHVzIiwiaWF0IjoxNTE2MjM5MDIyfQ.1Cm7Ej8oIy1P5pkpu8-Q0B7bTU254I1og-ZukEe84II JWT有三部分组成:Header:头部,Payload:负载,Signature:签名SpringBoot+Shiro+JWTpom.xml Shiro依赖<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.4.1</version></dependency>pom.xml JWT依赖<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.3</version></dependency>ShiroConfig.java配置@Slf4j@Configurationpublic class ShiroConfig { /** * JWT过滤器名称 */ private static final String JWT_FILTER_NAME = "jwtFilter"; /** * Shiro过滤器名称 */ private static final String SHIRO_FILTER_NAME = "shiroFilter"; @Bean public CredentialsMatcher credentialsMatcher() { return new JwtCredentialsMatcher(); } /** * JWT数据源验证 * * @return */ @Bean public JwtRealm jwtRealm(LoginRedisService loginRedisService) { JwtRealm jwtRealm = new JwtRealm(loginRedisService); jwtRealm.setCachingEnabled(false); jwtRealm.setCredentialsMatcher(credentialsMatcher()); return jwtRealm; } /** * 禁用session * * @return */ @Bean public DefaultSessionManager sessionManager() { DefaultSessionManager manager = new DefaultSessionManager(); manager.setSessionValidationSchedulerEnabled(false); return manager; } @Bean public SessionStorageEvaluator sessionStorageEvaluator() { DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator(); sessionStorageEvaluator.setSessionStorageEnabled(false); return sessionStorageEvaluator; } @Bean public DefaultSubjectDAO subjectDAO() { DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO(); defaultSubjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator()); return defaultSubjectDAO; } /** * 安全管理器配置 * * @return */ @Bean public DefaultWebSecurityManager securityManager(LoginRedisService loginRedisService) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(jwtRealm(loginRedisService)); securityManager.setSubjectDAO(subjectDAO()); securityManager.setSessionManager(sessionManager()); SecurityUtils.setSecurityManager(securityManager); return securityManager; } /** * ShiroFilterFactoryBean配置 * * @param securityManager * @param loginRedisService * @param shiroProperties * @param jwtProperties * @return */ @Bean(SHIRO_FILTER_NAME) public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager, LoginService loginService, LoginRedisService loginRedisService, ShiroProperties shiroProperties, JwtProperties jwtProperties) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, Filter> filterMap = new HashedMap(); filterMap.put(JWT_FILTER_NAME, new JwtFilter(loginService, loginRedisService, jwtProperties)); shiroFilterFactoryBean.setFilters(filterMap); Map<String, String> filterChainMap = shiroFilterChainDefinition(shiroProperties).getFilterChainMap(); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap); return shiroFilterFactoryBean; } /** * Shiro路径权限配置 * * @return */ @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition(ShiroProperties shiroProperties) { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); // 获取ini格式配置 String definitions = shiroProperties.getFilterChainDefinitions(); if (StringUtils.isNotBlank(definitions)) { Map<String, String> section = IniUtil.parseIni(definitions); log.debug("definitions:{}", JSON.toJSONString(section)); for (Map.Entry<String, String> entry : section.entrySet()) { chainDefinition.addPathDefinition(entry.getKey(), entry.getValue()); } } // 获取自定义权限路径配置集合 List<ShiroPermissionConfig> permissionConfigs = shiroProperties.getPermissionConfig(); log.debug("permissionConfigs:{}", JSON.toJSONString(permissionConfigs)); if (CollectionUtils.isNotEmpty(permissionConfigs)) { for (ShiroPermissionConfig permissionConfig : permissionConfigs) { String url = permissionConfig.getUrl(); String[] urls = permissionConfig.getUrls(); String permission = permissionConfig.getPermission(); if (StringUtils.isBlank(url) && ArrayUtils.isEmpty(urls)) { throw new ShiroConfigException("shiro permission config 路径配置不能为空"); } if (StringUtils.isBlank(permission)) { throw new ShiroConfigException("shiro permission config permission不能为空"); } if (StringUtils.isNotBlank(url)) { chainDefinition.addPathDefinition(url, permission); } if (ArrayUtils.isNotEmpty(urls)) { for (String string : urls) { chainDefinition.addPathDefinition(string, permission); } } } } // 最后一个设置为JWTFilter chainDefinition.addPathDefinition("/**", JWT_FILTER_NAME); Map<String, String> filterChainMap = chainDefinition.getFilterChainMap(); log.debug("filterChainMap:{}", JSON.toJSONString(filterChainMap)); return chainDefinition; } /** * ShiroFilter配置 * * @return */ @Bean public FilterRegistrationBean delegatingFilterProxy() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); DelegatingFilterProxy proxy = new DelegatingFilterProxy(); proxy.setTargetFilterLifecycle(true); proxy.setTargetBeanName(SHIRO_FILTER_NAME); filterRegistrationBean.setFilter(proxy); filterRegistrationBean.setAsyncSupported(true); filterRegistrationBean.setEnabled(true); filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC); return filterRegistrationBean; } @Bean public Authenticator authenticator(LoginRedisService loginRedisService) { ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator(); authenticator.setRealms(Arrays.asList(jwtRealm(loginRedisService))); authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy()); return authenticator; } /** * Enabling Shiro Annotations * * @return */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * depends-on lifecycleBeanPostProcessor * * @return */ @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; }}JWT过滤器配置@Slf4jpublic class JwtFilter extends AuthenticatingFilter { private LoginService loginService; private LoginRedisService loginRedisService; private JwtProperties jwtProperties; public JwtFilter(LoginService loginService, LoginRedisService loginRedisService, JwtProperties jwtProperties) { this.loginService = loginService; this.loginRedisService = loginRedisService; this.jwtProperties = jwtProperties; } /** * 将JWT Token包装成AuthenticationToken * * @param servletRequest * @param servletResponse * @return * @throws Exception */ @Override protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { String token = JwtTokenUtil.getToken(); if (StringUtils.isBlank(token)) { throw new AuthenticationException("token不能为空"); } if (JwtUtil.isExpired(token)) { throw new AuthenticationException("JWT Token已过期,token:" + token); } // 如果开启redis二次校验,或者设置为单个用户token登陆,则先在redis中判断token是否存在 if (jwtProperties.isRedisCheck() || jwtProperties.isSingleLogin()) { boolean redisExpired = loginRedisService.exists(token); if (!redisExpired) { throw new AuthenticationException("Redis Token不存在,token:" + token); } } String username = JwtUtil.getUsername(token); String salt; if (jwtProperties.isSaltCheck()){ salt = loginRedisService.getSalt(username); }else{ salt = jwtProperties.getSecret(); } return JwtToken.build(token, username, salt, jwtProperties.getExpireSecond()); } /** * 访问失败处理 * * @param request * @param response * @return * @throws Exception */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = WebUtils.toHttp(request); HttpServletResponse httpServletResponse = WebUtils.toHttp(response); // 返回401 httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 设置响应码为401或者直接输出消息 String url = httpServletRequest.getRequestURI(); log.error("onAccessDenied url:{}", url); ApiResult apiResult = ApiResult.fail(ApiCode.UNAUTHORIZED); HttpServletResponseUtil.printJSON(httpServletResponse, apiResult); return false; } /** * 判断是否允许访问 * * @param request * @param response * @param mappedValue * @return */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { String url = WebUtils.toHttp(request).getRequestURI(); log.debug("isAccessAllowed url:{}", url); if (this.isLoginRequest(request, response)) { return true; } boolean allowed = false; try { allowed = executeLogin(request, response); } catch (IllegalStateException e) { //not found any token log.error("Token不能为空", e); } catch (Exception e) { log.error("访问错误", e); } return allowed || super.isPermissive(mappedValue); } /** * 登陆成功处理 * * @param token * @param subject * @param request * @param response * @return * @throws Exception */ @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { String url = WebUtils.toHttp(request).getRequestURI(); log.debug("鉴权成功,token:{},url:{}", token, url); // 刷新token JwtToken jwtToken = (JwtToken) token; HttpServletResponse httpServletResponse = WebUtils.toHttp(response); loginService.refreshToken(jwtToken, httpServletResponse); return true; } /** * 登陆失败处理 * * @param token * @param e * @param request * @param response * @return */ @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { log.error("登陆失败,token:" + token + ",error:" + e.getMessage(), e); return false; }}JWT Realm配置@Slf4jpublic class JwtRealm extends AuthorizingRealm { private LoginRedisService loginRedisService; public JwtRealm(LoginRedisService loginRedisService) { this.loginRedisService = loginRedisService; } @Override public boolean supports(AuthenticationToken token) { return token != null && token instanceof JwtToken; } /** * 授权认证,设置角色/权限信息 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { log.debug("doGetAuthorizationInfo principalCollection..."); // 设置角色/权限信息 String token = principalCollection.toString(); // 获取username String username = JwtUtil.getUsername(token); // 获取登陆用户角色权限信息 LoginSysUserRedisVo loginSysUserRedisVo = loginRedisService.getLoginSysUserRedisVo(username); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); // 设置角色 authorizationInfo.setRoles(loginSysUserRedisVo.getRoles()); // 设置权限 authorizationInfo.setStringPermissions(loginSysUserRedisVo.getPermissions()); return authorizationInfo; } /** * 登陆认证 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { log.debug("doGetAuthenticationInfo authenticationToken..."); // 校验token JwtToken jwtToken = (JwtToken) authenticationToken; if (jwtToken == null) { throw new AuthenticationException("jwtToken不能为空"); } String salt = jwtToken.getSalt(); if (StringUtils.isBlank(salt)) { throw new AuthenticationException("salt不能为空"); } return new SimpleAuthenticationInfo( jwtToken, salt, getName() ); }}更多配置:https://github.com/geekidea/spring-boot-plusapplication.yml配置############################## spring-boot-plus start ##############################spring-boot-plus: ######################## Spring Shiro start ######################## shiro: # shiro ini 多行字符串配置 filter-chain-definitions: | /=anon /static/**=anon /templates/**=anon # 权限配置 permission-config: # 排除登陆登出相关 - urls: /login,/logout permission: anon # 排除静态资源 - urls: /static/**,/templates/** permission: anon # 排除Swagger - urls: /docs,/swagger-ui.html, /webjars/springfox-swagger-ui/**,/swagger-resources/**,/v2/api-docs permission: anon # 排除SpringBootAdmin - urls: /,/favicon.ico,/actuator/**,/instances/**,/assets/**,/sba-settings.js,/applications/** permission: anon # 测试 - url: /sysUser/getPageList permission: anon ######################## Spring Shiro end ########################## ############################ JWT start ############################# jwt: token-name: token secret: 666666 issuer: spring-boot-plus audience: web # 默认过期时间1小时,单位:秒 expire-second: 3600 # 是否刷新token refresh-token: true # 刷新token的时间间隔,默认10分钟,单位:秒 refresh-token-countdown: 600 # redis校验jwt token是否存在,可选 redis-check: true # true: 同一个账号只能是最后一次登陆token有效,false:同一个账号可多次登陆 single-login: false # 盐值校验,如果不加自定义盐值,则使用secret校验 salt-check: true ############################ JWT end ############################################################## spring-boot-plus end ###############################Redis存储信息使用Redis缓存JWTToken和盐值:方便鉴权,token后台过期控制等Redis二次校验和盐值校验是可选的127.0.0.1:6379> keys *1) "login:user:token:admin:0f2c5d670f9f5b00201c78293304b5b5"2) "login:salt:admin"3) "login:user:admin"4) "login:token:0f2c5d670f9f5b00201c78293304b5b5"Redis存储的JwtToken信息127.0.0.1:6379> get login:token:0f2c5d670f9f5b00201c78293304b5b5{ "@class": "io.geekidea.springbootplus.shiro.vo.JwtTokenRedisVo", "host": "127.0.0.1", "username": "admin", "salt": "f80b2eed0110a7ea5a94c35cbea1fe003d9bb450803473428b74862cceb697f8", "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWIiLCJpc3MiOiJzcHJpbmctYm9vdC1wbHVzIiwiZXhwIjoxNTcwMzU3ODY1LCJpYXQiOjE1NzAzNTQyNjUsImp0aSI6IjE2MWQ1MDQxZmUwZjRmYTBhOThjYmQ0ZjRlNDI1ZGQ3IiwidXNlcm5hbWUiOiJhZG1pbiJ9.0ExWSiniq7ThMXfqCOi9pCdonY8D1azeu78_vLNa2v0", "createDate": [ "java.util.Date", 1570354265000 ], "expireSecond": 3600, "expireDate": [ "java.util.Date", 1570357865000 ]}ReferenceShirohttps://shiro.apache.org/spring.htmlhttps://shiro.apache.org/spring-boot.htmlJWThttps://jwt.io/https://github.com/auth0/java-jwtspring-boot-plushttps://github.com/geekidea/spring-boot-plushttps://springboot.plus/guide/shiro-jwt.html

October 8, 2019 · 6 min · jiezi