目前最流行的框架莫过于spring boot和spring cloud了,相信大家都对这两个多多少少都有了解。
但是在运用过程中难免有些地方会遇到问题,以下简单整理一下我再次学习spring boot的过程中以前没有注意到的或者容易被人忽视的知识点。相信你看了也会有种恍然大悟的感觉。
-------------------------------持续更新----------------------------------------
1、application.properties配置文件里面中文的处理
大家都知道对于该配置文件中的属性使用很简单,直接@Value注入即可,但是如果属性值为中文时程序中注入得到的会是乱码,这里就告诉大家怎么处理。
book.author=罗贯中
book.name=三国演义
book.pinyin=sanguoyanyi
@Value(value = "${book.author}") private String bookAuthor;
只需要在配置文件中添加以下配置即可。
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
spring.messages.encoding=UTF-8
然后 在IntelliJ IDEA中依次点击File -> Settings -> Editor -> File Encodings
将Properties Files (*.properties)下的Default encoding for properties files设置为UTF-8,将Transparent native-to-ascii conversion前的勾选上。eclipse的话Window—>Preferences—>General—>Content Types—>Text—>Java Properties File,将Default encoding中的ISO-8859-1更改为UTF-8后,点击update即可。
2、日志输出
默认情况下Spring Boot使用Logback作为日志框架,当然如果有需要我们可以手动配置日志级别以及日志输出位置,只需要在application.properties中添加如下代码:
logging.file=/home/sang/workspace/log.log
logging.level.org.springframework.web=debug
3、springMVC相关配置
虽然Spring Boot默认的配置很多情况都可以满足我们的项目需求,可是有的时候我们可能还是会需要更加灵活的SpringMVC配置,这个时候我们只需要自定义类继承自WebMvcConfigurerAdapter,然后使用@Configuration和@EnableWebMvc注解,这样我们会完全屏蔽掉Spring Boot的默认配置,但是正常情况下我们可能只是希望在Spring Boot已有默认配置的基础上再添加一些配置即Spring Boot提供的默认配置和我自定义的配置并存的情况,这个也简单,只需要去掉@EnableWebMvc注解就行了.
@Configuration
//@EnableWebMvc//无需使用该注解,否则会覆盖掉SpringBoot的默认配置值
public class WebMVCConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("/hello");
}}
4、websocket的理解
WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算,而且这种太占用资源)
WebSocket为浏览器和服务器之间提供了双工异步通信功能,也就是说我们可以利用浏览器给服务器发送消息,服务器也可以给浏览器发送消息。而http只能是浏览器发起请求。
配置WebSocket:
/**
*
* 1.@EnableWebSocketMessageBroker注解表示开启使用STOMP协议来传输基于代理的消息,Broker就是代理的意思。
* 2.registerStompEndpoints方法表示注册STOMP协议的节点,并指定映射的URL。
* 3.stompEndpointRegistry.addEndpoint("/endpointSang").withSockJS();这一行代码用来注册STOMP协议节点,同时指定使用SockJS协议。
* 4.configureMessageBroker方法用来配置消息代理,由于我们是实现推送功能,这里的消息代理是/topic
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/ws/endpointChat").withSockJS();
}@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue","/topic");
}
}
/**
* WebSocket 消息处理类
*
*
* 首先@Controller注解不必多言,
* 方法上添加的@MessageMapping注解和我们之前使用的@RequestMapping类似。
* 第二个方法是广播式* @SendTo注解表示当服务器有消息需要推送的时候,会对订阅了@SendTo中路径的浏览器发送消息。
* 第一个方法是点对点聊天
* 1.SimpMessagingTemplate这个类主要是实现向浏览器发送消息的功能。
* 2.在Spring MVC中,可以直接在参数中获取Principal,Principal中包含有当前用户的用户名。
* 3.convertAndSendToUser方法是向用户发送一条消息,第一个参数是目标用户用户名,第二个参数是浏览器中订阅消息的地址,第三个参数是消息本身。
*
*/
@Controller
public class WsController {
@Autowired
SimpMessagingTemplate messagingTemplate;@MessageMapping("/ws/chat")
public void handleChat(Principal principal, String msg) {
String destUser = msg.substring(msg.lastIndexOf(";") + 1, msg.length());
String message = msg.substring(0, msg.lastIndexOf(";"));
messagingTemplate.convertAndSendToUser(destUser, "/queue/chat", new ChatResp(message, principal.getName()));
}@MessageMapping("/ws/nf")
@SendTo("/topic/nf")
public String handleNF() {
return "系统消息";
}
}
前端页面需要:STOMP协议的客户端脚本stomp.js、SockJS的客户端脚本sock.js以及jQuery 来实现。具体前端代码可以参考一个简单示例:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>广播式WebSocket</title>
<script th:src="@{js/sockjs.min.js}"></script>
<script th:src="@{js/stomp.js}"></script>
<script th:src="@{js/jquery-3.1.1.js}"></script>
</head>
<body οnlοad="disconnect()">
<noscript><h2 style="color: #e80b0a;">Sorry,浏览器不支持WebSocket</h2></noscript>
<div>
<div>
<button id="connect" οnclick="connect();">连接</button>
<button id="disconnect" disabled="disabled" οnclick="disconnect();">断开连接</button>
</div><div id="conversationDiv">
<label>输入你的名字</label><input type="text" id="name"/>
<button id="sendName" οnclick="sendName();">发送</button>
<p id="response"></p>
</div>
</div>
<script type="text/javascript">
var stompClient = null;
function setConnected(connected) {
document.getElementById("connect").disabled = connected;
document.getElementById("disconnect").disabled = !connected;
document.getElementById("conversationDiv").style.visibility = connected ? 'visible' : 'hidden';
// $("#connect").disabled = connected;
// $("#disconnect").disabled = !connected;
$("#response").html();
}
function connect() {
var socket = new SockJS('/endpointSang');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected:' + frame);
stompClient.subscribe('/topic/getResponse', function (response) {
showResponse(JSON.parse(response.body).responseMessage);
})
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log('Disconnected');
}
function sendName() {
var name = $('#name').val();
console.log('name:' + name);
stompClient.send("/welcome", {}, JSON.stringify({'name': name}));
}
function showResponse(message) {
$("#response").html(message);
}
</script>
</body>
</html>我们的页面上先有两个按钮,一个是连接,一个是断开连接,两个按钮分别对应不同的点击事件,在这两个按钮下方有一个输入框,就是我们要发送的内容,然后还有一个发送按钮,发送按钮对应了一个发送消息的点击事件。这是整个页面的元素,很简单,我们这里重点来看一下js逻辑代码。connect方法是当我点击连接按钮的时候执行的,var socket = new SockJS('/endpointSang');表示连接的SockJS的endpoint名称为/endpointSang,stompClient = Stomp.over(socket);表示使用STOMP来创建WebSocket客户端。然后调用stompClient中的connect方法来连接服务端,连接成功之后调用setConnected方法,该隐藏的隐藏,该显示的显示。然后再通过调用stompClient中的subscribe方法来订阅/topic/getResponse发送来的消息,也就是我们在Controller中的say方法上添加的@SendTo注解的参数。stompClient中的send方法表示发送一条消息到服务端,其他的都是常规的js用法。
热部署配置(pom.xml):
<!-- 热部署 -->
<!-- devtools可以实现页面热部署(即页面修改后会立即生效,
这个可以直接在application.properties文件中配置spring.thymeleaf.cache=false来实现) -->
<!-- 实现类文件热部署(类文件修改后不会立即生效),实现对属性文件的热部署。 -->
<!-- 即devtools会监听classpath下的文件变动,并且会立即重启应用(发生在保存时机),
注意:因为其采用的虚拟机机制,该项重启是很快的 -->
<!-- (1)base classloader (Base类加载器):加载不改变的Class,例如:第三方提供的jar包。 -->
<!-- (2)restart classloader(Restart类加载器):加载正在开发的Class。 -->
<!-- 为什么重启很快,因为重启的时候只是加载了在开发的Class,没有重新加载第三方的jar包。 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!-- optional=true, 依赖不会传递, 该项目依赖devtools;之后依赖boot项目的项目如果想要使用devtools, 需要重新引入 -->
<optional>true</optional>
</dependency>
打包跳过测试类配置(pom.xml):
<build>
<finalName>address-statistics</finalName>
<plugins>
<!-- 安装时不运行测试类 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
maven打包,父级packging类型为pom,子级为jar(不指定默认为jar),子级非可执行jar包时报错(找不到main函数)解决如下:
此时如果parent中pom添加的build插件,没有使用pluginManagement标签,在打包common模块时就会提示找不到main入口
<!-- 添加spring-boot的maven插件 -->
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<target>${java.version}</target>
<source>${java.version}</source>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
restful服务时间类型格式化返回:
由于spring boot 首选和默认的是Jackson,而且Jackson是spring-boot-starter-json依赖中的一部分,spring-boot-starter-web中包含spring-boot-starter-json。便引出以下配置,在返回json的时候省去了对时间类型数据的处理,精简代码。
1、全局配置(properties配置文件配置) :
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
2、时间类型字段添加注解
/**
* 创建时间
*/
@ApiModelProperty(value = "创建时间")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
@TableField("CREATETIME")
private Date createtime;
3、@RequestParam、@RequestBody和@ModelAttribute区别
@RequestParam(org.springframework.web.bind.annotation.RequestParam)用于将指定的请求参数赋值给方法中的形参,post/get均可用
@RequestBody注解处理HttpEntity传递过来的数据,GET请求中,因为没有HttpEntity,不能用于get请求。可以接收json格式的数据,并将其转换成对应的数据类型
@ModelAttribute注解类型将参数绑定到Model对象
当前台界面使用GET或POST方式提交数据时,数据编码格式由请求头的ContentType指定。分为以下几种情况:
1. application/x-www-form-urlencoded,这种情况的数据@RequestParam、@ModelAttribute可以处理,@RequestBody也可以处理。
2. multipart/form-data,@RequestBody不能处理这种格式的数据。(form表单里面有文件上传时,必须要指定enctype属性值为multipart/form-data,意思是以二进制流的形式传输文件。)
3. application/json、application/xml等格式的数据,必须使用@RequestBody来处理
@PostConstruct 注释
用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。此方法必须在将类放入服务之前调用。支持依赖关系注入的所有类都必须支持此注释。即使类没有请求注入任何资源,用 PostConstruct 注释的方法也必须被调用。只有一个方法可以用此注释进行注释。应用 PostConstruct 注释的方法必须遵守以下所有标准:该方法不得有任何参数,除非是在 EJB 拦截器 (interceptor) 的情况下,根据 EJB 规范的定义,在这种情况下它将带有一个 InvocationContext 对象 ;该方法的返回类型必须为 void;该方法不得抛出已检查异常;应用 PostConstruct 的方法可以是 public、protected、package private 或 private;除了应用程序客户端之外,该方法不能是 static;该方法可以是 final;如果该方法抛出未检查异常,那么不得将类放入服务中,除非是能够处理异常并可从中恢复的 EJB。
1.@PostConstruct说明
被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,类似于Serclet的inti()方法。被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行(@Autowired加载之后)。
2.@PreConstruct说明
被@PreConstruct修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreConstruct修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前。
3、PostConstruct注解不可以有参数的,否则就会报错:java.lang.IllegalStateException: Lifecycle method annotation requires a no-arg method
@Mapper注解和 @MapperScan
1、@Mapper注解:
作用:在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类
添加位置:接口类上面@Mapper
public interface UserDAO {
//代码
}如果想要每个接口都要变成实现类,那么需要在每个接口类上加上@Mapper注解,比较麻烦,解决这个问题用@MapperScan
2、@MapperScan
作用:指定要变成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类
添加位置:是在Springboot启动类上面添加,@SpringBootApplication
@MapperScan("com.winter.dao")
public class SpringbootMybatisDemoApplication {public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisDemoApplication.class, args);
}
}添加@MapperScan(“com.winter.dao”)注解以后,com.winter.dao包下面的接口类,在编译之后都会生成相应的实现类
3、使用@MapperScan注解多个包
(实际用的时候根据自己的包路径进行修改)@SpringBootApplication
@MapperScan({"com.kfit.demo","com.kfit.user"})
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}4、 如果dao接口类没有在Spring Boot主程序可以扫描的包或者子包下面,可以使用如下方式进行配置:
(没验证过,不确定能否使用,或许需要根据自己定义的包名进行修改路径)@SpringBootApplication
@MapperScan({"com.kfit.*.mapper","org.kfit.*.mapper"})
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
spring boot打开actuator监控
引入依赖spring-boot-starter-actuator
----很坑
2.0之前 直接启动
在启动log日志中,可以看到springboot把一些默认的地址都映射好了,其中我们配置的actuator中health和info已经被映射上了,我们打开地址:http://localhost:8080/actuator/info 或者 http://localhost:8080/actuator/health2.0之后
management: #actuator
server:
port: 8081
endpoints:
web:
# base-path: / #默认是/actuator 前缀,可以在这里修改
exposure:
include: "*" #打开全部请求端点
# include: refresh,health,info,bus-refresh #打开部分
然后加注解@RefreshScope
基于注解开启定时任务
@Configuration //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling // 2.开启定时任务
public class SaticScheduleTask {
//3.添加定时任务
@Scheduled(cron = "0/5 * * * * ?")
//或直接指定时间间隔,例如:5秒
//@Scheduled(fixedRate=5000)
private void configureTasks() {
System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
}
}//@Component注解用于对那些比较中立的类进行注释;
//相对与在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释
@Component
@EnableScheduling // 1.开启定时任务
@EnableAsync // 2.开启多线程
public class MultithreadScheduleTask {@Async //很关键
@Scheduled(fixedDelay = 1000) //间隔1秒
public void first() throws InterruptedException {
System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
Thread.sleep(1000 * 10);
}@Async
@Scheduled(fixedDelay = 2000)
public void second() {
System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
System.out.println();
}
}
cron表达式
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
maven打包jar执行报错:没有主程序清单属性
pom打包方式选择jar,然后添加下面build插件
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
mysql时区处理
可以修改服务时区或者直接在jdbc连接数据库时设置时区
mysql> set global time_zone='+08:00';
mysql> set time_zone='+08:00';
mysql> flush privileges;
或者[mysqld]
default-time-zone = '+08:00'
或者&serverTimezone=GMT%2b8 相差16个时区
&serverTimezone=UTC 相差8个时区
spring boot配置jetCache
jetcache: statIntervalMinutes: 15 areaInCacheName: false hidePackages: com.cn.geostar.md.manage local: # 默认2小时本地缓存 default: type: caffeine keyConvertor: fastjson expireAfterWriteInMillis: 3600000 expireAfterAccessInMillis: 1800000 # 長時本地緩存,主要用于要求时效一般 longTime: type: caffeine keyConvertor: fastjson expireAfterWriteInMillis: 300000 expireAfterAccessInMillis: 180000 # 短時本地緩存,主要用于要求时效较高的配置 shortTime: type: caffeine keyConvertor: fastjson expireAfterWriteInMillis: 60000 expireAfterAccessInMillis: 40000 remote: # 默认2小时的远程缓存 default: type: redis expireAfterWriteInMillis: 43200000 keyConvertor: fastjson valueEncoder: kryo valueDecoder: kryo poolConfig: minIdle: 5 maxIdle: 20 maxTotal: 50 host: ${REDIS_HOST:172.17.0.104} port: ${REDIS_PORT:6400} password: redis-123456-redis database: 1 # 长时远程緩存,主要用于要求时效要求一般的集中式缓存 longTime: type: redis expireAfterWriteInMillis: 7200000 keyConvertor: fastjson valueEncoder: java valueDecoder: java poolConfig: minIdle: 5 maxIdle: 20 maxTotal: 50 host: ${REDIS_HOST:172.17.0.104} port: ${REDIS_PORT:6400} password: redis-123456-redis database: 1 # 短時远程緩存,主要用于要求时效较高的集中式缓存 shortTime: type: redis expireAfterWriteInMillis: 300000 keyConvertor: fastjson valueEncoder: kryo valueDecoder: kryo poolConfig: minIdle: 5 maxIdle: 20 maxTotal: 50 host: ${REDIS_HOST:172.17.0.104} port: ${REDIS_PORT:6400} password: redis-123456-redis database: 1
maven打包跳过单元测试
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>
true
</skip>
</configuration>
</plugin>
springboot 连接 postgresql 指定模式Schema
一般的连接方式,我们创建数据库之后,在public 的Schema(模式)下建表,这时使用连接方式
jdbc:postgresql://localhost:5432/postgresql 在这种连接方式下,默认连接使用的是postgresql数据库的public 模式
在业务场景中有时允许多个用户使用一个数据库并且不会互相干扰。这时需要在使用同一个数据库 新建其他模式进行连接。这时在springboot的数据源jdbc配置时注意。
postgresql-> 9.3 及以前的版本指定方式 spring.datasource.url=jdbc:postgresql://localhost:5432/postgresql?searchpath=newschema
postgresql-> 9.4 及以后的版本指定方式 spring.datasource.url=jdbc:postgresql://localhost:5432/postgresql?currentSchema=newschema
sql语句指定schema在表名前面加上schema.表名就行了