springboot学习笔记,没写的都是和springmvc一样,请参考spring mvc
springboot使用maven集成方式简化依赖:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.4.RELEASEversion>
<relativePath/>
parent>
以下是一个支持web的基本依赖:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.4.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
dependencies>
可以使用spring初始化器来进行项目初始化,加入需要的模块如web,activemq等
加入上边的依赖,然后写一个helloworld,直接run即可,访问http://localhost:8080/
即可得到结果
@SpringBootApplication
@RestController
public class DemoApplication {
@GetMapping("/")
public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
return String.format("Hello %s!", name);
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
jackson注解
jackson处理相关自动
指定字段不返回:@JsonIgnore
指定日期格式:@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
空字段不返回:@JsonInclude(Include.NON_NULL)
指定别名:@JsonProperty
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:
同个文件的加载顺序,静态资源文件
Spring Boot 默认会挨个从 META/resources > resources > static > public
里面找是否存在相应的资源,如果有则直接返回。
可以在application.properties
修改默认加载顺序:
spring.resources.static-locations = classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
建议:一般项目都会前后端分离,所以无需太过关注这些文件结构,一般js、html文件也不会在这放,在src/main/resources文件夹下存放java代码配置文件即可,springboot默认配置文件名为:application.properties
参考springmvc中的上传下载,这里只说springboot的配置
spring.servlet.multipart.enabled=true,是否支持 multipart 上传文件
spring.servlet.multipart.file-size-threshold=0,支持文件写入磁盘
spring.servlet.multipart.location=,上传文件的临时目录
spring.servlet.multipart.max-file-size=10MB,最大支持文件大小,这里单位必须是MB,-1为不限制大小(不用带单位)
spring.servlet.multipart.max-request-size=10MB,最大支持请求大小,这里单位必须是MB,-1为不限制大小(不用带单位),因为一个请求可以上传多个文件
spring.servlet.multipart.resolve-lazily=false,是否支持 multipart 上传文件时懒加载
如果只是用单文件上传max-file-size和max-request-size应该设置为一样大小,如果是多文件上传则max-request-size=n*max-file-size(n为文件个数)
可以使用以下方式在application.properties中指定文件上传和访问需要指定磁盘路径
指定文件路径,并将它加入到静态路径中
web.images-path=/Users/jack/Desktop
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/test/,file:${web.upload-path}
前端可以多写几个
<body>
<form enctype="multipart/form-data" method="post" action="/upload">
文件:<input type="file" name="head_img"/><input type="file" name="head_img"/>
姓名:<input type="text" name="name"/>
<input type="submit" value="上传"/>
form>
body>
后端需要用数组MultipartFile[] files
来接
jar包不需要指定打包方式,因为默认就是jar包
加入以下依赖即可,spring-boot-maven-plugin会自动扫描main方法,无需手动指定
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
指定打包形式为war包
<packaging>warpackaging>
需要继承SpringBootServletInitializer 并且重写configure方法加载@SpringBootApplication注解的类,不需要main方法,也不需要spring-boot-maven-plugin
@SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(DemoApplication.class);
}
}
springboot热部署需要依赖开发工具包,热部署出发时机是保存文件
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
application.properties配置文件可以指定排除某些文件进行热部署,也就是修改这些文件不会出发热部署
spring.devtools.restart.exclude=static/**,public/**
springboot默认/META-INF/maven, /META-INF/resources, /resources, /static, /public, or /templates
的修改不会进行热部署
application.properties和还可以进行设置热部署触发器,也就是只有修改这个特定的文件才会触发热部署
,trigger.txt放在resource文件夹下
spring.devtools.restart.trigger-file=trigger.txt
方式一
1、Controller上面配置
@PropertySource({“classpath:resource.properties”})
2、增加属性
@Value("${test.name}")
private String name;
方式二:实体类配置文件
步骤:
1、添加 @Component 注解;
2、使用 @PropertySource 注解指定配置文件位置;
3、必须 通过注入IOC对象Resource 进来 , 才能在类中使用获取的配置文件值。
@Configuration
@PropertySource(value="classpath:resource.properties")
public class ServerConstant {
@ConfigurationProperties(prefix="test")
方式@Configuration
@ConfigurationProperties(prefix="test")
//也可以不配置文件路径,默认从application.properties中加载属性
@PropertySource(value = "classpath:resource.properties")
public class Config {
// @Value("${a}")//这里不需要Value注解
private int a;
// @Value("${b}")
private int b;
@ConfigurationProperties注解可以忽略非法字符,不过默认不开启,可以使用ignoreInvalidFields=true开启
public @interface ConfigurationProperties {
@AliasFor("prefix")
String value() default "";
@AliasFor("value")
String prefix() default "";
boolean ignoreInvalidFields() default false;
boolean ignoreUnknownFields() default true;
}
依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
使用:
@RunWith(SpringRunner.class) //底层用junit SpringJUnit4ClassRunner
@SpringBootTest(classes={DemoApplication.class})//启动整个springboot工程
public class SpringBootTests { //一定要是public类
@Test
public void testNormalController() {
System.out.println("hello");
TestCase.assertEquals(1, 1);
}
}
关闭banner
spring.main.banner-mode=off
在resource文件加下建立banner.txt,默认banner路径就是这个,修改banner.txt的内容
banner字符生成网站
以下是使用Servlet3.0的注解进行配置
启动类里面增加 @ServletComponentScan
,进行扫描,@WebFilter
标记一个类为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;
@WebFilter(urlPatterns = "/*", 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");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
System.out.println("destroy loginFilter");
}
}
以下是使用Servlet3.0的注解进行配置
启动类里面增加 @ServletComponentScan
,进行扫描,@WebServlet
标记一个类为servlet
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "userServlet",urlPatterns = "/v1/api/test/customs")
public class UserServlet extends HttpServlet{
private static final long serialVersionUID = 8581537594033826517L;
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("custom sevlet");
resp.getWriter().flush();
resp.getWriter().close();
}
}
以下三种listener都是单例对象,并且都是在应用启动的时候创建的
以下是使用Servlet3.0的注解进行配置
启动类里面增加 @ServletComponentScan
,进行扫描,@WebListener
标记一个类为servlet
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@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========");
}
}
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class RequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("======requestDestroyed========");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("======requestInitialized========");
}
}
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
@WebListener
public class SessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("======httpSessionInitialized========" + this);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("======httpSessionDestroyed========" + this);
}
}
可以使用以下这种方式触发sessionDestroyed方法
@GetMapping("/")
public String hello(HttpServletRequest request) {
HttpSession session = request.getSession();
String ret = "Hello "+ session.getId();
session.invalidate();//将session作废
return ret;
}
拦截器是通过aop实现的,在后续执行顺序中可以看出。
定义一个拦截器配置类:
实现WebMvcConfigurer 添加@Configuration注解
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 OneIntercepter()).addPathPatterns("/**");
registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
}
拦截器类1:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class OneIntercepter implements HandlerInterceptor {
/**
* 进入controller方法之前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("OneIntercepter------->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("OneIntercepter------->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("OneIntercepter------->afterCompletion");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
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);
}
}
拦截器类2:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class OneIntercepter implements HandlerInterceptor {
/**
* 进入controller方法之前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("OneIntercepter------->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("OneIntercepter------->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("OneIntercepter------->afterCompletion");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
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);
}
}
执行顺序:
如果只添加拦截器类1,执行结果为:
OneIntercepter------->preHandle
controller method invoke
OneIntercepter------->postHandle
OneIntercepter------->afterCompletion
添加拦截器类1和拦截器类2,执行结果为:
OneIntercepter------->preHandle
TwoIntercepter------>preHandle
controller method invoke
TwoIntercepter------>postHandle
OneIntercepter------->postHandle
TwoIntercepter------>afterCompletion
OneIntercepter------->afterCompletion
注意要点:
拦截器最后路径一定要 “/**”, 如果是目录的话则是 /*/
Filter和Interceptor的执行顺序
过滤前->拦截前->controller执行->拦截后->过滤后
pre filter
OneIntercepter------->preHandle
controller method invoke
OneIntercepter------->postHandle
OneIntercepter------->afterCompletion
after filter
依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
基础配置:
是否开启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/
依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
基础配置:
开发时关闭缓存,不然没法看到实时页面
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
依赖:
这里以mysql数据库为例
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.2version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
配置:
#mybatis.type-aliases-package=com.example.demo.domain
#可以自动识别driver
#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
#打印sql日志和相关事务信息
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
扫描mybatis的mapper(相当于hibernate的dao)@MapperScan("com.example.demo.mapper")
@SpringBootApplication
@RestController
@MapperScan("com.example.demo.mapper")
public class DemoApplication {
@Autowired
private IStudentService studentService;
@GetMapping("/save")
public int save() {
return studentService.save();
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
domain(相当于hibernate的entity)、mapper和service
domain
package com.example.demo.domain;
public class Student {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
mapper
package com.example.demo.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import com.example.demo.domain.Student;
public interface StudentMapper {
@Insert("INSERT INTO student(name) VALUES(#{name})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
int insert(Student user);
}
service
package com.example.demo.service;
public interface IStudentService {
public int save();
}
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.domain.Student;
import com.example.demo.mapper.StudentMapper;
@Service
public class StudentService implements IStudentService {
@Autowired
private StudentMapper studentMapper;
@Transactional
@Override
public int save() {
Student s = new Student();
s.setName("s1");
return studentMapper.insert(s);
}
}
依赖:
使用mysql数据库测试
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
配置:
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.username =root
spring.datasource.password =zklong
#不用建hibernate.SEQUENCE表
spring.jpa.hibernate.use-new-id-generator-mappings=false
spring.jpa.show-sql=true
entity、dao和service
entity
package com.example.demo.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Student {
@Id
@GeneratedValue
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
dao
可以使用JpaRepository或CrudRepository都可以,这里需要注意如果StudentCustomer接口和实现类在一个包里,实现类就可以命名为StudentCustomerImpl,如果不在一个包里,实现类只能命名为StudentDaoImpl,也就是说StudentDaoImpl名称在哪都好使,而StudentCustomerImpl只有和接口在同一个包下才好使,建议直接命名为StudentDaoImpl,可以省去很多麻烦
//除了继承JpaRepository已经实现的方法,还可以自定义接口进行扩展
import org.springframework.data.repository.CrudRepository;
import com.example.demo.entity.Student;
public interface StudentDao extends JpaRepository<Student,Integer>, StudentCustomer{
}
//定义自定义接口
import com.example.demo.entity.Student;
public interface StudentCustomer {
public Student customerQuery(int id);
}
//定义自定义接口实现
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import com.example.demo.entity.Student;
public class StudentDaoImpl implements StudentCustomer {
@PersistenceContext
private EntityManager entityManager;
@Override
public Student customerQuery(int id) {
return (Student) entityManager.createQuery("from Student where id="+id).getSingleResult();
}
}
service
import com.example.demo.entity.Student;
public interface IStudentService {
public Student save();
public Student query(int id);
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.dao.StudentDao;
import com.example.demo.entity.Student;
@Service
public class StudentService implements IStudentService {
@Autowired
private StudentDao studentDao;
@Transactional
@Override
public Student save() {
Student s = new Student();
s.setName("s1");
return studentDao.save(s);
}
@Override
@Transactional
public Student query(int id) {
return studentDao.customerQuery(id);
}
}
依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
配置:
#使用db0
spring.redis.database=0
spring.redis.host=192.168.1.115
spring.redis.port=6379
spring.redis.timeout=3000
spring.redis.password=123456
参考:spring的线程池和定时器
@Scheduled参数:
其余配置参考spring的线程池和定时器
配置定时任务线程个数
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(3)); //设置多个线程去执行
}
}
//全局开启异步任务
@SpringBootApplication
@RestController
@EnableAsync
public class DemoApplication {
//调用异步任务类执行
@Autowired
private AsyncTask task;
@GetMapping("/async")
public String async() throws InterruptedException, ExecutionException {
Future<String> task2 = task.task4();
return task2.get();
}
}
//异步任务类配置
import java.util.concurrent.Future;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
@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 Future<String> task2() throws InterruptedException{
long begin = System.currentTimeMillis();
Thread.sleep(2000L);
long end = System.currentTimeMillis();
System.out.println("任务2耗时="+(end-begin));
return new AsyncResult<String>("任务2");
}
}
调整springboot日志级别
logging.level.org.springframework=debug
log4j配置示例:
===========log4j示例===========
### 设置###
log4j.rootLogger = debug,stdout,D,E
### 输出信息到控制抬 ###
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
使用在线转换工具转化log4j配置转logback配置
转化后:
<configuration>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<Target>System.outTarget>
<encoder>
<pattern>[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%npattern>
encoder>
appender>
<appender name="D" class="ch.qos.logback.core.rolling.RollingFileAppender">
<Append>trueAppend>
<File>D://logs/log.logFile>
<encoder>
<pattern>%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%npattern>
encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUGlevel>
filter>
appender>
<appender name="E" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>E://logs/error.logFile>
<Append>trueAppend>
<encoder>
<pattern>%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%npattern>
encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERRORlevel>
filter>
appender>
<root level="debug">
<appender-ref ref="stdout"/>
<appender-ref ref="D"/>
<appender-ref ref="E"/>
root>
configuration>
在resource下创建logback-spring.xml
,具体配置参考logback配置详解,root节点加载最后
样例:
<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>ERRORlevel>
<onMatch>DENYonMatch>
<onMismatch>ACCEPTonMismatch>
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.logfileNamePattern>
rollingPolicy>
appender>
<appender name="fileErrorApp"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERRORlevel>
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.logfileNamePattern>
<MaxHistory>1MaxHistory>
rollingPolicy>
appender>
<root level="INFO">
<appender-ref ref="consoleApp" />
<appender-ref ref="fileInfoApp" />
<appender-ref ref="fileErrorApp" />
root>
configuration>
依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-elasticsearchartifactId>
dependency>
依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-activemqartifactId>
dependency>
<dependency>
<groupId>org.messaginghubgroupId>
<artifactId>pooled-jmsartifactId>
dependency>
配置:
#整合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
spring.jms.pub-sub-domain=true//开启发布订阅
注意:发布订阅模式是需要开启的,开启之后消费者才可以收到消息,springboot默认只支持点对点
使用参考:
spring-jms-activemq
代码:
需要给启动类加
@EnableJms
配置文件存放路径:
spring boot允许通过命名约定按照一定的格式(application-{profile}.properties)来定义多个配置文件,比如:application-dev.properties,application-test.properties
依赖:
这个不需要spring-boot-starter-web
如果加了还需要在配置文件中进行配置,还不如直接删了
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webfluxartifactId>
dependency>
原理:
代码:
import java.time.Duration;
import java.util.Arrays;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@SpringBootApplication
@RestController
public class DemoApplication {
/**
* 一般单个对象
* @return
*/
@GetMapping("/mono")
public Mono<String> mono() {
return Mono.just("hello world");
}
/**
* 一般多个对象
* @return
*/
@GetMapping("/flux")
public Flux<Integer> flux() {
return Flux.fromIterable(Arrays.asList(1, 2, 3));
}
/**
* 流式多个对象(有点像异步推送,一个一个出来)
* @return
*/
@GetMapping(value = "/fluxStream", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
public Flux<Integer> fluxStream() {
return Flux.fromIterable(Arrays.asList(1, 2, 3)).delayElements(Duration.ofSeconds(2));
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
WebClient
可以使用WebClient进行webflux接口测试或者调用
@Test
public void testWebClient() {
Mono<String> bodyMono = WebClient.create().get().uri("http://localhost:8080/mono")
.accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(String.class);
System.out.println(bodyMono.block());
}
依赖:
web依赖即可,不用多余添加其他依赖
代码:
后端:
produces 必须为text/event-stream
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";
}
}
前端:
<html>
<head>
<meta charset="UTF-8">
<title>Insert title heretitle>
<script type="text/javascript">
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 testdiv>
<div id="result">div>
body>
html>
依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
代码:
Spring security 5.0之后要求passwordEncoder必须配置否则会报错,passwordEncoder是要求密码传递到后端以密文形式对比,存数据库之前的编码,如果需要在http上传输密文,则需要前端编码,然后后端解码之后再用passwordEncoder编码进数据库
/**
* 配置用户,内存用户,这里需要注意的是Spring security 5.0之后要求passwordEncoder必须配置否则会报错
*/
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("admin")
.password(new BCryptPasswordEncoder().encode("admin")).roles("ADMIN");
}
参考:spring security
依赖:
如果要使用@SendToUser("/aaa/response")发送给某一个用户信息,而不是所有用户,还需要加入以下包,因为springboot 连接activemq需要使用tcp,而springboot应该是用netty进行优化连接了,这里不用加activemq,感觉很费解;这里加入security只是为了支撑用户使用和鉴权
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
dependency>
<dependency>
<groupId>io.projectreactorgroupId>
<artifactId>reactor-coreartifactId>
dependency>
<dependency>
<groupId>io.projectreactor.nettygroupId>
<artifactId>reactor-nettyartifactId>
dependency>
参考:spring mvc websocket和异步推送中stomp的代码
执行器可以监控springboot的状态,几乎可以获取所有cpu、内存、硬盘还有url可访问性分析数据
依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
默认可以访问以下url:
/actuator/health
/actuator/info
/actuator
可以开启management.endpoints.web.exposure.include=*
开启所有执行器监控url
以还有其他开启样例:
开启全部:management.endpoints.web.exposure.include=*
开启某个:management.endpoints.web.exposure.include=metrics
关闭某个:management.endpoints.web.exposure.exclude=metrics
参考:Spring Boot Actuator:健康检查、审计、统计和监控
引入测试框架
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
@RunWith、@SpringBootTest、@Test
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyredisApplicationTests {
@Test
public void set() {
System.out.println(“111”);
}
}