springboot学习笔记

文章目录

  • springboot
    • 依赖
    • 一个helloword
    • 使用jackson序列化或反序列化
    • springboot的目录结构
    • 文件上传下载
      • 多文件上传
    • 打包
      • jar包(默认)
      • war包
    • springboot热部署
    • springboot 配置文件
    • springboot 测试
    • springboot 个性化banner
    • filter过滤器
    • servlet
    • listener
    • 拦截器
      • 拦截器和filter的区别
    • 模板引擎
      • freemarker
      • thymeleaf
    • 持久化
      • mybatis
      • jpa&hibernate
      • 事务隔离级别
      • 事务传播行为
    • 分布式缓存
      • redis
    • 定时任务和异步任务
      • 定时任务
      • 异步任务
    • 日志
      • log4j配置转logback
      • 自定义logback配置
    • ElastcSearch(搜索引擎)
    • 消息中间件
      • activemq
    • 多环境配置profiles
    • 响应式编程webflux
    • 异步推送
    • security
    • websocket
    • 执行器actuator
    • 测试

springboot

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等

一个helloword

加入上边的依赖,然后写一个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注解
jackson处理相关自动
指定字段不返回:@JsonIgnore
指定日期格式:@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
空字段不返回:@JsonInclude(Include.NON_NULL)
指定别名:@JsonProperty

springboot的目录结构

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包不需要指定打包方式,因为默认就是jar包
加入以下依赖即可,spring-boot-maven-plugin会自动扫描main方法,无需手动指定

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-maven-pluginartifactId>
		plugin>
	plugins>
build>

war包

指定打包形式为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热部署

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

springboot 配置文件

  • 方式一
    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")方式
    @ConfigurationProperties自动注解所有属性,但是要求和配置文件中的名称一致,可以指定前缀
@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;
}

springboot 测试

依赖

<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);
	}
}

springboot 个性化banner

关闭banner

spring.main.banner-mode=off

在resource文件加下建立banner.txt,默认banner路径就是这个,修改banner.txt的内容
banner字符生成网站

filter过滤器

以下是使用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");
      }
}

servlet

以下是使用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

以下三种listener都是单例对象,并且都是在应用启动的时候创建的
以下是使用Servlet3.0的注解进行配置
启动类里面增加 @ServletComponentScan,进行扫描,@WebListener 标记一个类为servlet

  • servletContext在整个应用启动到结束中生效,contextInitialized和contextDestroyed方法只会被调用一次
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========");
	}
}
  • servletRequest是在一个request请求被创建和销毁的过程中生效,requestDestroyed和requestInitialized方法会随请求的到来而多次调用
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========");	
	}   
}
  • httpSession则是在一个session会话中生效,在一个session被创建直到失效的过程中都起作用,不过一个启动的应用中httpSession对象可以有多个,比如同一台电脑两个浏览器访问(可以使用清除cookie数据的方式进行模拟)
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的区别

  • Filter是基于函数回调 doFilter(),而Interceptor则是基于AOP思想
  • Filter在只在Servlet前后起作用,而Interceptor够深入到方法前后、异常抛出前后等
  • Filter依赖于Servlet容器即web应用中,而Interceptor不依赖于Servlet容器所以可以运行在多种环境
  • 在接口调用的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次

Filter和Interceptor的执行顺序
过滤前->拦截前->controller执行->拦截后->过滤后

pre filter
OneIntercepter------->preHandle
controller method invoke
OneIntercepter------->postHandle
OneIntercepter------->afterCompletion
after filter

模板引擎

freemarker

依赖:

<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/

thymeleaf

依赖:

<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

持久化

mybatis

依赖:
这里以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);
	}
}

jpa&hibernate

依赖:
使用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);
	}
}

事务隔离级别

  • Serializable: 最严格,串行处理,消耗资源大
  • Repeatable Read:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据
  • Read Committed:大多数主流数据库的默认事务等级
  • Read Uncommitted:保证了读取过程中不会读取到非法数据。

事务传播行为

  • PROPAGATION_REQUIRED–支持当前事务,如果当前没有事务,就新建一个事务,最常见的选择。
  • PROPAGATION_SUPPORTS–支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY–支持当前事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW–新建事务,如果当前存在事务,把当前事务挂起, 两个事务之间没有关系,一个异常,一个提交,不会同时回滚
  • PROPAGATION_NOT_SUPPORTED–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER–以非事务方式执行,如果当前存在事务,则抛出异常

分布式缓存

redis

依赖:

<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的线程池和定时器

定时任务

  • 启动类里面 @EnableScheduling开启定时任务,自动扫描
  • 定时任务业务类 加注解 @Component被容器扫描
  • 定时执行的方法加上注解 @Scheduled(fixedRate=2000) 定期执行一次
  • @Scheduled注解注释的方法只会被同一个线程执行,不论配置定时任务线程个数多少

@Scheduled参数:

  • cron 定时任务表达式 @Scheduled(cron="*/1 * * * * *") 表示每秒
  • crontab 工具 https://tool.lu/crontab/
  • fixedRate: 定时多久执行一次(上一次开始执行时间点后xx秒再次执行;)
  • fixedDelay: 上一次执行结束时间点后xx秒再次执行
  • fixedDelayString: 字符串形式,含义和fixedDelay一样,就是字符串表达而已,为了可以通过配置文件指定,因为字符串可以用占位符,而数字不可以

其余配置参考spring的线程池和定时器

配置定时任务线程个数

@Configuration  
public class ScheduleConfig implements SchedulingConfigurer {  
  @Override  
  public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {  
      taskRegistrar.setScheduler(Executors.newScheduledThreadPool(3)); //设置多个线程去执行  
  }  
} 

异步任务

  • 启动类里面使用@EnableAsync注解开启功能,自动扫描
  • 定义异步任务类并使用@Component标记组件被容器扫描,异步方法加上@Async
    注意点:
    1)要把异步任务封装到类里面,不能直接写到Controller
    2)增加Future 返回结果 AsyncResult(“task执行完成”);
    3)如果需要拿到结果 需要判断全部的 task.isDone()
  • 通过注入方式,注入到controller里面,如果测试前后区别则改为同步则把Async注释掉
    配置可参考配置参考spring的线程池和定时器,不过springboot无需指定执行器,以下是配置:
//全局开启异步任务
@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配置转logback

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>

自定义logback配置

在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>

ElastcSearch(搜索引擎)

依赖:

<dependency>  
    <groupId>org.springframework.bootgroupId>  
    <artifactId>spring-boot-starter-data-elasticsearchartifactId>  
dependency>  

消息中间件

activemq

依赖:


		<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

多环境配置profiles

配置文件存放路径:

  • resources根目录的“/config”包下
  • resources的根目录下
  • 建议将application.properties放在resources下,其他的profiles配置放在resources/config下(最佳实践,因为放classpath导致配置太多,不好找)

spring boot允许通过命名约定按照一定的格式(application-{profile}.properties)来定义多个配置文件,比如:application-dev.properties,application-test.properties

响应式编程webflux

依赖:
这个不需要spring-boot-starter-web如果加了还需要在配置文件中进行配置,还不如直接删了

<dependency>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-webfluxartifactId>
dependency>

原理:

  • Mono 表示的是包含 0 或者 1 个元素的异步序列
  • Flux 表示的是包含 0 到 N 个元素的异步序列
  • Flux 和 Mono 之间可以进行转换

代码:

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>

security

依赖:

<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

websocket

依赖:
如果要使用@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的代码

执行器actuator

执行器可以监控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);
	}
}

你可能感兴趣的:(springboot)