特性
虽然Spring的组件代码是轻量级的,但它的配置却是重量级的。一开始,Spring用XML配置,而且是很多XML配置。Spring 2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式XML配置。Spring 3.0引入了基于Java的配置,这是一种类型安全的可重构配置方式,可以代替XML。
所有这些配置都代表了开发时的损耗。因为在思考Spring特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。和所有框架一样,Spring实用,但与此同时它要求的回报也不少。
除此之外,项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度。
为基于Spring的开发提供更快的入门体验
开箱即用,没有代码生成,也无需XML配置。同时也可以修改默认值来满足特定的需求
提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等
SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式
起步依赖
起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。
简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。自动配置
Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是Spring自动完成的。
依赖环境和版本新特性说明
1、依赖版本jdk8以上, Springboot2.x用JDK8, 因为底层是 Spring framework5,
2、安装maven最新版本,maven3.2以上版本,下载地址 :https://maven.apache.org/download.cgi
3、Eclipse或者IDE
4、新特性
5、翻译工具:https://translate.google.cn/
6、springbootGitHub地址:https://github.com/spring-projects/spring-boot
7、springboot官方文档:https://spring.io/guides/gs/spring-boot/
快速创建
手工创建web应用
简介:使用Maven手工创建SpringBoot2.x应用
手工创建:https://projects.spring.io/spring-boot/#quick-start
官方推荐包命名接口,不要使用默认 defaultPackage
官方文档: https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#using-boot-using-the-default-package
例子:
com
+- example
+- myapplication
+- Application.java
|
+- customer
| +- Customer.java
| +- CustomerController.java
| +- CustomerService.java
| +- CustomerRepository.java
|
+- order
+- Order.java
+- OrderController.java
+- OrderService.java
+- OrderRepository.java
自动创建web应用
工具自动创建:http://start.spring.io/
依赖默认Maven版本
[https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#appendix-dependency-versions](https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#appendix-dependency-versions)
HTTP请求
注解讲解和简化注解配置技巧
1、@RestController and @RequestMapping是springMVC的注解,不是springboot特有的
2、@RestController = @Controller+@ResponseBody
3、@SpringBootApplication = @Configuration+@EnableAutoConfiguration+@ComponentScan
localhost:8080
GET,POST请求实战
springboot接口,http的get请求,各个注解使用
1、GET请求
1、单一参数@RequestMapping(path = "/{id}", method = RequestMethod.GET)
1) public String getUser(@PathVariable String id ) {}
2)@RequestMapping(path = "/{depid}/{userid}", method = RequestMethod.GET) 可以同时指定多个提交方法
getUser(@PathVariable("depid") String departmentID,@PathVariable("userid") String userid)
3)一个顶俩
@GetMapping = @RequestMapping(method = RequestMethod.GET)
@PostMapping = @RequestMapping(method = RequestMethod.POST)
@PutMapping = @RequestMapping(method = RequestMethod.PUT)
@DeleteMapping = @RequestMapping(method = RequestMethod.DELETE)
4)@RequestParam(value = "name", required = true)
可以设置默认值,比如分页
4)@RequestBody 请求体映射实体类
需要指定http头为 content-type为application/json charset=utf-8
5)@RequestHeader 请求头,比如鉴权
@RequestHeader("access_token") String accessToken
6)HttpServletRequest request自动注入获取参数
package com.niliv.demo.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.niliv.demo.domain.User;
//测试http协议的get请求
@RestController
public class GetController {
private Map<String,Object> params = new HashMap<>();
/**
* 功能描述:测试restful协议,从路径中获取字段
* @param cityId
* @param userId
* @return
*/
@RequestMapping(path = "/{city_id}/{user_id}", method = RequestMethod.GET)
public Object findUser(@PathVariable("city_id") String cityId,
@PathVariable("user_id") String userId ){
params.clear();
params.put("cityId", cityId);
params.put("userId", userId);
return params;
}
/**
* 功能描述:测试GetMapping
* @param from
* @param size
* @return
*/
@GetMapping(value="/v1/page_user1")
public Object pageUser(int from, int size ){
params.clear();
params.put("from", from);
params.put("size", size);
return params;
}
/**
* 功能描述:默认值,是否必须的参数,如果没有page参数from则为0
* @param from
* @param size
* @return
*/
@GetMapping(value="/v1/page_user2")
public Object pageUserV2(@RequestParam(defaultValue="0",name="page") int from, int size ){
params.clear();
params.put("from", from);
params.put("size", size);
return params;
}
/**
* 功能描述:bean对象传参
* 注意:1、注意需要指定http头为 content-type为application/json
* 2、使用body传输数据
* @param user
* @return
*/
@RequestMapping("/v1/save_user")
public Object saveUser(@RequestBody User user){
params.clear();
params.put("user", user);
return params;
}
/**
* 功能描述:测试获取http头信息
* @param accessToken
* @param id
* @return
*/
@GetMapping("/v1/get_header")
public Object getHeader(@RequestHeader("access_token") String accessToken, String id){
params.clear();
params.put("access_token", accessToken);
params.put("id", id);
return params;
}
/**
* 获取request
* @param request
* @return
*/
@GetMapping("/v1/test_request")
public Object testRequest(HttpServletRequest request){
params.clear();
String id = request.getParameter("id");
params.put("id", id);
return params;
}
}
private Map<String,Object> params = new HashMap<>();
/**
* 功能描述:测试PostMapping
* @param accessToken
* @param id
* @return
*/
@PostMapping("/v1/login")
public Object login(String id, String pwd){
params.clear();
params.put("id", id);
params.put("pwd", pwd);
return params;
}
@PutMapping("/v1/put")
public Object put(String id){
params.clear();
params.put("id", id);
return params;
}
@DeleteMapping("/v1/del")
public Object del(String id){
params.clear();
params.put("id", id);
return params;
}
json框架Jackson返回结果处理
1、常用框架 阿里 fastjson,谷歌gson等
JavaBean序列化为Json,性能:Jackson > FastJson > Gson > Json-lib 同个结构
Jackson、FastJson、Gson类库各有优点,各有自己的专长
空间换时间,时间换空间
2、jackson处理相关自动
指定字段不返回:@JsonIgnore
指定日期格式:@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
空字段不返回:@JsonInclude(Include.NON_NUll)
指定别名:@JsonProperty
package com.niliv.demo.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
public class User {
private int age;
@JsonIgnore
private String pwd;
@JsonProperty("account")
@JsonInclude(Include.NON_NULL)
private String phone;
@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
private Date createTime;
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public User() {
super();
}
public User(int age, String pwd, String phone, Date createTime) {
super();
this.age = age;
this.pwd = pwd;
this.createTime = createTime;
}
}
@GetMapping("/testjson")
public Object testjson(){
return new User(111, "abc123", "10001000", new Date());
}
目录文件结构
1、目录讲解
src/main/java:存放代码
src/main/resources
static: 存放静态文件,比如 css、js、image, (访问方式 http://localhost:8080/js/main.js)
templates:存放静态页面jsp,html,tpl
config:存放配置文件,application.properties
resources:
2、引入依赖 Thymeleaf
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
注意:如果不引人这个依赖包,html文件应该放在默认加载文件夹里面,
比如resources、static、public这个几个文件夹,才可以访问
3、同个文件的加载顺序,静态资源文件
Spring Boot 默认会挨个从
META/resources > resources > static > public 里面找是否存在相应的资源,如果有则直接返回。
4、默认配置
1)官网地址:https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc-static-content
2)spring.resources.static-locations = classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
5、静态资源文件存储在CDN
html放在templates下无法访问,可以加入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
再写一个跳转controller
@Controller
public class FileController {
@RequestMapping(value = "/api/v1/gopage")
public Object index() {
return "index";
}
resources,static,temploates等目录在url中不需要写,直接可以访问资源文件
自定义资源目录test
resources下新建application.properties
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/
test中的文件可以直接访问
文件上传实战
1)静态页面直接访问:localhost:8080/index.html
注意点:
如果想要直接访问html页面,则需要把html放在springboot默认加载的文件夹下面
2)MultipartFile 对象的transferTo方法,用于文件保存(效率和操作比原先用FileOutStream方便和高效)
访问路径 http://localhost:8080/images/39020dbb-9253-41b9-8ff9-403309ff3f19.jpeg
private static final String filePath = "G:\\全新版本SpringBoot2.x全套视频零基础入门到高级实战SpringBoot教程\\SpringBoot资料csdn\\第2章\\第7课\\xdclass_springboot\\src\\main\\resources\\static\\images\\";
@RequestMapping(value = "upload")
@ResponseBody
public JsonData upload(@RequestParam("head_img") MultipartFile file,HttpServletRequest request) {
//file.isEmpty(); 判断图片是否为空
//file.getSize(); 图片大小进行判断
String name = request.getParameter("name");
System.out.println("用户名:"+name);
// 获取文件名
String fileName = file.getOriginalFilename();
System.out.println("上传的文件名为:" + fileName);
// 获取文件的后缀名,比如图片的jpeg,png
String suffixName = fileName.substring(fileName.lastIndexOf("."));
System.out.println("上传的后缀名为:" + suffixName);
// 文件上传后的路径
fileName = UUID.randomUUID() + suffixName;
System.out.println("转换后的名称:"+fileName);
File dest = new File(filePath + fileName);
try {
file.transferTo(dest);
return new JsonData(0, fileName);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return new JsonData(-1, "fail to save ", null);
}
<form enctype="multipart/form-data" method="post" action="/upload">
文件:<input type="file" name="head_img"/>
姓名:<input type="text" name="name"/>
<input type="submit" value="上传"/>
</form>
package net.xdclass.demo.domain;
import java.io.Serializable;
public class JsonData implements Serializable {
private static final long serialVersionUID = 1L;
//状态码,0表示成功,-1表示失败
private int code;
//结果
private Object data;
//错误描述
private String msg;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void setCode(int code) {
this.code = code;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public JsonData(int code, Object data) {
super();
this.code = code;
this.data = data;
}
public JsonData(int code, String msg,Object data) {
super();
this.code = code;
this.msg = msg;
this.data = data;
}
}
在启动main文件中添加文件限制
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
//单个文件最大
factory.setMaxFileSize("10240KB"); //KB,MB
/// 设置总上传数据总大小
factory.setMaxRequestSize("1024000KB");
return factory.createMultipartConfig();
}
jar包方式运行
java -jar *.jar
1、文件大小配置,启动类里面配置
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
//单个文件最大
factory.setMaxFileSize("10240KB"); //KB,MB
/// 设置总上传数据总大小
factory.setMaxRequestSize("1024000KB");
return factory.createMultipartConfig();
}
2、打包成jar包,需要增加maven依赖
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
如果没加相关依赖,执行maven打包,运行后会报错:no main manifest attribute, in XXX.jar
GUI:反编译工具,作用就是用于把class文件转换成java文件
3、jar包里的文件上传和访问需要指定磁盘路径
application.properties中增加下面配置
1) web.images-path=/Users/jack/Desktop
2) spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/,file:${web.upload-path}
4、文件服务器:fastdfs,阿里云oss,nginx搭建一个简单的文件服务器
web.upload-path=/Users/jack/Desktop
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/,file:${web.upload-path}
热部署 配置文件注入
热部署
官方地址:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#using-boot-devtools
核心依赖包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
添加依赖后,在ide里面重启应用,后续修改后马上可以生效
classloader
不被热部署的文件
1、/META-INF/maven, /META-INF/resources, /resources, /static, /public, or /templates
2、指定文件不进行热部署 spring.devtools.restart.exclude=static/**,public/**
3、手工触发重启 spring.devtools.restart.trigger-file=trigger.txt
改代码不重启,通过一个文本去控制
https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#using-boot-devtools-restart-exclude
注意点:生产环境不要开启这个功能,如果用java -jar启动,springBoot是不会进行热部署的
web.upload-path=/Users/jack/Desktop
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/,file:${web.upload-path}
#指定某些文件不进行监听,即不会进行热加载
#spring.devtools.restart.exclude=application.properties
#通过触发器,去控制什么时候进行热加载部署新的文件
spring.devtools.restart.trigger-file=trigger.txt
//trigger.txt
version=2
配置文件介绍和注入
xml、properties、json、yaml
1、常见的配置文件 xx.yml, xx.properties,
1)YAML(Yet Another Markup Language)
写 YAML 要比写 XML 快得多(无需关注标签或引号)
使用空格 Space 缩进表示分层,不同层次之间的缩进可以使用不同的空格数目
注意:key后面的冒号,后面一定要跟一个空格,树状结构
application.properties示例
server.port=8090
server.session-timeout=30
server.tomcat.max-threads=0
server.tomcat.uri-encoding=UTF-8
application.yml示例
server:
port: 8090
session-timeout: 30
tomcat.max-threads: 0
tomcat.uri-encoding: UTF-8
2、默认示例文件仅作为指导。 不要将整个内容复制并粘贴到您的应用程序中,只挑选您需要的属性。
3、properties配置项参考:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#common-application-properties
如果需要修改,直接复制对应的配置文件,加到application.properties里面
比如 server.port=8081 # Server HTTP port.
SpringBoot注解把配置文件自动映射到属性和实体类实战
1、配置文件加载
方式一
1、Controller上面配置
@PropertySource({"classpath:resource.properties"})
2、增加属性
@Value("${test.name}")
private String name;
方式二:实体类配置文件
步骤:
1、添加 @Component 注解;
2、使用 @PropertySource 注解指定配置文件位置;
3、使用 @ConfigurationProperties 注解,设置相关属性;
4、必须 通过注入IOC对象Resource 进来 , 才能在类中使用获取的配置文件值。
@Autowired
private ServerSettings serverSettings;
例子:
@Configuration
@ConfigurationProperties(prefix="test")
@PropertySource(value="classpath:resource.properties")
public class ServerConstant {
常见问题:
1、配置文件注入失败,Could not resolve placeholder
解决:根据springboot启动流程,会有自动扫描包没有扫描到相关注解,
默认Spring框架实现会从声明@ComponentScan所在的类的package进行扫描,来自动注入,
因此启动类最好放在根路径下面,或者指定扫描包范围
spring-boot扫描启动类对应的目录和子目录
2、注入bean的方式,属性名称和配置文件里面的key一一对应,就不用加@Value 这个注解
如果不一样,就要加@value("${XXX}")
web.upload-path=/Users/jack/Desktop
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/,file:${web.upload-path}
#指定某些文件不进行监听,即不会进行热加载
#spring.devtools.restart.exclude=application.properties
#通过触发器,去控制什么时候进行热加载部署新的文件
spring.devtools.restart.trigger-file=trigger.txt
server.port=8081
#文件上传路径配置
web.file.path=/Users/jack/Desktop/
#测试配置文件注入
test.domain=www.xdclass.net
test.name=springboot
实体类注入
package net.xdclass.demo.domain;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
//服务器配置
@Component
@PropertySource({"classpath:application.properties"})
//@ConfigurationProperties
@ConfigurationProperties(prefix="test")
public class ServerSettings {
//名称
//@Value("${appname}")
private String name;
//@Value("${domain}")
private String domain;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
}
#测试配置文件注入
test.domain=www.xdclass.net
test.name=springboot
//controll
@Autowired
private ServerSettings serverSettings;
@GetMapping("/v1/test_properties")
public Object testPeroperties(){
return serverSettings;
}
单元测试
1、引入相关依赖
<!--springboot程序测试依赖,如果是自动创建项目默认添加-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2、使用
@RunWith(SpringRunner.class) //底层用junit SpringJUnit4ClassRunner
@SpringBootTest(classes={XdclassApplication.class})//启动整个springboot工程
public class SpringBootTests { }
@RunWith(SpringRunner.class) //底层用junit SpringJUnit4ClassRunner
@SpringBootTest(classes={XdclassApplication.class})//启动整个springboot工程
public class SpringBootTestDemo {
@Test
public void testOne(){
System.out.println("test hello 1");
TestCase.assertEquals(1, 1);
}
@Test
public void testTwo(){
System.out.println("test hello 2");
TestCase.assertEquals(1, 1);
}
@Before
public void testBefore(){
System.out.println("before");
}
@After
public void testAfter(){
System.out.println("after");
}
}
SpringBoot测试进阶高级篇之MockMvc讲解
1、增加类注解 @AutoConfigureMockMvc
@SpringBootTest(classes={XdclassApplication.class})
2、相关API
perform:执行一个RequestBuilder请求
andExpect:添加ResultMatcher->MockMvcResultMatchers验证规则
andReturn:最后返回相应的MvcResult->Response
/**
* 功能描述:测试mockmvc类
*
* <p> 创建时间:Apr 24, 2018 10:01:12 PM </p>
* <p> 作者:小D课堂</p>
*/
@RunWith(SpringRunner.class) //底层用junit SpringJUnit4ClassRunner
@SpringBootTest(classes={XdclassApplication.class}) //启动整个springboot工程
@AutoConfigureMockMvc
public class MockMvcTestDemo {
@Autowired
private MockMvc mockMvc;
@Test
public void apiTest() throws Exception {
MvcResult mvcResult = mockMvc.perform( MockMvcRequestBuilders.get("/test/home") ).
andExpect( MockMvcResultMatchers.status().isOk() ).andReturn();
int status = mvcResult.getResponse().getStatus();
System.out.println(status);
}
}
banner和debug
1、启动获取更多信息 java -jar xxx.jar --debug
2、修改启动的banner信息
1)在类路径下增加一个banner.txt,里面是启动要输出的信息
2)在applicatoin.properties增加banner文件的路径地址
spring.banner.location=banner.txt
3)官网地址 https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-banners
全局异常
1、默认异常测试 int i = 1/0,不友好
2、异常注解介绍
@ControllerAdvice 如果是返回json数据 则用 RestControllerAdvice,就可以不加 @ResponseBody
//捕获全局异常,处理所有不可知的异常
@ExceptionHandler(value=Exception.class)
@RestControllerAdvice
public class CustomExtHandler {
private static final Logger LOG = LoggerFactory.getLogger(CustomExtHandler.class);
//捕获全局异常,处理所有不可知的异常
@ExceptionHandler(value=Exception.class)
//@ResponseBody
Object handleException(Exception e,HttpServletRequest request){
LOG.error("url {}, msg {}",request.getRequestURL(), e.getMessage());
Map<String, Object> map = new HashMap<>();
map.put("code", 100);
map.put("msg", e.getMessage());
map.put("url", request.getRequestURL());
return map;
}
}
自定义异常,返回错误页面
1、返回自定义异常界面,需要引入thymeleaf依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2、resource目录下新建templates,并新建error.html
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error.html");
modelAndView.addObject("msg", e.getMessage());
return modelAndView;
https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-error-handling
/**
* 功能描述:异常测试
*
* <p> 创建时间:Apr 22, 2018 11:22:29 PM </p>
* <p> 作者:小D课堂</p>
*/
@RestController
public class ExcptionController {
/**
* 功能描述:模拟全局异常
* @return
*/
@RequestMapping(value = "/api/v1/test_ext")
public Object index() {
int i= 1/0;
return new User(11, "sfsfds", "1000000", new Date());
}
/**
* 功能描述:模拟自定义异常
* @return
*/
@RequestMapping("/api/v1/myext")
public Object myexc(){
throw new MyException("499", "my ext异常");
}
}
package net.xdclass.demo.domain;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.ModelAndView;
@RestControllerAdvice
public class CustomExtHandler {
private static final Logger LOG = LoggerFactory.getLogger(CustomExtHandler.class);
//捕获全局异常,处理所有不可知的异常
@ExceptionHandler(value=Exception.class)
Object handleException(Exception e,HttpServletRequest request){
LOG.error("url {}, msg {}",request.getRequestURL(), e.getMessage());
Map<String, Object> map = new HashMap<>();
map.put("code", 100);
map.put("msg", e.getMessage());
map.put("url", request.getRequestURL());
return map;
}
/**
* 功能描述:处理自定义异常
* @return
*/
@ExceptionHandler(value=MyException.class)
Object handleMyException(MyException e,HttpServletRequest request){
//进行页面跳转
// ModelAndView modelAndView = new ModelAndView();
// modelAndView.setViewName("error.html");
// modelAndView.addObject("msg", e.getMessage());
// return modelAndView;
//返回json数据,由前端去判断加载什么页面
Map<String, Object> map = new HashMap<>();
map.put("code", e.getCode());
map.put("msg", e.getMsg());
map.put("url", request.getRequestURL());
return map;
}
}
war tomcat9
1、ide启动
2、jar包方式启动
maven插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
如果没有加,则执行jar包 ,报错如下
java -jar spring-boot-demo-0.0.1-SNAPSHOT.jar
no main manifest attribute, in spring-boot-demo-0.0.1-SNAPSHOT.jar
如果有安装maven 用 mvn spring-boot:run
项目结构
example.jar
|
+-META-INF
| +-MANIFEST.MF
+-org
| +-springframework
| +-boot
| +-loader
| +-<spring boot loader classes>
+-BOOT-INF
+-classes
| +-mycompany
| +-project
| +-YourClasses.class
+-lib
+-dependency1.jar
+-dependency2.jar
目录结构讲解
https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#executable-jar-jar-file-structure
3、war包方式启动
1)在pom.xml中将打包形式 jar 修改为war <packaging>war</packaging>
构建项目名称 <finalName>xdclass_springboot</finalName>
2)tocmat下载 https://tomcat.apache.org/download-90.cgi
<br />
3)修改启动类<br />
public class XdclassApplication extends SpringBootServletInitializer {<br />
<br />
[@Override ](/Override ) <br />
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {<br />
return application.sources(XdclassApplication.class);<br />
}<br />
<br />
public static void main(String[] args) throws Exception {
SpringApplication.run(XdclassApplication.class, args);
}
}
4)打包项目,启动tomcat
war放入webapps
4、启动容器介绍和第三方测试数据讲解
使用Jmter测试工具测试性能,QPS,TPS,RT
https://examples.javacodegeeks.com/enterprise-java/spring/tomcat-vs-jetty-vs-undertow-comparison-of-spring-boot-embedded-servlet-containers/
package net.xdclass.base_project;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication //一个注解顶下面3个
public class XdclassApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(XdclassApplication.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(XdclassApplication.class, args);
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.xdclass</groupId>
<artifactId>base_project</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 打包方式 -->
<packaging>war</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<properties>
</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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!-- 打包的项目名称 -->
<finalName>xdclass_springboot</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
过滤,监听,拦
过滤器
filter简单理解:人--->检票员(filter)---> 景点
1、SpringBoot启动默认加载的Filter
characterEncodingFilter
hiddenHttpMethodFilter
httpPutFormContentFilter
requestContextFilter
2、Filter优先级
Ordered.HIGHEST_PRECEDENCE
Ordered.LOWEST_PRECEDENCE
低位值意味着更高的优先级 Higher values are interpreted as lower priority
自定义Filter,避免和默认的Filter优先级一样,不然会冲突
注册Filter的bean FilterRegistrationBean
同模块里面有相关默认Filter
web->servlet->filter
3、自定义Filter
1)使用Servlet3.0的注解进行配置
2)启动类里面增加 @ServletComponentScan,进行扫描
3)新建一个Filter类,implements Filter,并实现对应的接口
4) @WebFilter 标记一个类为filter,被spring进行扫描
urlPatterns:拦截规则,支持正则
6)控制chain.doFilter的方法的调用,来实现是否通过放行
不放行,web应用resp.sendRedirect("/index.html");
场景:权限控制、用户登录(非前端后端分离场景)等
1、官网地址:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-embedded-container-servlets-filters-listeners
package net.xdclass.demo.Filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebFilter(urlPatterns = "/api/*", filterName = "loginFilter")
public class LoginFilter implements Filter{
/**
* 容器加载的时候调用
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init loginFilter");
}
/**
* 请求被拦截的时候进行调用
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter loginFilter");
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
String username = req.getParameter("username");
if ("xdclass".equals(username)) {
filterChain.doFilter(servletRequest,servletResponse);
} else {
resp.sendRedirect("/index.html");
return;
}
}
/**
* 容器被销毁的时候被调用
*/
@Override
public void destroy() {
System.out.println("destroy loginFilter");
}
}
@GetMapping(value="/api/v1/account")
public Object account(){
params.put("money", "1000");
return params;
}
package net.xdclass.demo;
import javax.servlet.MultipartConfigElement;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
//@SpringBootApplication //一个注解顶下面3个
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
@ServletComponentScan //扫描filter,servlet等
public class XdclassApplication {
public static void main(String[] args) {
SpringApplication.run(XdclassApplication.class, args);
}
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
//单个文件最大
factory.setMaxFileSize("10240KB"); //KB,MB
/// 设置总上传数据总大小
factory.setMaxRequestSize("1024000KB");
return factory.createMultipartConfig();
}
}
自定义原生Servlet
@WebServlet(name = "userServlet",urlPatterns = "/test/customs")
public class UserServlet extends HttpServlet{
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("custom sevlet");
resp.getWriter().flush();
resp.getWriter().close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
监听器
(常用的监听器 servletContextListener、httpSessionListener、servletRequestListener)
@WebListener
public class RequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
// TODO Auto-generated method stub
System.out.println("======requestDestroyed========");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("======requestInitialized========");
}
@WebListener
public class RequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
// TODO Auto-generated method stub
System.out.println("======requestDestroyed========");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("======requestInitialized========");
}
}
@WebListener
public class CustomContextListener implements ServletContextListener{
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("======contextInitialized========");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("======contextDestroyed========");
}
}
拦截器
1、@Configuration
继承WebMvcConfigurationAdapter(SpringBoot2.X之前旧版本)
SpringBoot2.X 新版本配置拦截器 implements WebMvcConfigurer
2、自定义拦截器 HandlerInterceptor
preHandle:调用Controller某个方法之前
postHandle:Controller之后调用,视图渲染之前,如果控制器Controller出现了异常,则不会执行此方法
afterCompletion:不管有没有异常,这个afterCompletion都会被调用,用于资源清理
3、按照注册顺序进行拦截,先注册,先被拦截
拦截器不生效常见问题:
1)是否有加@Configuration
2)拦截路径是否有问题 ** 和 *
3)拦截器最后路径一定要 “/**”, 如果是目录的话则是 /*/
Filter
是基于函数回调 doFilter(),而Interceptor则是基于AOP思想
Filter在只在Servlet前后起作用,而Interceptor够深入到方法前后、异常抛出前后等
依赖于Servlet容器即web应用中,而Interceptor不依赖于Servlet容器所以可以运行在多种环境。
在接口调用的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次。
Filter和Interceptor的执行顺序
过滤前->拦截前->action执行->拦截后->过滤后
package net.xdclass.demo.intecpter;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CustomWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginIntercepter()).addPathPatterns("/api2/*/**");
registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/api2/*/**");
//.excludePathPatterns("/api2/xxx/**"); //拦截全部 /*/*/**
WebMvcConfigurer.super.addInterceptors(registry);
}
}
package net.xdclass.demo.intecpter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class LoginIntercepter implements HandlerInterceptor{
/**
* 进入controller方法之前
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("LoginIntercepter------->preHandle");
// String token = request.getParameter("access_token");
//
// response.getWriter().print("fail");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
/**
* 调用完controller之后,视图渲染之前
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("LoginIntercepter------->postHandle");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
/**
* 整个完成之后,通常用于资源清理
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("LoginIntercepter------->afterCompletion");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
package net.xdclass.demo.intecpter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class TwoIntercepter implements HandlerInterceptor{
/**
* 进入对应的controller方法之前
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("TwoIntercepter------>preHandle");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
/**
* controller处理之后,返回对应的视图之前
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("TwoIntercepter------>postHandle");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
/**
* 整个请求结束后调用,视图渲染后,主要用于资源的清理
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("TwoIntercepter------>afterCompletion");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
SpringBoot Starter
1、官网地址:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#using-boot-starter
2、starter主要简化依赖用的
spring-boot-starter-web ->里面包含多种依赖
3、几个常用的starter
spring-boot-starter-activemq
spring-boot-starter-aop
spring-boot-starter-data-redis
spring-boot-starter-freemarker
spring-boot-starter-thymeleaf
spring-boot-starter-webflux
模板引擎
1、JSP(后端渲染,消耗性能)
Java Server Pages 动态网页技术,由应用服务器中的JSP引擎来编译和执行,再将生成的整个页面返回给客户端
可以写java代码
持表达式语言(el、jstl)
内建函数
JSP->Servlet(占用JVM内存)permSize
javaweb官方推荐
springboot不推荐 https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-jsp-limitations
2、Freemarker
FreeMarker Template Language(FTL) 文件一般保存为 xxx.ftl
严格依赖MVC模式,不依赖Servlet容器(不占用JVM内存)
内建函数
3、Thymeleaf (主推)
轻量级的模板引擎(负责逻辑业务的不推荐,解析DOM或者XML会占用多的内存)
可以直接在浏览器中打开且正确显示模板页面
直接是html结尾,直接编辑
xdlcass.net/user/userinfo.html
社会工程学
伪装
freemarker
1、Freemarker相关maven依赖
<!-- 引入freemarker模板引擎的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
2、Freemarker基础配置
# 是否开启thymeleaf缓存,本地为false,生产建议为true
spring.freemarker.cache=false
spring.freemarker.charset=UTF-8
spring.freemarker.allow-request-override=false
spring.freemarker.check-template-location=true
#类型
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=true
spring.freemarker.expose-session-attributes=true
#文件后缀
spring.freemarker.suffix=.ftl
#路径
spring.freemarker.template-loader-path=classpath:/templates/
3、建立文件夹
1)src/main/resources/templates/fm/user/
2)建立一个index.ftl
3)user文件夹下面建立一个user.html
4、简单测试代码编写和访问
@Controller
@RequestMapping("/freemaker")
public class FreemakerController {
@Autowired
private ServerSettings setting;
@GetMapping("hello")
public String index(ModelMap modelMap){
modelMap.addAttribute("setting", setting);
return "fm/index"; //不用加后缀,在配置文件里面已经指定了后缀
}
}
thymeleaf
官网地址:https://www.thymeleaf.org/doc/articles/thymeleaf3migration.html
1、thymeleaf相关maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2、thymeleaf基础配置
#开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML5
#前缀
spring.thymeleaf.prefix=classpath:/templates/
#编码
spring.thymeleaf.encoding=UTF-8
#类型
spring.thymeleaf.content-type=text/html
#名称的后缀
spring.thymeleaf.suffix=.html
3、建立文件夹
1)src/main/resources/templates/tl/
2)建立一个index.html
4、简单测试代码编写和访问
注意:$表达式只能写在th标签内部
快速入门:https://www.thymeleaf.org/doc/articles/standarddialect5minutes.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
模板引擎整合thymeleaf admin/info.html
<h1 >测试内容,未加th表达式</h1>
<h1 th:text="${setting.name}">测试内容</h1>
<h1>xdclass.net</h1>
</body>
</html>
package net.xdclass.demo.controller;
import net.xdclass.demo.domain.ServerSettings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/tyhmeleaf")
public class ThymeleafController {
@Autowired
private ServerSettings setting;
@GetMapping("hello")
public String index(){
return "index"; //不用加后缀,在配置文件里面已经指定了后缀
}
@GetMapping("info")
public String admin(ModelMap modelMap){
modelMap.addAttribute("setting", setting);
return "admin/info"; //不用加后缀,在配置文件里面已经指定了后缀
}
}
web.upload-path=/Users/jack/Desktop
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/,file:${web.upload-path},classpath:/templates/
#指定某些文件不进行监听,即不会进行热加载
#spring.devtools.restart.exclude=application.properties
#通过触发器,去控制什么时候进行热加载部署新的文件
spring.devtools.restart.trigger-file=trigger.txt
server.port=8081
#文件上传路径配置
web.file.path=/Users/jack/Desktop/
#测试配置文件注入
test.domain=www.xdclass.net
test.name=springboot
#自定义启动banner文件的路径
spring.banner.location=banner.txt
##整合freemaker相关配置
## 是否开启thymeleaf缓存,本地为false,生产建议为true
#spring.freemarker.cache=false
#spring.freemarker.charset=UTF-8
#spring.freemarker.allow-request-override=false
#spring.freemarker.check-template-location=true
#
##类型
#spring.freemarker.content-type=text/html
#spring.freemarker.expose-request-attributes=true
#spring.freemarker.expose-session-attributes=true
#
##文件后缀
#spring.freemarker.suffix=.ftl
##路径
#spring.freemarker.template-loader-path=classpath:/templates/
#整合thymeleaf相关配置
#开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML5
#前缀
spring.thymeleaf.prefix=classpath:/templates/tl/
#编码
spring.thymeleaf.encoding=UTF-8
#类型
spring.thymeleaf.content-type=text/html
#名称的后缀
spring.thymeleaf.suffix=.html
mybatis,事务
持久化数据方式
1、原始java访问数据库
开发流程麻烦
1、注册驱动/加载驱动
Class.forName("com.mysql.jdbc.Driver")
2、建立连接
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbname","root","root");
3、创建Statement
4、执行SQL语句
5、处理结果集
6、关闭连接,释放资源
2、apache dbutils框架
比上一步简单点
官网:https://commons.apache.org/proper/commons-dbutils/
3、jpa框架
spring-data-jpa
jpa在复杂查询的时候性能不是很好
4、Hiberante 解释:ORM:对象关系映射Object Relational Mapping
企业大都喜欢使用hibernate
5、Mybatis框架
互联网行业通常使用mybatis
不提供对象和关系模型的直接映射,半ORM
整合Mybatis3.x
1、使用starter, maven仓库地址:http://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter
2、加入依赖(可以用 http://start.spring.io/ 下载)
<!-- 引入starter-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
<scope>runtime</scope>
</dependency>
<!-- MySQL的JDBC驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 引入第三方数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
3、加入配置文件
#mybatis.type-aliases-package=net.xdclass.base_project.domain
#可以自动识别
#spring.datasource.driver-class-name =com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/movie?useUnicode=true&characterEncoding=utf-8
spring.datasource.username =root
spring.datasource.password =password
#如果不使用默认的数据源 (com.zaxxer.hikari.HikariDataSource)
spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
加载配置,注入到sqlSessionFactory等都是springBoot帮我们完成
4、启动类增加mapper扫描
@MapperScan("net.xdclass.base_project.mapper")
技巧:保存对象,获取数据库自增id
@Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")
4、开发mapper
参考语法 http://www.mybatis.org/mybatis-3/zh/java-api.html
5、sql脚本
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) DEFAULT NULL COMMENT '名称',
`phone` varchar(16) DEFAULT NULL COMMENT '用户手机号',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`age` int(4) DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
相关资料:
http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/#Configuration
https://github.com/mybatis/spring-boot-starter/tree/master/mybatis-spring-boot-samples
整合问题集合:
https://my.oschina.net/hxflar1314520/blog/1800035
https://blog.csdn.net/tingxuetage/article/details/80179772
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.xdclass</groupId>
<artifactId>base_project</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 打包方式 -->
<packaging>war</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<properties>
</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-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- 引入starter-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- MySQL的JDBC驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 引入第三方数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
</dependencies>
<build>
<!-- 打包的项目名称 -->
<finalName>xdclass_springboot</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
spring.datasource.url=jdbc:mysql://118.24.175.34:3306/java_test?useUnicode=true&characterEncoding=utf-8
spring.datasource.username =root
spring.datasource.password =p@ssw0rd
#如果不使用默认的数据源 (com.zaxxer.hikari.HikariDataSource)
spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
@SpringBootApplication //一个注解顶下面3个
@MapperScan("net.xdclass.base_project.mapper")
public class XdclassApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(XdclassApplication.class, args);
}
}
public interface UserMapper {
//推荐使用#{}取值,不要用${},因为存在注入的风险
@Insert("INSERT INTO user(name,phone,create_time,age) VALUES(#{name}, #{phone}, #{createTime},#{age})")
@Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id") //keyProperty java对象的属性;keyColumn表示数据库的字段
int insert(User user);
}
@RestController
@RequestMapping("/api/v1/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 功能描述: user 保存接口
* @return
*/
@GetMapping("add")
public Object add(){
User user = new User();
user.setAge(11);
user.setCreateTime(new Date());
user.setName("xdclass");
user.setPhone("10010000");
int id = userService.add(user);
return JsonData.buildSuccess(id);
}
增删改查
1、控制台打印sql语句
#增加打印sql语句,一般用于本地开发测试
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
2、增加mapper代码
@Select("SELECT * FROM user")
@Results({
@Result(column = "create_time",property = "createTime") //javaType = java.util.Date.class
})
List<User> getAll();
@Select("SELECT * FROM user WHERE id = #{id}")
@Results({
@Result(column = "create_time",property = "createTime")
})
User findById(Long id);
@Update("UPDATE user SET name=#{name} WHERE id =#{id}")
void update(User user);
@Delete("DELETE FROM user WHERE id =#{userId}")
void delete(Long userId);
3、增加API
@GetMapping("find_all")
public Object findAll(){
return JsonData.buildSuccess(userMapper.getAll());
}
@GetMapping("find_by_Id")
public Object findById(long id){
return JsonData.buildSuccess(userMapper.findById(id));
}
@GetMapping("del_by_id")
public Object delById(long id){
userMapper.delete(id);
return JsonData.buildSuccess();
}
@GetMapping("update")
public Object update(String name,int id){
User user = new User();
user.setName(name);
user.setId(id);
userMapper.update(user);
return JsonData.buildSuccess();
}
事务
简介:讲解什么是数据库事务,常见的隔离级别和传播行为
1、介绍什么是事务,单机事务,分布式事务处理等
2、讲解场景的隔离级别
Serializable: 最严格,串行处理,消耗资源大
Repeatable Read:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据
Read Committed:大多数主流数据库的默认事务等级
Read Uncommitted:保证了读取过程中不会读取到非法数据。
3、讲解常见的传播行为
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务,最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起, 两个事务之间没有关系,一个异常,一个提交,不会同时回滚
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常
1、service逻辑引入事务 @Transantional(propagation=Propagation.REQUIRED)
2、service代码
@Override
@Transactional
public int addAccount() {
User user = new User();
user.setAge(9);
user.setCreateTime(new Date());
user.setName("事务测试");
user.setPhone("000121212");
userMapper.insert(user);
int a = 1/0;
return user.getId();
}
#可以自动识别
#spring.datasource.driver-class-name =com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://118.24.175.34:3306/java_test?useUnicode=true&characterEncoding=utf-8
spring.datasource.username =root
spring.datasource.password =p@ssw0rd
#使用阿里巴巴druid数据源,默认使用自带的
spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
package net.xdclass.base_project.service.impl;
import java.util.Date;
import net.xdclass.base_project.domain.User;
import net.xdclass.base_project.mapper.UserMapper;
import net.xdclass.base_project.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public int add(User user) {
userMapper.insert(user);
int id = user.getId();
return id;
}
@Override
@Transactional(propagation=Propagation.REQUIRED)
public int addAccount() {
User user = new User();
user.setAge(88);
user.setCreateTime(new Date());
user.setName("测试事务啦加入事务");
user.setPhone("10010101010");
userMapper.insert(user);
int i = 19/0;
return 0;
}
}
//测试事务
@GetMapping("add_account")
public Object addAccount(){
int id = userService.addAccount();
return JsonData.buildSuccess(id);
}
redis
redis官网 https://redis.io/download
新手入门redis在线测试工具:http://try.redis.io/
1、快速安装 https://redis.io/download#installation
wget http://download.redis.io/releases/redis-4.0.9.tar.gz
tar xzf redis-4.0.9.tar.gz
cd redis-4.0.9
make
启动服务端:src/redis-server
启动客户端:src/redis-cli
2、默认是本地访问的,需要开放外网访问
1)打开redis.conf文件在NETWORK部分修改
注释掉bind 127.0.0.1可以使所有的ip访问redis
修改 protected-mode,值改为no
简介:使用springboot-starter整合reids实战
1、官网:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-redis
集群文档:https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#cluster
2、springboot整合redis相关依赖引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3、相关配置文件配置
#=========redis基础配置=========
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6390
# 连接超时时间 单位 ms(毫秒)
spring.redis.timeout=3000
#=========redis线程池设置=========
# 连接池中的最大空闲连接,默认值也是8。
spring.redis.pool.max-idle=200
#连接池中的最小空闲连接,默认值也是0。
spring.redis.pool.min-idle=200
# 如果赋值为-1,则表示不限制;pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
spring.redis.pool.max-active=2000
# 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时
spring.redis.pool.max-wait=1000
4、常见redistemplate种类讲解和缓存实操(使用自动注入)
1、注入模板
@Autowired
private StirngRedisTemplate strTplRedis
2、类型String,List,Hash,Set,ZSet
对应的方法分别是opsForValue()、opsForList()、opsForHash()、opsForSet()、opsForZSet()
#通过触发器,去控制什么时候进行热加载部署新的文件
spring.devtools.restart.trigger-file=trigger.txt
#自定义启动banner文件的路径
spring.banner.location=banner.txt
#=========redis基础配置=========
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
# 连接超时时间 单位 ms(毫秒)
spring.redis.timeout=3000
#=========redis线程池设置=========
# 连接池中的最大空闲连接,默认值也是8。
spring.redis.pool.max-idle=200
#连接池中的最小空闲连接,默认值也是0。
spring.redis.pool.min-idle=200
# 如果赋值为-1,则表示不限制;pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
spring.redis.pool.max-active=2000
# 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时
spring.redis.pool.max-wait=1000
package net.xdclass.base_project.controller;
import net.xdclass.base_project.domain.JsonData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/redis")
public class RdisTestController {
@Autowired
private StringRedisTemplate redisTpl; //jdbcTemplate
@GetMapping(value="add")
public Object add(){
//opsForValue : Returns the operations performed on simple values (or Strings in Redis terminology).
redisTpl.opsForValue().set("name", "xdclass2018");
return JsonData.buildSuccess();
}
@GetMapping(value="get")
public Object get(){
String value = redisTpl.opsForValue().get("name");
return JsonData.buildSuccess(value);
}
}
常用客户端 https://redisdesktop.com/download
封装redis工具类并操作
package net.xdclass.base_project.utils;
import java.io.IOException;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonUtils {
private static ObjectMapper objectMapper = new ObjectMapper();
//对象转字符串
public static <T> String obj2String(T obj){
if (obj == null){
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//字符串转对象
public static <T> T string2Obj(String str,Class<T> clazz){
if (StringUtils.isEmpty(str) || clazz == null){
return null;
}
try {
return clazz.equals(String.class)? (T) str :objectMapper.readValue(str,clazz);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
package net.xdclass.base_project.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
/**
* 功能描述:redis工具类
*
* <p> 创建时间:Apr 29, 2018 10:07:30 PM </p>
*
*@作者 小D课堂 小D
*/
@Component
public class RedisClient {
@Autowired
private StringRedisTemplate redisTpl; //jdbcTemplate
/**
* 功能描述:设置key-value到redis中
* @param key
* @param value
* @return
*/
public boolean set(String key ,String value){
try{
redisTpl.opsForValue().set(key, value);
return true;
}catch(Exception e){
e.printStackTrace();
return false;
}
}
/**
* 功能描述:通过key获取缓存里面的值
* @param key
* @return
*/
public String get(String key){
return redisTpl.opsForValue().get(key);
}
// @Autowired
// private StringRedisTemplate redisTemplate;
//
//
// /**
// * 通过字符串key获取值
// * @param key 键
// * @return 值
// */
// public String get(String key){
// return key==null?null:redisTemplate.opsForValue().get(key);
// }
//
//
// /**
// * 普通缓存放入
// * @param key 键
// * @param value 值
// * @return true成功 false失败
// */
// public boolean set(String key,String value) {
// try {
// redisTemplate.opsForValue().set(key, value);
// return true;
// } catch (Exception e) {
// e.printStackTrace();
// return false;
// }
//
// }
//
//
// /**
// * 功能描述:设置某个key过期时间
// * @param key
// * @param time
// * @return
// */
// public boolean expire(String key,long time){
// try {
// if(time>0){
// redisTemplate.expire(key, time, TimeUnit.SECONDS);
// }
// return true;
// } catch (Exception e) {
// e.printStackTrace();
// return false;
// }
// }
//
//
//
//
// /**
// * 功能描述:根据key 获取过期时间
// * @param key
// * @return
// */
// public long getExpire(String key){
// return redisTemplate.getExpire(key,TimeUnit.SECONDS);
// }
//
//
// /**
// * 递增
// * @param key 键
// * @return
// */
// public long incr(String key, long delta){
// return redisTemplate.opsForValue().increment(key, delta);
// }
//
//
// /**
// * 递减
// * @param key 键
// * @param delta 要减少几
// * @return
// */
// public long decr(String key, long delta){
// return redisTemplate.opsForValue().increment(key, -delta);
// }
//
// //==============Map结构=====================
//
//
// //==============List结构=====================
//
//
//
}
package net.xdclass.base_project.domain;
import java.util.Date;
public class User {
private int age;
private String pwd;
private String phone;
private Date createTime;
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public User() {
super();
}
public User(int age, String pwd, String phone, Date createTime) {
super();
this.age = age;
this.pwd = pwd;
this.phone = phone;
this.createTime = createTime;
}
}
package net.xdclass.base_project.domain;
import java.io.Serializable;
/**
* 功能描述:响应结果类
*
* <p>
* 创建时间:Apr 29, 2018 4:08:36 PM
* </p>
*
* @作者 小D课堂 小D
*/
public class JsonData implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer code; // 状态码 0 表示成功,1表示处理中,-1表示失败
private Object data; // 数据
private String msg;// 描述
public JsonData() {
}
public JsonData(Integer code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
// 成功,传入数据
public static JsonData buildSuccess() {
return new JsonData(0, null, null);
}
// 成功,传入数据
public static JsonData buildSuccess(Object data) {
return new JsonData(0, data, null);
}
// 失败,传入描述信息
public static JsonData buildError(String msg) {
return new JsonData(-1, null, msg);
}
// 失败,传入描述信息,状态码
public static JsonData buildError(String msg, Integer code) {
return new JsonData(code, null, msg);
}
// 成功,传入数据,及描述信息
public static JsonData buildSuccess(Object data, String msg) {
return new JsonData(0, data, msg);
}
// 成功,传入数据,及状态码
public static JsonData buildSuccess(Object data, int code) {
return new JsonData(code, data, null);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String toString() {
return "JsonData [code=" + code + ", data=" + data + ", msg=" + msg
+ "]";
}
}
package net.xdclass.base_project.controller;
import java.util.Date;
import net.xdclass.base_project.domain.JsonData;
import net.xdclass.base_project.domain.User;
import net.xdclass.base_project.utils.JsonUtils;
import net.xdclass.base_project.utils.RedisClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/redis")
public class RdisTestController {
@Autowired
private StringRedisTemplate redisTpl; //jdbcTemplate
@Autowired
private RedisClient redis;
@GetMapping(value="add")
public Object add(){
//redisTpl.opsForValue().set("name", "xdclass2018");
redis.set("username", "xddddddd");
return JsonData.buildSuccess();
}
@GetMapping(value="get")
public Object get(){
//String value = redisTpl.opsForValue().get("name");
String value = redis.get("username");
return JsonData.buildSuccess(value);
}
@GetMapping(value="save_user")
public Object saveUser(){
User user = new User(1, "abc", "11", new Date());
String userStr = JsonUtils.obj2String(user);
boolean flag = redis.set("base:user:11", userStr);
return JsonData.buildSuccess(flag);
}
@GetMapping(value="find_user")
public Object findUser(){
String userStr = redis.get("base:user:11");
User user = JsonUtils.string2Obj(userStr, User.class);
return JsonData.buildSuccess(user);
}
}
任务
1、常见定时任务 Java自带的java.util.Timer类
timer:配置比较麻烦,时间延后问题
timertask:不推荐
2、Quartz框架
配置更简单
xml或者注解
3、SpringBoot使用注解方式开启定时任务
1)启动类里面 @EnableScheduling开启定时任务,自动扫描
2)定时任务业务类 加注解 @Component被容器扫描
3)定时执行的方法加上注解 @Scheduled(fixedRate=2000) 定期执行一次
@SpringBootApplication //一个注解顶下面3个
@EnableScheduling //开启定时任务
public class XdclassApplication {
public static void main(String[] args) {
SpringApplication.run(XdclassApplication.class, args);
}
}
/**
* 功能描述:定时任务业务类
*
* <p> 创建时间:Apr 30, 2018 10:21:48 AM </p>
*
*@作者 小D课堂 小D
*/
@Component
public class TestTask {
@Scheduled(fixedRate=2000) //两秒执行一次
public void sum(){
System.out.println("当前时间:"+new Date());
}
}
1、cron 定时任务表达式 @Scheduled(cron="*/1 * * * * *") 表示每秒
1)crontab 工具 https://tool.lu/crontab/
2、fixedRate: 定时多久执行一次(上一次开始执行时间点后xx秒再次执行;)
3、fixedDelay: 上一次执行结束时间点后xx秒再次执行
4、fixedDelayString: 字符串形式,可以通过配置文件指定
package net.xdclass.base_project.task;
import java.util.Date;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 功能描述:定时任务业务类
*
* <p> 创建时间:Apr 30, 2018 10:21:48 AM </p>
*
*@作者 小D课堂 小D
*/
@Component
public class TestTask {
@Scheduled(fixedRateString="2000")//两秒执行一次
//@Scheduled(cron="*/2 * * * * *")
public void sum() throws InterruptedException{
Thread.sleep(4000L);
System.out.println("结束 当前时间:"+new Date());
}
//@Scheduled(cron="*/1 * * * * *")
public void sum2(){
System.out.println("cron 每秒 当前时间:"+new Date());
}
}
异步任务
2、启动类里面使用@EnableAsync注解开启功能,自动扫描
3、定义异步任务类并使用@Component标记组件被容器扫描,异步方法加上@Async
注意点:
1)要把异步任务封装到类里面,不能直接写到Controller
2)增加Future<String> 返回结果 AsyncResult<String>("task执行完成");
3)如果需要拿到结果 需要判断全部的 task.isDone()
4、通过注入方式,注入到controller里面,如果测试前后区别则改为同步则把Async注释掉
package net.xdclass.base_project;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication //一个注解顶下面3个
@EnableScheduling //开启定时任务
@EnableAsync //开启异步任务
public class XdclassApplication {
public static void main(String[] args) {
SpringApplication.run(XdclassApplication.class, args);
}
}
package net.xdclass.base_project.task;
import java.util.concurrent.Future;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
/**
* 功能描述:异步任务业务类
*
* <p> 创建时间:Apr 30, 2018 11:25:15 PM </p>
*
*@作者 小D课堂 小D
*/
@Component
@Async
public class AsyncTask {
public void task1() throws InterruptedException{
long begin = System.currentTimeMillis();
Thread.sleep(1000L);
long end = System.currentTimeMillis();
System.out.println("任务1耗时="+(end-begin));
}
public void task2() throws InterruptedException{
long begin = System.currentTimeMillis();
Thread.sleep(2000L);
long end = System.currentTimeMillis();
System.out.println("任务2耗时="+(end-begin));
}
public void task3() throws InterruptedException{
long begin = System.currentTimeMillis();
Thread.sleep(3000L);
long end = System.currentTimeMillis();
System.out.println("任务3耗时="+(end-begin));
}
//获取异步结果
public Future<String> task4() throws InterruptedException{
long begin = System.currentTimeMillis();
Thread.sleep(2000L);
long end = System.currentTimeMillis();
System.out.println("任务4耗时="+(end-begin));
return new AsyncResult<String>("任务4");
}
public Future<String> task5() throws InterruptedException{
long begin = System.currentTimeMillis();
Thread.sleep(3000L);
long end = System.currentTimeMillis();
System.out.println("任务5耗时="+(end-begin));
return new AsyncResult<String>("任务5");
}
public Future<String> task6() throws InterruptedException{
long begin = System.currentTimeMillis();
Thread.sleep(1000L);
long end = System.currentTimeMillis();
System.out.println("任务6耗时="+(end-begin));
return new AsyncResult<String>("任务6");
}
}
package net.xdclass.base_project.controller;
import java.util.concurrent.Future;
import net.xdclass.base_project.domain.JsonData;
import net.xdclass.base_project.task.AsyncTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1")
public class UserController {
@Autowired
private AsyncTask task;
@GetMapping("async_task")
public JsonData exeTask() throws InterruptedException{
long begin = System.currentTimeMillis();
// task.task1();
// task.task2();
// task.task3();
// long end = System.currentTimeMillis();
// long total = end-begin;
// System.out.println("执行总耗时="+total);
// return JsonData.buildSuccess(total);
Future<String> task4 = task.task4();
Future<String> task5 = task.task5();
Future<String> task6 = task.task6();
for(;;){
if (task4.isDone() && task5.isDone() && task6.isDone()) {
break;
}
}
long end = System.currentTimeMillis();
long total = end-begin;
System.out.println("执行总耗时="+total);
return JsonData.buildSuccess(total);
}
}
日志
1.常用处理java的日志组件 slf4j,log4j,logback,common-logging 等
2、logback介绍:基于Log4j基础上大量改良,不能单独使用,推荐配合日志框架SLF4J来使用
logback当前分成三个模块:logback-core,logback-classic和logback-access;
logback-core是其它两个模块的基础模块
3、Logback的核心对象:
Logger:日志记录器
Appender:指定日志输出的目的地,目的地可以是控制台,文件
Layout:日志布局 格式化日志信息的输出
<br />
4、日志级别:DEBUG < INFO < WARN < ERROR<br />
<br />
=log4j示例= <br />
### 设置###<br />
log4j.rootLogger = debug,stdout,D,E<br />
<br />
输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
### 输出DEBUG 级别以上的日志到=D://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = D://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
### 输出ERROR 级别以上的日志到=D://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
===========logback============
4、Log4j日志转换为logback在线工具(支持log4j.properties转换为logback.xml,不支持 log4j.xml转换为logback.xml)
https://logback.qos.ch/translator/
自定义Logback配置实战
1、官网介绍:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-logging
各个组件案例:https://logback.qos.ch/manual/index.html
2、分析SpringBoot启动日志
1)默认情况下,Spring Boot将日志输出到控制台
3、整合Logback实战
1)创建 日志文件logback-spring.xml,官方推荐 -spring.xml结尾
默认加载加载配置顺序 logback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy
注释:
<configuration> 子节点
<appender></appender>
<logger></logger>
<root></root>(要加在最后)
//logback-spring.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="consoleApp" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
</pattern>
</layout>
</appender>
<appender name="fileInfoApp" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<pattern>
%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
</pattern>
</encoder>
<!-- 滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 路径 -->
<fileNamePattern>app_log/log/app.info.%d.log</fileNamePattern>
</rollingPolicy>
</appender>
<appender name="fileErrorApp" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>
%date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
</pattern>
</encoder>
<!-- 设置滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 路径 -->
<fileNamePattern>app_log/log/app.err.%d.log</fileNamePattern>
<!-- 控制保留的归档文件的最大数量,超出数量就删除旧文件,假设设置每个月滚动,
且<maxHistory> 是1,则只保存最近1个月的文件,删除之前的旧文件 -->
<MaxHistory>1</MaxHistory>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="consoleApp"/>
<appender-ref ref="fileInfoApp"/>
<appender-ref ref="fileErrorApp"/>
</root>
</configuration>
package net.xdclass.base_project.controller;
import net.xdclass.base_project.domain.JsonData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1")
public class UserController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping("log")
public Object testLog(){
logger.debug("this is debug level");
logger.info("this is info level ");
logger.warn("this is warn level ");
logger.error("this is error level");
return JsonData.buildSuccess();
}
}
ElasticSearch
前言:介绍ES的主要特点和使用场景,新特性讲解
mysql:like 模糊,性能问题,
solr:针对企业,Lucene
elasticsearch:针对数据量特别大,PB,TB
纯java开发,springboot使用,5.6版本
es升级4->5版本,改动大,但是5版本后,改动不大
elasticSearch主要特点
1、特点:全文检索,结构化检索,数据统计、分析,接近实时处理,分布式搜索(可部署数百台服务器),处理PB级别的数据
搜索纠错,自动完成
2、使用场景:日志搜索,数据聚合,数据监控,报表统计分析
3、国内外使用者:维基百科,Stack Overflow,GitHub
新特性讲解
1、6.2.x版本基于Lucene 7.x,更快,性能进一步提升,对应的序列化组件,升级到Jackson 2.8
mysql:database table rocord
es : index type(只能存在一个) document
2、推荐使用5.0版本推出的Java REST/HTTP客户端,依赖少,比Transport使用更方便,在基准测试中,性能并不输于Transport客户端,
在5.0到6.0版本中,每次有对应的API更新, 文档中也说明,推荐使用这种方式进行开发使用,所有可用节点间的负载均衡
在节点故障和特定响应代码的情况下进行故障转移,失败的连接处罚(失败的节点是否重试取决于失败的连续次数;失败的失败次数越多,客户端在再次尝试同一节点之前等待的时间越长)
3、(重要)不再支持一个索引库里面多个type,6.x版本已经禁止一个index里面多个type,所以一个index索引库只能存在1个type
官方文档:
1、6.0更新特性
https://www.elastic.co/guide/en/elasticsearch/reference/6.0/release-notes-6.0.0.html#breaking-java-6.0.0
2、6.1更新特性
https://www.elastic.co/guide/en/elasticsearch/reference/6.1/release-notes-6.1.0.html
快熟部署ElastcSearch5.6.x
配置JDK1.8
使用wget 下载elasticsearch安装包
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.8.tar.gz
解压
tar -zxvf elasticsearch-5.6.8.tar.gz
官网:https://www.elastic.co/products/elasticsearch
外网访问配置:
config目录下面elasticsearch.yml
修改为 network.host: 0.0.0.0
配置es出现相关问题处理(阿里云、腾讯云,亚马逊云安装问题集合):
1、问题一
Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000c5330000, 986513408, 0) failed; error='Cannot allocate memory' (errno=12)
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 986513408 bytes for committing reserved memory.
# An error report file with more information is saved as:
# /usr/local/software/temp/elasticsearch-6.2.2/hs_err_pid1912.log
解决:内存不够,购买阿里云的机器可以动态增加内存
2、问题二
[root@iZwz95j86y235aroi85ht0Z bin]# ./elasticsearch
[2018-02-22T20:14:04,870][WARN ][o.e.b.ElasticsearchUncaughtExceptionHandler] [] uncaught exception in thread [main]
org.elasticsearch.bootstrap.StartupException: java.lang.RuntimeException: can not run elasticsearch as root
at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:125) ~[elasticsearch-6.2.2.jar:6.2.2]
at org.elasticsearch.bootstrap.Elasticsearch.execute(Elasticsearch.java:112) ~[elasticsearch-6.2.2.jar:6.2.2]
at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86) ~[elasticsearch-6.2.2.jar:6.2.2]
at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124) ~[elasticsearch-cli-6.2.2.jar:6.2.2]
解决:用非root用户
添加用户:useradd -m 用户名 然后设置密码 passwd 用户名
3、问题三
./elasticsearch
Exception in thread "main" java.nio.file.AccessDeniedException: /usr/local/software/temp/elasticsearch-6.2.2/config/jvm.options
解决:权限不够 chmod 777 -R 当前es目录
常见配置问题资料:https://www.jianshu.com/p/c5d6ec0f35e0
lasticSearch5.6.8测试数据准备
简介: ElasticSearch5.6.x简单测试
1、步骤 https://www.elastic.co/guide/en/elasticsearch/reference/5.6/index.html
2、使用POSTMAN 工具
基础
查看集群状态:localhost:9200/_cat/health?v
查看索引列表:localhost:9200/_cat/indices?v
4、SpringBoot2.x整合elasticsearch5.6.x
简介:SpringBoot2.x整合elasticSearch5.6.8实战
Spring Data Elasticsearch文档地址
https://docs.spring.io/spring-data/elasticsearch/docs/3.0.6.RELEASE/reference/html/
版本说明:SpringBoot整合elasticsearch
https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix
1、添加maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2、接口继承ElasticSearchRepository,里面有很多默认实现
注意点:
索引名称记得小写,类属性名称也要小写
新建实体对象article
加上类注解 @Document(indexName = "blog", type = "article")
3、配置文件:
ELASTICSEARCH (ElasticsearchProperties)
spring.data.elasticsearch.cluster-name=elasticsearch # Elasticsearch cluster name.
spring.data.elasticsearch.cluster-nodes=localhost:9300 # Comma-separated list of cluster node addresses.
spring.data.elasticsearch.repositories.enabled=true # Whether to enable Elasticsearch repositories.
4、QueryBuilder使用
https://www.elastic.co/guide/en/elasticsearch/client/java-api/1.3/query-dsl-queries.html
//单个匹配,搜索name为jack的文档
QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "搜");
4、查看es数据
查看索引信息:http://localhost:9200/_cat/indices?v
查看某个索引库结构:http://localhost:9200/blog
查看某个对象:http://localhost:9200/blog/article/1
package net.xdclass.base_project.repository;
import net.xdclass.base_project.domain.Article;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
@Component
//@Repository
public interface ArticleRepository extends ElasticsearchRepository<Article, Long> {
}
package net.xdclass.base_project.controller;
import net.xdclass.base_project.domain.Article;
import net.xdclass.base_project.domain.JsonData;
import net.xdclass.base_project.repository.ArticleRepository;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/article")
public class ArticleController {
@Autowired
private ArticleRepository articleRepository;
@GetMapping("save")
public Object save(long id,String title){
Article article = new Article();
article.setId(id);
article.setPv(123);
article.setContent("springboot整合elasticsearch,这个是新版本 2018年录制");
article.setTitle(title);
article.setSummary("搜索框架整合");
articleRepository.save(article);
return JsonData.buildSuccess();
}
@GetMapping("search")
public Object search(String title){
//QueryBuilder queryBuilder = QueryBuilders.matchAllQuery(); //搜索全部文档
QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", title);
Iterable<Article> list = articleRepository.search(queryBuilder);
return JsonData.buildSuccess(list);
}
}
消息服务
介绍
JMS介绍和使用场景及基础编程模型
简介:讲解什么是小写队列,JMS的基础知识和使用场景
1、什么是JMS: Java消息服务(Java Message Service),Java平台中关于面向消息中间件的接口
2、JMS是一种与厂商无关的 API,用来访问消息收发系统消息,它类似于JDBC(Java Database Connectivity)。这里,JDBC 是可以用来访问许多不同关系数据库的 API
3、使用场景:
1)跨平台
2)多语言
3)多项目
4)解耦
5)分布式事务
6)流量控制
7)最终一致性
8)RPC调用
上下游对接,数据源变动->通知下属
4、概念
JMS提供者:Apache ActiveMQ、RabbitMQ、Kafka、Notify、MetaQ、RocketMQ
JMS生产者(Message Producer)
JMS消费者(Message Consumer)
JMS消息
JMS队列
JMS主题
JMS消息通常有两种类型:点对点(Point-to-Point)、发布/订阅(Publish/Subscribe)
5、编程模型
MQ中需要用的一些类
ConnectionFactory :连接工厂,JMS 用它创建连接
Connection :JMS 客户端到JMS Provider 的连接
Session: 一个发送或接收消息的线程
Destination :消息的目的地;消息发送给谁.
MessageConsumer / MessageProducer: 消息接收者,消费者
ActiveMQ
腾讯云服务器安装aq的问题解决方案
https://blog.csdn.net/qq_38880340/article/details/85147033
http://activemq.2283324.n4.nabble.com/Connection-refused-with-embeded-AMQ-in-Tomcat-td2363352.html
客户端访问 http://118.24.175.34:8161/admin/
简介:介绍ActiveMQ5.x消息队列基础特性和本地快速安装
特点:
1)支持来自Java,C,C ++,C#,Ruby,Perl,Python,PHP的各种跨语言客户端和协议
2)支持许多高级功能,如消息组,虚拟目标,通配符和复合目标
3) 完全支持JMS 1.1和J2EE 1.4,支持瞬态,持久,事务和XA消息
4) Spring支持,ActiveMQ可以轻松嵌入到Spring应用程序中,并使用Spring的XML配置机制进行配置
5) 支持在流行的J2EE服务器(如TomEE,Geronimo,JBoss,GlassFish和WebLogic)中进行测试
6) 使用JDBC和高性能日志支持非常快速的持久化
...
1、下载地址:http://activemq.apache.org/activemq-5153-release.html
2、快速开始:http://activemq.apache.org/getting-started.html
3、如果我们是32位的机器,就双击win32目录下的activemq.bat,如果是64位机器,则双击win64目录下的activemq.bat
4、bin目录里面启动 选择对应的系统版本和位数,activeMQ start 启动
5、启动后访问路径http://127.0.0.1:8161/
6、用户名和密码默认都是admin
7、官方案例集合
https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples
面板:
Name:队列名称。
Number Of Pending Messages:等待消费的消息个数。
Number Of Consumers:当前连接的消费者数目
Messages Enqueued:进入队列的消息总个数,包括出队列的和待消费的,这个数量只增不减。
Messages Dequeued:已经消费的消息数量。
点对点消息(p2p)
简介:SpringBoot2.x整合ActiveMQ实战之点对点消息
1、官网地址:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-activemq
2、加入依赖
<!-- 整合消息队列ActiveMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<!-- 如果配置线程池则加入 -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
</dependency>
3、application.properties配置文件配置
#整合jms测试,安装在别的机器,防火墙和端口号记得开放
spring.activemq.broker-url=tcp://127.0.0.1:61616
#集群配置
#spring.activemq.broker-url=failover:(tcp://localhost:61616,tcp://localhost:61617)
spring.activemq.user=admin
spring.activemq.password=admin
#下列配置要增加依赖
spring.activemq.pool.enabled=true
spring.activemq.pool.max-connections=100
4、springboot启动类 @EnableJms,开启支持jms
5、模拟请求
localhost:8080/api/v1/order?msg=12312321321312
6、消费者:实时监听对应的队列
@JmsListener(destination = "order.queue")
订阅模式(pub/sub)
1、需要加入配置文件,支持发布订阅模型,默认只支持点对点
#default point to point
spring.jms.pub-sub-domain=true
注意点:
1、默认消费者并不会消费订阅发布类型的消息,这是由于springboot默认采用的是p2p模式进行消息的监听
修改配置:spring.jms.pub-sub-domain=true
2、@JmsListener如果不指定独立的containerFactory的话是只能消费queue消息
修改订阅者container:containerFactory="jmsListenerContainerTopic"
//需要给topic定义独立的JmsListenerContainer
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setPubSubDomain(true);
bean.setConnectionFactory(activeMQConnectionFactory);
return bean;
}
在配置文件里面,注释掉 #spring.jms.pub-sub-domain=true
#通过触发器,去控制什么时候进行热加载部署新的文件
spring.devtools.restart.trigger-file=trigger.txt
#自定义启动banner文件的路径
spring.banner.location=banner.txt
#整合jms测试,安装在别的机器,防火墙和端口号记得开放
spring.activemq.broker-url=tcp://118.24.175.34:61616
#集群配置
#spring.activemq.broker-url=failover:(tcp://localhost:61616,tcp://localhost:61617)
spring.activemq.user=admin
spring.activemq.password=admin
#下列配置要增加依赖
spring.activemq.pool.enabled=true
spring.activemq.pool.max-connections=100
#default point to point
# spring.jms.pub-sub-domain=true
package net.xdclass.base_project;
import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.jms.Topic;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
@SpringBootApplication //一个注解顶下面3个
@EnableJms
public class XdclassApplication {
@Bean
public Topic topic(){
return new ActiveMQTopic("video.topic");
}
@Bean
public Queue queue(){
return new ActiveMQQueue("common.queue");
}
public static void main(String[] args) {
SpringApplication.run(XdclassApplication.class, args);
}
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setPubSubDomain(true);
bean.setConnectionFactory(activeMQConnectionFactory);
return bean;
}
}
package net.xdclass.base_project.service.impl;
import javax.jms.Destination;
import javax.jms.Queue;
import javax.jms.Topic;
import net.xdclass.base_project.service.ProducerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
/**
* 功能描述:消息生产者
*
* <p> 创建时间:May 2, 2018 11:29:47 PM </p>
*
*@作者 小D课堂 小D
*/
@Service
public class ProducerServiceImpl implements ProducerService{
@Autowired
private Queue queue;
@Autowired
private JmsMessagingTemplate jmsTemplate; //用来发送消息到broker的对象
//发送消息,destination是发送到的队列,message是待发送的消息
@Override
public void sendMessage(Destination destination, String message) {
jmsTemplate.convertAndSend(destination, message);
}
//发送消息,destination是发送到的队列,message是待发送的消息
@Override
public void sendMessage(final String message) {
jmsTemplate.convertAndSend( message);
}
//=======发布订阅相关代码=========
@Autowired
private Topic topic;
@Override
public void publish(String msg) {
this.jmsTemplate.convertAndSend(this.topic, msg);
}
}
package net.xdclass.base_project.jms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class OrderConsumer {
@JmsListener(destination="order.queue")
public void receiveQueue(String text){
System.out.println("OrderConsumer收到的报文为:"+text);
}
}
package net.xdclass.base_project.jms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class TopicSub {
@JmsListener(destination="video.topic", containerFactory="jmsListenerContainerTopic")
public void receive1(String text){
System.out.println("video.topic 消费者:receive1="+text);
}
@JmsListener(destination="video.topic", containerFactory="jmsListenerContainerTopic")
public void receive2(String text){
System.out.println("video.topic 消费者:receive2="+text);
}
@JmsListener(destination="video.topic", containerFactory="jmsListenerContainerTopic")
public void receive3(String text){
System.out.println("video.topic 消费者:receive3="+text);
}
}
package net.xdclass.base_project.jms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class CommonConsumer {
@JmsListener(destination="common.queue")
public void receiveQueue(String text){
System.out.println("CommonConsumer收到的报文为:"+text);
}
}
package net.xdclass.base_project.controller;
import javax.jms.Destination;
import net.xdclass.base_project.domain.JsonData;
import net.xdclass.base_project.service.ProducerService;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 功能描述:模拟微信支付回调
*
* <p> 创建时间:May 3, 2018 9:53:14 PM </p>
*
*@作者 小D课堂 小D
*/
@RestController
@RequestMapping("/api/v1")
public class OrderController {
@Autowired
private ProducerService producerService;
/**
* 功能描述:微信支付回调接口
* @param msg 支付信息
* @return
*/
@GetMapping("order")
public Object order(String msg){
Destination destination = new ActiveMQQueue("order.queue");
producerService.sendMessage(destination, msg);
return JsonData.buildSuccess();
}
@GetMapping("common")
public Object common(String msg){
producerService.sendMessage(msg);
return JsonData.buildSuccess();
}
@GetMapping("topic")
public Object topic(String msg) {
producerService.publish(msg);
return JsonData.buildSuccess();
}
//
// /**
// * 功能描述:微信支付回调接口
// * @param msg 支付信息
// * @return
// */
// @GetMapping("comment")
// public Object comment(String msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, UnsupportedEncodingException{
//
// /**
// * 创建一个消息实例,包含 topic、tag 和 消息体
// */
// Message message = new Message("commentTopic","add", msg.getBytes(RemotingHelper.DEFAULT_CHARSET));
//
// //同步的方式,会有返回结果,发送的是普通消息
// SendResult result = msgProducer.getProducer().send(message);
//
// System.out.println("发送响应:MsgId:" + result.getMsgId() + ",发送状态:" + result.getSendStatus());
//
// return JsonData.buildSuccess();
// }
//
//
//
}
RocketMQ
1、Apache RocketMQ作为阿里开源的一款高性能、高吞吐量的分布式消息中间件
2、特点
1)在高压下1毫秒内响应延迟超过99.6%。
2)适合金融类业务,高可用性跟踪和审计功能。
3)支持发布订阅模型,和点对点
4)支持拉pull和推push两种消息模式
5)单一队列百万消息
6)支持单master节点,多master节点,多master多slave节点
...
3、概念
Producer:消息生产者
Producer Group:消息生产者组,发送同类消息的一个消息生产组
Consumer:消费者
Consumer Group:消费同个消息的多个实例
Tag:标签,子主题(二级分类),用于区分同一个主题下的不同业务的消息
Topic:主题
Message:消息
Broker:MQ程序,接收生产的消息,提供给消费者消费的程序
Name Server:给生产和消费者提供路由信息,提供轻量级的服务发现和路由
3、官网地址:http://rocketmq.apache.org/
学习资源:
1)http://jm.taobao.org/2017/01/12/rocketmq-quick-start-in-10-minutes/
2)https://www.jianshu.com/p/453c6e7ff81c
RocketMQ4.x本地快速部署
简介:RocketMQ4.x本地快速部署
1、安装前提条件(推荐)
64bit OS, Linux/Unix/Mac
64bit JDK 1.8+;
2、快速开始 http://rocketmq.apache.org/docs/quick-start/
下载安装包:https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.2.0/rocketmq-all-4.2.0-bin-release.zip
路径:/Users/jack/Desktop/person/springboot/资料/第13章/第5课/rocketmq-all-4.2.0-bin-release/bin
3、解压压缩包
1)进入bin目录,启动namesrv
nohup sh mqnamesrv &
2) 查看日志 tail -f nohup.out
结尾:The Name Server boot success. serializeType=JSON 表示启动成功
3、启动broker
nohup sh mqbroker -n 127.0.0.1:9876 &
4)、关闭nameserver broker执行的命令
sh mqshutdown namesrv
sh mqshutdown broker
7、RoekerMQ4.x可视化控制台讲解
简介:RoekerMQ4.x可视化控制台讲解
1、下载 https://github.com/apache/rocketmq-externals
2、编译打包 mvn clean package -Dmaven.test.skip=true
3、target目录 通过java -jar的方式运行
4、无法连接获取broker信息
1)修改配置文件,名称路由地址为 namesrvAddr,例如我本机为
2)src/main/resources/application.properties
rocketmq.config.namesrvAddr=192.168.0.101:9876
5、默认端口 localhost:8080
6、注意:
在阿里云,腾讯云或者虚拟机,记得检查端口号和防火墙是否启动
Springboot2.x整合RocketMQ4.x实战上集
简介:Springboot2.x整合RocketMQ4.x实战,加入相关依赖,开发生产者代码
启动nameser和broker
1、加入相关依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>${rocketmq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-common</artifactId>
<version>${rocketmq.version}</version>
</dependency>
2、application.properties加入配置文件
# 消费者的组名
apache.rocketmq.consumer.PushConsumer=orderConsumer
# 生产者的组名
apache.rocketmq.producer.producerGroup=Producer
# NameServer地址
apache.rocketmq.namesrvAddr=127.0.0.1:9876
3、开发MsgProducer
/**
* 生产者的组名
*/
@Value("${apache.rocketmq.producer.producerGroup}")
private String producerGroup;
/**
* NameServer 地址
*/
@Value("${apache.rocketmq.namesrvAddr}")
private String namesrvAddr;
private DefaultMQProducer producer ;
<br />
public DefaultMQProducer getProducer(){<br />
return this.producer;<br />
}<br />
<br />
[@PostConstruct ](/PostConstruct ) <br />
public void defaultMQProducer() {<br />
//生产者的组名<br />
producer = new DefaultMQProducer(producerGroup);<br />
//指定NameServer地址,多个地址以 ; 隔开<br />
//如 producer.setNamesrvAddr("192.168.100.141:9876;192.168.100.142:9876;192.168.100.149:9876");<br />
producer.setNamesrvAddr(namesrvAddr);<br />
producer.setVipChannelEnabled(false);<br />
<br />
try {
/**
- Producer对象在使用之前必须要调用start初始化,只能初始化一次
*/
producer.start();
} catch (Exception e) {
e.printStackTrace();
}
// producer.shutdown(); 一般在应用上下文,关闭的时候进行关闭,用上下文监听器
}
9、Springboot2.x整合RocketMQ4.x实战下集
简介:Springboot2.x整合RocketMQ4.x实战,开发消费者代码,常见问题处理
1、创建消费者
问题:
1、Caused by: org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to <172.17.42.1:10911> failed
2、com.alibaba.rocketmq.client.exception.MQClientException: Send [1] times, still failed, cost [1647]ms, Topic: TopicTest1, BrokersSent: [broker-a, null, null]
3、org.apache.rocketmq.client.exception.MQClientException: Send [3] times, still failed, cost [497]ms, Topic: TopicTest, BrokersSent: [chenyaowudeMacBook-Air.local, chenyaowudeMacBook-Air.local, chenyaowudeMacBook-Air.local]
解决:多网卡问题处理
1、设置producer: producer.setVipChannelEnabled(false);
2、编辑ROCKETMQ 配置文件:broker.conf(下列ip为自己的ip)
namesrvAddr = 192.168.0.101:9876
brokerIP1 = 192.168.0.101
4、DESC: service not available now, maybe disk full, CL:
解决:修改启动脚本runbroker.sh,在里面增加一句话即可:
JAVA_OPT="${JAVA_OPT} -Drocketmq.broker.diskSpaceWarningLevelRatio=0.98"
(磁盘保护的百分比设置成98%,只有磁盘空间使用率达到98%时才拒绝接收producer消息)
常见问题处理:
https://blog.csdn.net/sqzhao/article/details/54834761
https://blog.csdn.net/mayifan0/article/details/67633729
https://blog.csdn.net/a906423355/article/details/78192828
#通过触发器,去控制什么时候进行热加载部署新的文件
spring.devtools.restart.trigger-file=trigger.txt
#自定义启动banner文件的路径
spring.banner.location=banner.txt
# 消费者的组名
apache.rocketmq.consumer.PushConsumer=orderConsumer
# 生产者的组名
apache.rocketmq.producer.producerGroup=Producer
# NameServer地址
apache.rocketmq.namesrvAddr=118.24.175.34:9876
package net.xdclass.base_project.jms;
import javax.annotation.PostConstruct;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MsgProducer {
/**
* 生产者的组名
*/
@Value("${apache.rocketmq.producer.producerGroup}")
private String producerGroup;
/**
* NameServer 地址
*/
@Value("${apache.rocketmq.namesrvAddr}")
private String namesrvAddr;
private DefaultMQProducer producer ;
public DefaultMQProducer getProducer(){
return this.producer;
}
@PostConstruct
public void init() {
//生产者的组名
producer = new DefaultMQProducer(producerGroup);
//指定NameServer地址,多个地址以 ; 隔开
//如 producer.setNamesrvAddr("192.168.100.141:9876;192.168.100.142:9876;192.168.100.149:9876");
producer.setNamesrvAddr(namesrvAddr);
producer.setVipChannelEnabled(false);
try {
/**
* Producer对象在使用之前必须要调用start初始化,只能初始化一次
*/
producer.start();
} catch (Exception e) {
e.printStackTrace();
}
// producer.shutdown(); 一般在应用上下文,关闭的时候进行关闭,用上下文监听器
}
}
package net.xdclass.base_project.jms;
import javax.annotation.PostConstruct;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MsgConsumer {
/**
* 消费者的组名
*/
@Value("${apache.rocketmq.consumer.PushConsumer}")
private String consumerGroup;
/**
* NameServer 地址
*/
@Value("${apache.rocketmq.namesrvAddr}")
private String namesrvAddr;
@PostConstruct
public void defaultMQPushConsumer() {
//消费者的组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroup);
//指定NameServer地址,多个地址以 ; 隔开
consumer.setNamesrvAddr(namesrvAddr);
try {
//设置consumer所订阅的Topic和Tag,*代表全部的Tag
consumer.subscribe("testTopic", "*");
//CONSUME_FROM_LAST_OFFSET 默认策略,从该队列最尾开始消费,跳过历史消息
//CONSUME_FROM_FIRST_OFFSET 从队列最开始开始消费,即历史消息(还储存在broker的)全部消费一遍
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//MessageListenerOrderly 这个是有序的
//MessageListenerConcurrently 这个是无序的,并行的方式处理,效率高很多
consumer.registerMessageListener((MessageListenerConcurrently) (list, context) -> {
try {
for (MessageExt messageExt : list) {
System.out.println("messageExt: " + messageExt);//输出消息内容
String messageBody = new String(messageExt.getBody(), RemotingHelper.DEFAULT_CHARSET);
System.out.println("消费响应:msgId : " + messageExt.getMsgId() + ", msgBody : " + messageBody);//输出消息内容
}
} catch (Exception e) {
e.printStackTrace();
return ConsumeConcurrentlyStatus.RECONSUME_LATER; //稍后再试
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //消费成功
});
consumer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package net.xdclass.base_project.controller;
import java.io.UnsupportedEncodingException;
import net.xdclass.base_project.domain.JsonData;
import net.xdclass.base_project.jms.MsgProducer;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 功能描述:模拟微信支付回调
*
* <p> 创建时间:May 3, 2018 9:53:14 PM </p>
*
*@作者 小D课堂 小D
*/
@RestController
@RequestMapping("/api/v1")
public class OrderController {
@Autowired
private MsgProducer msgProducer;
/**
* 功能描述:微信支付回调接口
* @param msg 支付信息
* @param tag 消息二级分类
* @return
*/
@GetMapping("order")
public Object order(String msg, String tag) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, UnsupportedEncodingException{
/**
* 创建一个消息实例,包含 topic、tag 和 消息体
*/
Message message = new Message("testTopic",tag, msg.getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult result = msgProducer.getProducer().send(message);
System.out.println("发送响应:MsgId:" + result.getMsgId() + ",发送状态:" + result.getSendStatus());
return JsonData.buildSuccess();
}
//
// /**
// * 功能描述:微信支付回调接口
// * @param msg 支付信息
// * @return
// */
// @GetMapping("comment")
// public Object comment(String msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, UnsupportedEncodingException{
//
// /**
// * 创建一个消息实例,包含 topic、tag 和 消息体
// */
// Message message = new Message("commentTopic","add", msg.getBytes(RemotingHelper.DEFAULT_CHARSET));
//
// //同步的方式,会有返回结果,发送的是普通消息
// SendResult result = msgProducer.getProducer().send(message);
//
// System.out.println("发送响应:MsgId:" + result.getMsgId() + ",发送状态:" + result.getSendStatus());
//
// return JsonData.buildSuccess();
// }
//
//
//
}
多环境
1、SpringBoot多环境配置介绍和项目实战(核心知识)
简介:SpringBoot介绍多环境配置和使用场景
1、不同环境使用不同配置
例如数据库配置,在开发的时候,我们一般用开发数据库,而在生产环境的时候,我们是用正式的数据
2、配置文件存放路径
classpath根目录的“/config”包下
classpath的根目录下
3、spring boot允许通过命名约定按照一定的格式(application-{profile}.properties)来定义多个配置文件
//application-dev.properties
test.url=dev.com
//application-test.properties
test.url=test.com
//application.properties
#springboot多环境配置======begin
test.url=local
#指定哪个profile
spring.profiles.active=dev
#springboot多环境配置======end
package net.xdclass.base_project.controller;
import net.xdclass.base_project.domain.JsonData;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*@作者 小D课堂 小D
*/
@RestController
@RequestMapping("/api/v1")
public class OrderController {
@Value("${test.url}")
private String domain;
/**
* 功能描述:微信支付回调接口
* @param msg 支付信息
* @return
*/
@GetMapping("order")
public Object order(String msg){
return JsonData.buildSuccess(domain);
}
}
响应式编程
SprinBoot2.x响应式编程简介
简介:讲解什么是reactive响应式编程和使用的好处
1、基础理解:
依赖于事件,事件驱动(Event-driven)
一系列事件称为“流”
异步
非阻塞
观察者模式
网上的一个例子:
int b= 2;
int c=3
int a = b+c //命令式编程后续b和c变化,都不影响a
b=5;
int b= 2;
int c= 3
int a = b+c //响应式编程中,a的变化,会和b、c的变化而变化(事件驱动)
b=5;
2、官网:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-webflux
SpingBoot2底层是用spring5,开始支持响应式编程,Spring又是基于Reactor试下响应式。
学习资料
1、reactive-streams学习资料:http://www.reactive-streams.org/
2、web-flux相关资料:https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#spring-webflux
SpringBoot2.x响应式编程webflux介绍
简介:讲解SpringBoot2.x响应式编程介绍 Mono、Flux对象和优缺点
1、Spring WebFlux是Spring Framework 5.0中引入的新的反应式Web框架
与Spring MVC不同,它不需要Servlet API,完全异步和非阻塞,并 通过Reactor项目实现Reactive Streams规范。
RxJava
2、Flux和Mono User List<User>
1)简单业务而言:和其他普通对象差别不大,复杂请求业务,就可以提升性能
2)通俗理解:
Mono 表示的是包含 0 或者 1 个元素的异步序列
mono->单一对象 User redis->用户ID-》唯一的用户Mono<User>
Flux 表示的是包含 0 到 N 个元素的异步序列
flux->数组列表对象 List<User> redis->男性用户->Flux<User>
Flux 和 Mono 之间可以进行转换
3、Spring WebFlux有两种风格:基于功能和基于注解的。基于注解非常接近Spring MVC模型,如以下示例所示:
第一种:
@RestController
@RequestMapping(“/ users”)
public class MyRestController {
@GetMapping(“/ {user}”)
public Mono <User> getUser( @PathVariable Long user){
// ...
}
@GetMapping(“/ {user} / customers”)
public Flux <Customer> getUserCustomers( @PathVariable Long user){
// ...
}
@DeleteMapping(“/ {user}”)
public Mono <User> deleteUser( @PathVariable Long user){
// ...
}
}
第二种: 路由配置与请求的实际处理分开
@Configuration
public class RoutingConfiguration {
@Bean
public RouterFunction <ServerResponse> monoRouterFunction(UserHandler userHandler){
return route(GET( “/ {user}”).and(accept(APPLICATION_JSON)),userHandler :: getUser)
.andRoute(GET(“/ {user} / customers”).and(accept(APPLICATION_JSON)),userHandler :: getUserCustomers)
.andRoute(DELETE(“/ {user}”).and(accept(APPLICATION_JSON)),userHandler :: deleteUser);
}
}
@Component
public class UserHandler {
公共 Mono <ServerResponse> getUser(ServerRequest请求){
// ...
}
public Mono <ServerResponse> getUserCustomers(ServerRequest request){
// ...
}
公共 Mono <ServerResponse> deleteUser(ServerRequest请求){
// ...
}
}
4、Spring WebFlux应用程序不严格依赖于Servlet API,因此它们不能作为war文件部署,也不能使用src/main/webapp目录
5、可以整合多个模板引擎
除了REST Web服务外,您还可以使用Spring WebFlux提供动态HTML内容。Spring WebFlux支持各种模板技术,包括Thymeleaf,FreeMarker
SpringBoot2.x webflux实战
简介:webflux响应式编程实战
1、WebFlux中,请求和响应不再是WebMVC中的ServletRequest和ServletResponse,而是ServerRequest和ServerResponse
2、加入依赖,如果同时存在spring-boot-starter-web,则会优先用spring-boot-starter-web
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
测试
localhost:8080/api/v1/user/test
3、启动方式默认是Netty,8080端口
4、参考:https://spring.io/blog/2016/04/19/understanding-reactive-types
package net.xdclass.base_project.service;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import net.xdclass.base_project.domain.User;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class UserService {
private static final Map<String, User> dataMap = new HashMap<>();
static{
dataMap.put("1", new User("1", "小X老师"));
dataMap.put("2", new User("2", "小D老师"));
dataMap.put("3", new User("3", "小C老师"));
dataMap.put("4", new User("4", "小L老师"));
dataMap.put("5", new User("5", "小A老师"));
dataMap.put("6", new User("6", "小S老师"));
dataMap.put("7", new User("7", "小S老师"));
}
/**
* 功能描述:返回用户列表
* @return
*/
public Flux<User> list(){
Collection<User> list = UserService.dataMap.values();
return Flux.fromIterable(list);
}
/**
* 功能描述:根据id查找用户
* @param id
* @return
*/
public Mono<User> getById(final String id){
return Mono.justOrEmpty(UserService.dataMap.get(id));
}
/**
* 功能描述:根据id删除用户
* @param id
* @return
*/
public Mono<User> del(final String id){
return Mono.justOrEmpty(UserService.dataMap.remove(id));
}
}
package net.xdclass.base_project.controller;
import java.time.Duration;
import net.xdclass.base_project.domain.User;
import net.xdclass.base_project.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
*@作者 小D课堂 小D
*/
@RestController
@RequestMapping("/api/v1/user")
public class UserController {
//@Autowired
//private UserService userService;
private final UserService userService;
public UserController(final UserService userService) {
this.userService = userService;
}
@GetMapping("/test")
public Mono<String> test(){
return Mono.just("hello 小D课堂");
}
/**
* 功能描述:根据id找用户
* @param id
* @return
*/
@GetMapping("find")
public Mono<User> findByid(final String id){
return userService.getById(id);
}
/**
* 功能描述:删除用户
* @param id
* @return
*/
@GetMapping("del")
public Mono<User> del(final String id){
return userService.del(id);
}
/**
* 功能描述:列表
* @return
*/
@GetMapping(value="list",produces=MediaType.APPLICATION_STREAM_JSON_VALUE)
public Flux<User> list(){
return userService.list().delayElements(Duration.ofSeconds(2));
}
}
package base_project.base;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
//@RunWith(SpringRunner.class) //底层用junit SpringJUnit4ClassRunner
//@SpringBootTest(classes={XdclassApplication.class})//启动整个springboot工程
public class WebClientTest {
@Test
public void testBase(){
Mono<String> bodyMono = WebClient.create().get()
.uri("http://localhost:8080/api/v1/user/find?id=1")
.accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToMono(String.class);
System.out.println(bodyMono.block());
}
@Test
public void testBase2(){
Mono<String> bodyMono = WebClient.create().get()
.uri("http://localhost:8080/api/v1/user/find?id={id}",2)
.accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToMono(String.class);
System.out.println(bodyMono.block());
}
}
推送
服务端推送常用技术介绍
推荐使用websocket,sse只能服务器往浏览器推
1、客户端轮询:ajax定时拉取
2、服务端主动推送:WebSocket
全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议
更加复杂一些,适用于需要进行复杂双向数据通讯的场景
3、服务端主动推送:SSE (Server Send Event)
html5新标准,用来从服务端实时推送数据到浏览器端,
直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议
简单的服务器数据推送的场景,使用服务器推送事件
学习资料:http://www.w3school.com.cn/html5/html_5_serversentevents.asp
package net.xdclass.base_project.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/sse")
public class SSEController {
@RequestMapping(value = "/get_data", produces = "text/event-stream;charset=UTF-8")
public String push() {
try {
Thread.sleep(1000);
//第三方数据源调用
} catch (InterruptedException e) {
e.printStackTrace();
}
return "data:xdclass 行情" + Math.random() + "\n\n";
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
//需要判断浏览器支不支持,可以去w3c进行查看
var source = new EventSource('sse/get_data');
source.onmessage = function (event) {
console.info(event.data);
document.getElementById('result').innerText = event.data
};
</script>
</head>
<body>
模拟股票行情
<div>xdclass test</div>
<div id="result"></div>
</body>
</html>
部署
发布
阿里云Linux服务器部署JDK8实战
简介:在阿里云服务器上安装JDK8和配置环境变量
lnux下使用wget下载jdk8:
进到目录/usr/local/software
配置环境变量
vim /etc/profile
加入
export JAVA_HOME=/usr/local/software/jdk8
export PATH=$PATH:$JAVA_HOME/bin
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export JAVA_HOME PATH CLASSPATH
使用 source /etc/profile 让配置立刻生效
3、阿里云服务器SpringBoot2.x生产环境部署实战
简介:讲解SpringBoot生产环境部署和常见注意事项
1、去除相关生产环境没用的jar
比如热部署dev-tool
2、本地maven打包成jar包
mvn clean package -Dmaven.test.skip=true 跳过测试
3、服务器安装jdk,上传Jar包
上传工具:
windows:
winscp
securtyCRT
mac:
filezilla
ssh root@120.79.160.143
访问路径 http://120.79.160.143:8080/api/v1/user/find
java -jar xxxx.jar
守护进程、系统服务、shell脚本
https://blog.csdn.net/weixin_41574643/article/details/79716052
nohup java -jar shareniu.jar >temp.txt &
打包指定配置文件
1、使用maven的profiles
2、使用springboot的profile=active
访问不了
1、阿里云防火墙是否开启,可以选择关闭,关闭是不安全的,可以选择开放端口
2、阿里云的安全访问组,开启对应的端口,如果应用是以80端口启动,则默认可以访问
4、成熟的互联网公司应该有的架构
本地提交生产代码->gitlab仓库->Jenkins自动化构建->运维或者开发人员发布
监控
SpringBoot2.x监控Actuator实战上集
简介:讲解SpringBoot使用actuator监控配置和使用
可用性:100%,99.9%
1、介绍什么是actuator
官方介绍:
Spring Boot包含许多附加功能,可帮助您在将应用程序投入生产时监视和管理应用程序。 可以选择使用HTTP端点或JMX来管理和监控您的应用程序,自动应用于审计,健康和指标收集;
一句话:springboot提供用于监控和管理生产环境的模块
官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#production-ready
2、加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
3、加入上述依赖后,访问几个url
/actuator/health
/actuator/info
/actuator
SpringBoot2.x监控Actuator实战下集及生产环境建议(核心知识)
简介:SpringBoot2.x监控Actuator实战下集及生产环境建议,SpringBoot新旧版本区别
注意点: 网上的资料大多数没有讲到访问的前缀
端点基础路径由 / 调整到 /actuator
如:/info调整为/actuator/info
/actuator/xxx
1、只能访问几个url
1)需要在配置文件中加入下列配置
management.endpoints.web.exposure.include=*
2)官网说明:https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security-actuator
原因:
出于安全考虑,除/ health和/ info之外的所有执行器默认都是禁用的。 management.endpoints.web.exposure.include属性可用于启用执行器
2、建议
在设置management.endpoints.web.exposure.include之前,请确保暴露的执行器不包含敏感信息和/
或通过将其放置在防火墙进行控制,不对外进行使用
禁用的端点将从应用程序上下文中完全删除。如果您只想更改端点所暴露的技术,请改用 include和exclude属性 。
例子:
开启全部:management.endpoints.web.exposure.include=*
开启某个:management.endpoints.web.exposure.include=metrics
关闭某个:management.endpoints.web.exposure.exclude=metrics
或者用springadmin进行管理
相关资料:https://www.cnblogs.com/ityouknow/p/8440455.html
或者用自己编写脚本监控
CPU、内存、磁盘、nginx的http响应状态码200,404,5xx
3、介绍常用的几个
/health 查看应用健康指标
/actuator/metrics 查看应用基本指标列表
/actuator/metrics/{name} 通过上述列表,查看具体 查看具体指标
/actuator/env 显示来自Spring的 ConfigurableEnvironment的属性 ,不能暴露
#可以自动识别
#spring.datasource.driver-class-name =com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://118.24.175.34:3306/java_test?useUnicode=true&characterEncoding=utf-8
spring.datasource.username =root
spring.datasource.password =p@ssw0rd
#使用阿里巴巴druid数据源,默认使用自带的
spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#开启监控端点
management.endpoints.web.exposure.include=*
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.xdclass</groupId>
<artifactId>base_project</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<properties>
</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-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- 引入starter-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- MySQL的JDBC驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 引入第三方数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!-- springboot监控依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>