-
@PathVariable: 路径参数
/{arg1}/{arg2}
方法中写法:(@PathVariable("arg1") String arg1, @PathVarible("arg2") String arg2
。 -
@RequestParam:
url
中?
后面的参数http://localhost:8080/v1?id=1
@Controller public class ArgsController { Map<String, String> res = new HashMap<>(); @GetMapping("/v1") @ResponseBody public Object getParams(@RequestParam String arg1, String arg2) { res.put("arg1", arg1); res.put("arg2", arg2); return res; } }
请求连接:
http://localhost:8080/v1?arg1=Hello&arg2=World
返回结果:
{"arg2":"World","arg1":"Hello"}
-
@RequestBody:
body
-
@RequestHeader:
header
-
HttpServletRequest request: 自动注入获取参数
@RestController:
返回json格式数据。
-
@JsonIgnore: 指定字段不反回,例如
password
。 -
@JsonFormat: 指定日期格式。
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss", locale = "zh", timezone = "GMT+8")
-
@JsonInclude: 空字段不反回。
@JsonInclude(Include.NON_NULL)
-
@JsonProperty: 指定别名。
静态文件加载顺序:
META/resources > resources > static > public
静态资源官方说明:https://docs.spring.io/spring-boot/docs/2.1.4.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-static-content
静态资源一般存放在CDN(内容分发网),前后端分离后,不会存放到后端服务器上。
如果想在 templates
存放静态资源,需要添加模板引擎,例如:themleaf
等。否则直接存放到上面的静态资源路径下。
MultipartFile
对象的 transferTo
用于保存文件。效率和操作比原先的FileOutStream方便和高效。
需要在 pom
文件中添加
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
idea中在 Maven -> Lifecycle -> install 双击打jar包,生成路径在 target 目录下。
如果运行的是jar包,上传路径需要时服务器的本地路径,不能指定为jar包中的路径。配置方法如下:
# application.properties file:${web.images-path}:获取配置文件中的配置
web.images-path=/User/test/Desktop
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,file:${web.images-path}
@Controller
@PropertySource("classpath:application.properties")
public class FileController {
// 读取配置文件中的值
@Value("${web.images-path}")
private String filePath;
资源服务器和应用服务器分开处理:
fastdfs、阿里云oss、nginx自己搭建的简单文件服务器等等。
pom.xml中添加如下内容:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
idea的配置:
-
Settings -> Build, Execution, Deployment -> Complier
选择 Build project automatically.
-
windows: ctrl+shift+alt+/ 到 registry
选择 compiler.automake.allow.when.app.running
Mac: command+shift+a ,输入 registry
-
/META-INF/maven
,/META-INF/resources
,/resources
,/static
,/public
, or/templates
-
以下手动配置的路径下的文件修改后不会热启动
spring.devtools.restart.exclude=static/**,public/**
-
使用触发文件触发,修改所有代码都不重启,只有通过一个文本grigger.txt去控制
spring.devtools.restart.trigger-file=trigger.txt
-
@PropertySource加载配置文件,在变量前添加@Value,自动注入给变量。
-
通过实体类注入配置信息:
<!-- 使用自己的配置信息 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
// 实体类的设置,类属性名称与配置文件一致
@Component @PropertySource("classpath:application.properties") @ConfigurationProperties(prefix = "test") public class ServerConfig { // 类属性名要与配置文件一致,不需要加@Value()注解。 String name; String domain; }
``` properties
# application.properties中的配置
test.name=spring-test
test.domain=www.spring-test.com
// Controller 中的使用
@Autowired
ServerConfig serverConfig;
@RequestMapping("/v2")
@ResponseBody
public Object getServer() {
return serverConfig;
}
访问:http://localhost:8080/v2 返回的结果是:
{
name: "spring-test",
domain: "www.spring-test.com",
}
-- 这种写法比较高效
SELECT * FROM
(
SELECT A.*, ROWNUM RN
FROM (SELECT * FROM TABLE_NAME) A
WHERE ROWNUM <= 40
)
WHERE RN >= 21
等同于下面的写法
-- 这种写法简单,但是效率比较低
SELECT * FROM
(
SELECT A.*, ROWNUM RN
FROM (SELECT * FROM TABLE_NAME) A
)
WHERE RN BETWEEN 21 AND 40
@Test
public void contextLoads() {
System.out.println("Hello!");
TestCase.assertEquals(1, 1);
}
@Test
public void contextLoads2() {
System.out.println("contextLoads2");
}
// test之前调用
@Before
public void testBefroe() {
System.out.println("before");
}
// test之后调用
@After
public void testAfter() {
System.out.println("after");
}
测试API接口使用MockMvc
模拟请求:
@RunWith(SpringRunner.class)
@SpringBootTest
// 需要添加这个注解
@AutoConfigureMockMvc
public class SpringbootTestApplicationTests {
@Autowired
MockMvc mockMvc;
@Test
public void testArgsController() throws Exception{
// perform模拟一个http请求
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/v1?arg1=1&arg2=2")).
andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
System.out.println(result.getResponse().getContentAsString());
}
}
- 创建
banner.txt
文件 - 修改配置
spring.banner.location=banner.txt
java -jar *.jar --debug
// 返回json串,如果使用@ControllerAdvice注解,需要添加@ReponseBody注解
//@ControllerAdvice
@RestControllerAdvice
public class CustomExceptionHandler {
// 获取所有的异常信息
@ExceptionHandler(value = Exception.class)
// @ResponseBody
Object handleException(Exception e, HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
map.put("code", 100);
map.put("msg", e.getMessage());
map.put("url", request.getRequestURL());
return map;
}
}
测试用的controller:
@Controller
public class TestExceptionController {
@GetMapping("/testException")
@ResponseBody
public Object index() {
int i = 1 / 0; // 这里会报异常
return new JsonData(0, "HelloWorld", null);
}
}
返回的结果:
{
msg: "/ by zero",
code: 100,
url: "http://localhost:8080/testException",
}
如果不自定义这些信息,返回的异常信息将非常不友好。
// 自定义异常类
public class MyException extends RuntimeException{
int code;
String msg;
public MyException(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
CustomExceptionHandler
中添加自定义的异常处理函数
@ExceptionHandler(value = MyException.class)
Object handleMyException(MyException e, HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
map.put("code", e.getCode());
map.put("msg", e.getMsg());
map.put("url", request.getRequestURL());
return map;
}
controller中抛出自定义异常:
@GetMapping("/testMyException")
@ResponseBody
public Object index2() {
throw new MyException(499, "page not found!");
}
使用 JMeter
做性能测试。
<!-- 打jar包 -->
<packaging>jar</packaging>
<!-- ... -->
<!-- 打war包 -->
<packaging>war</packaging>
<!-- ... -->
idea中在 Maven -> Lifecycle -> clean/install 双击打jar/war包,生成路径在 target 目录下。打war包的main函数与jar包不同,需要注意一下。
// jar包的main函数
@SpringBootApplication
public class SpringbootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class, args);
}
}
// war包的main函数
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(DemoApplication.class);
}
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
JavaBean是一个遵循特定写法的Java类,它通常具有如下特点:
- 这个Java类必须具有一个无参的构造函数。
- 属性必须私有化。
- 私有化的属性必须通过public类型的方法暴露给其它程序,并且方法的命名也必须遵守一定的命名规范。
举个例子: 人-->检票员(filter)-->景点
Servlet3.0提供新的自定义Filter的方式:
-
使用Servlet3.0注解进行配置。
-
启动类中添加
@ServletComponentScan
进行扫描。 -
新建一个Filter类,
implements Filter
, 实现对应的接口。 -
@WebFilter
标记一个类为filter,被spring进行扫描。urlPatterns // 拦截规则,支持正则
-
放行和拦截
chain.doFilter; // 放行 resp.sendRedirect("/index.html"); // 拦截跳转
完整代码:
// 启动类
@SpringBootApplication
@ServletComponentScan
public class SpringbootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class, args);
}
}
// 自定义过滤器
@WebFilter(urlPatterns = "/api/*", filterName = "MyFilter")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init MyFilter");
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter doFilter");
HttpServletRequest res = (HttpServletRequest) servletRequest;
HttpServletResponse rep = (HttpServletResponse) servletResponse;
String userName = res.getParameter("userName");
if ("tianer".equals(userName)) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
rep.sendRedirect("/index.html");
return;
}
}
@Override
public void destroy() {
System.out.println("destroy MyFilter");
}
}
// controller实现
@GetMapping("/api/testFilter")
@ResponseBody
public Object testFilter(@RequestParam String userName, String passWord) {
res.put("userName", userName);
res.put("passWord", passWord);
return res;
}
// 访问连接
http://localhost:8080/api/testFilter?userName=tianer&passWord=123
// 返回结果
{
passWord: "123",
userName: "tianer",
}
servlet3.0自定义原生servlet
// 启动函数需要加一下注解,与过滤器一样
@ServletComponentScan
@WebServlet(name = "userServlet", urlPatterns = "/api/v1/test/do")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("user servlet");
resp.getWriter().flush();
resp.getWriter().close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
常用的三个listener: ServletRequestListener
,ServletContextListener
,HttpSessionListener
@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========");
}
}
Servlets, Filters, Listeners官方文档介绍
SpringBoot2.x拦截器和旧版本拦截器区别:
-
@Configuration
- SpringBoot旧版本: 继承自
extends WebConfigurationAdapter
- SpringBoot2.x 配置拦截器:
implements WebMvcConfigurer
- SpringBoot旧版本: 继承自
-
自定义拦截器 HandlerInterceptor
- preHandle: Controller调用之前。
- postHandle: Controller之后调用,如果Controller出现异常,不执行吃方法。
- afterCompletion: 无论是否有异常,该方法都会被执行,用于资源清理。
-
按照注册顺序进行拦截,先注册,先拦截。
拦截器不生效的常见问题:
- 是否加了注解
@Configuration
- 拦截器路径是否有问题 ** 和 *
- 拦截器最后路径一定要 "/**",如果是目录则是 /*/
具体代码实例:
// 自定义一个config文件添加拦截器
@Configuration
public class MyConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/api2/*/**");
// 添加多个拦截器
// registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/api2/*/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
}
// 自定义拦截器
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("=====>LoginInterceptor preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("=====>LoginInterceptor postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("=====>LoginInterceptor afterCompletion");
}
}
// controller测试
@GetMapping("/api2/test")
@ResponseBody
public Object testInterceptor(@RequestParam String userName, String passWord) {
res.put("userName", userName);
res.put("passWord", passWord);
return res;
}
http://localhost:8080//api2/test?userName=1&passWord=123
2019-05-15 14:14:39.625 INFO 1970 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-05-15 14:14:39.625 INFO 1970 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-05-15 14:14:39.632 INFO 1970 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 7 ms
=====>LoginInterceptor preHandle
=====>LoginInterceptor postHandle
=====>LoginInterceptor afterCompletion
========requestDestroyed========
========requestInitialized========
========requestDestroyed========
2019-05-15 14:14:59.304 INFO 1970 --- [ Thread-23] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
destroy MyFilter
Spring的拦截器与Servlet的Filter有相似之处,比如都能实现权限检查、日志记录等。不同的是:
-
Filter是基于函数回调
doFilter()
,而Interceptor则是基于AOP思想编程。 -
使用范围不同:Filter是Servlet规范规定的,只能用于Web程序中。而拦截器既可以用于Web程序,也可以用于Application、Swing程序中。
-
规范不同:Filter是在Servlet规范中定义的,是Servlet容器支持的。而拦截器是在Spring容器内的,是Spring框架支持的。
-
使用的资源不同:同其他的代码块一样,拦截器也是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象。例如Service对象、资源对象、事务管理等,通过IoC注入到拦截器即可;而Filter则不能。
-
深度不同:Filter只在Servlet前后起作用。而拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。
所以在Spring架构的程序中,要优先使用拦截器。
Filter和Interceptor的执行顺序
过滤前->拦截前->action执行->拦截后->过滤后
功能: 通过pom.xml添加starter来简化依赖。
pom.xml
常用的starter:
spring-boot-starter-web
spring-boot-starter-data-redis
spring-boot-starter-jdbc
spring-boot-starter-data-mongodb
spring-boot-starter-thymeleaf
spring-boot-starter-freemarker
JSP(Java Server Pages) 动态网页技术,有服务器的JSP引擎来编译和执行,再讲生成的整个页面返回给客户端。
优缺点:
- 可以使用Java代码编写。
- 支持表达式语言(el,jstl)
- 内建函数
- JSP->Servlet(占用JVM内存)
- javaweb官方推荐
- Springboot不推荐
FreeMarker Template Language(FTL) 文件一般保存为 xxx.ftl
- 严格依赖MVC模式,不依赖Servlet容器(不占用JVM内存)
- 内建函数
轻量级的模板引擎(负责复杂业务逻辑的不推荐,解析DOM或者XML会占用比较多的内存)
- 可以直接在浏览器打开且显示正确的模板页面
- 直接是以.html结尾,可以直接编辑
<!--freemarker模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
# 是否开启freemarker缓存,本地为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/
-
原始的SQL:
缺点是:开发流程麻烦。
-
注册驱动/加载驱动。
Class.forName("com.mysql.jdbc.Driver");
-
建立连接。
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbname", "root", "root");
-
创建Statement。
-
执行SQL语句。
-
处理结果集。
-
关闭连接,释放资源。
-
-
apache dbutils框架
比原始的SQL方式简单一点。
-
jpa框架
Spring-data-jpa
jpa在复杂的查询的时候性能不太好。
-
Hiberante框架
ORM: 对象关系映射,Object Relational Mapping
企业大部分使用Hiberante框架。
-
Mybatis框架
互联网企业大部分使用Mybatis框架。
半ORM框架,不提供对象和关系的直接映射。
添加mybatis的相关依赖。
<!--Mybatis配置:Mybatis,mysql,druid-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>
@Data
public class User {
private int id;
private String name;
private String phone;
private int age;
private Date createTime;
}
-- sql
create database xdclass;
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;
# 数据源和mybatis配置
spring.datasource.url=jdbc:mysql://localhost:3306/xdclass?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
启动类中添加注解
@MapperScan("com.xdclass.springboot_test.mapper")
Mapper中:
@Mapper
@Component
public interface UserMapper {
// 使用#{},不要使用${},因为存在注入风险
@Insert("INSERT INTO user(NAME, PHONE, CREATE_TIME, AGE) VALUES (#{name}, #{phone}, #{create_time}, #{age})")
@Options(useGeneratedKeys = true, keyColumn = "id")
int inseart(User user);
}
Service实现:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper mapper;
@Override
public int add(User user) {
mapper.inseart(user);
int id = user.getId();
return id;
}
}
Controller实现
// RestController 返回json对象
@RestController
@RequestMapping(value = "/api/v1/user")
public class UserController {
@Autowired
private UserService service;
@GetMapping(value = "/add")
public Object add() {
User user = new User();
user.setAge(11);
user.setCreateTime(new Date());
user.setName("xdClass");
user.setPhone("100001110");
int id = service.add(user);
return user;
}
}
# 使用阿里巴巴druid数据源,默认使用自带的
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
事务 : 单机事务,分布式事务
隔离级别 :
- Serializable: 最严格,串行处理,消耗资源大。
- Repeatable Read: 保证一个事务不会修改已经有另一个事务读取但未提交(回滚)的数据。
- Read Committed: 大多数主流数据库的默认事务登记
- Read Uncommitted: 保证读取过程中不会读取到非法数据。
传播行为:
- PROPAGATION_REQUIRED: 支持当前事务,如果当前没有事务,就新建一个事务,
- PROPAGATION_SUPPORTS: 支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY: 支持当前事务,如果当前没有事务,抛异常。
- PROPAGATION_REQUIRES_NEW: 支持当前事务,如果当前没有事务,就把当前事务挂起。两个事务之间没有关系,一个异常,一个提交,不会同时回滚。
- PROPAGATION_NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER: 以非事务方式执行,如果当前存在事务,则抛异常。
事务注解:
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
生成Setter
,Getter
插件。
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
Accessor的中文含义是存取器,@Accessors
用于配置getter
和setter
方法的生成结果。
fluent的中文含义是流畅的,设置为true,则getter和setter方法的方法名都是基础属性名,且setter方法返回当前对象。如下:
@Data
@Accessors(fluent = true)
public class User {
private Long id;
private String name;
// 生成的getter和setter方法如下,方法体略
public Long id() {}
public User id(Long id) {}
public String name() {}
public User name(String name) {}
}
chain的中文含义是链式的,设置为true,则setter方法返回当前对象。如下:
@Data
@Accessors(chain = true)
public class User {
private Long id;
private String name;
// 生成的setter方法如下,方法体略
public User setId(Long id) {}
public User setName(String name) {}
}
prefix的中文含义是前缀,用于生成getter和setter方法的字段名会忽视指定前缀(遵守驼峰命名)。如下:
@Data
@Accessors(prefix = "p")
class User {
private Long pId;
private String pName;
// 生成的getter和setter方法如下,方法体略
public Long getId() {}
public void setId(Long id) {}
public String getName() {}
public void setName(String name) {}
}
- @Null 验证对象是否为null
- @NotNull验证对象是否不为null, 无法查检长度为0的字符串
- @NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
- @NotEmpty 检查约束元素是否为NULL或者是EMPTY,用在集合类上面
- @AssertTrue 验证 Boolean 对象是否为 true
- @AssertFalse验证 Boolean 对象是否为 false
- @Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内。可以验证集合内元素的多少。
- @Length(min=, max=) Validates that the annotated string is between min and max included.主要用于String类型
- @Past 验证 Date 和 Calendar 对象是否在当前时间之前
- @Future 验证 Date 和 Calendar 对象是否在当前时间之后
- @Pattern验证 String 对象是否符合正则表达式的规则
- @Min 验证 Number 和 String 对象是否大等于指定的值
- @Max 验证 Number 和 String 对象是否小等于指定的值
- @DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
- @DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
- @Digits 验证 Number 和 String 的构成是否合法
- @Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
- @Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
- @CreditCardNumber信用卡验证
- @Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
- @ScriptAssert(lang= ,script=, alias=)
- @URL(protocol=,host=, port=,regexp=, flags=)
- @Range(min=, max=) Checks whether the annotated value lies between (inclusive) the specified minimum and maximum.
- @Range(min=10000,max=50000,message="range.bean.wage")
pom.xml
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.properties
# redis基础配置
spring.redis.database=0
spring.redis.host=192.168.3.221
spring.redis.port=6379
# 连接超时时间,单位ms
spring.redis.timeout=3000
# redis连接池配置
# 连接池中最大空闲连接,默认是8
spring.redis.jedis.pool.max-idle=200
# 连接池中最小空闲连接,默认是0
spring.redis.jedis.pool.min-idle=200
# 等待可用连接的最大时间,单位ms,默认值-1,表示用不超时
spring.redis.jedis.pool.max-wait=1000
# -1表示不限制,pool已经分配了MaxActive个Jedis的实例,此时pool的状态时已耗尽。
spring.redis.jedis.pool.max-active=2000
简单测试:Controller
@Autowired
private StringRedisTemplate redisTpl;
@GetMapping(value = "/redis/add")
public Object redisAdd() {
redisTpl.opsForValue().set("name", "lilei");
return "OK";
}
@GetMapping(value = "/redis/get")
public Object redisGet() {
String value = redisTpl.opsForValue().get("name");
return value;
}
常见定时任务:
-
Java自带的定时任务
java.util.Timer
类。不推荐。- timer:配置麻烦,时间延后。
- timerTask:
-
Quartz框架。
- 配置简单。
- xml或者注解。
-
SpringBoot注解方式:
-
在启动类中添加@EnableScheduling定时任务,自动扫描。
-
定时任务业务类添加 @Component被容器扫描。
-
定时任务方法中加 @Scheduled(fixedRate = 2000) 定时执行方法。
-
代码实现:
@Component
public class TestTask {
@Scheduled(fixedRate = 2000) // 两秒执行一次任务
public void test() {
System.out.println("当前时间为:" + new Date());
}
}
- cron 定时任务表达式
@Scheduled(cron="*/1 * * * * *")
表示每秒。 - fixedrate: 定时多久执行一次(上一次 开始执行 点后2秒)。
- fixedDelay: 上一次 执行结束 时间点后2秒再次执行。
- fixedDelayString: 字符串形式,可以通过配置文件制定。
-
使用场景: log、发邮件、短信……等。
-
启动类里面添加
@EnableAsync
注解开启功能,自动扫描。 -
定义异步任务类并使用
@Component
标记组件被容器扫描,异步方法加上@Async
。注意点:
- 把异步任务封装到类中,不要写到controller中。
- 增加
Future<String>
返回结果AsyncResult<String>("task执行完成")
。 - 如果需要拿到结果,需要判断全部的
task.isDone()
。
-
通过注入的方式,注入到controller中,如果测试前后区别,将@Async注释掉。
实例代码:
@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");
}
}
@Autowired
private AsyncTask task;
@GetMapping(value = "/task")
public Object exeTask() throws Exception {
long begin = System.currentTimeMillis();
// task.task1();
// task.task2();
// task.task3();
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 total;
}
SpringBoot为什么推荐使用Logback
,有的日志框架性能太低,日志阻塞,导致QPS(query per second 每秒查询率)上不去。
1) 常用的日志框架: slf4j
,log4j
,logback
,common-logging
等。
2) logback介绍:log4j
的升级版,不能单独使用,推荐是和slf4j配合使用。
logback
分成三个模块: logback-core
, logback-classic
, logback-access
;
logback-core
是基础模块。
3)Logback的核心对象:
Logger: 日志记录器。
Appender: 指定日志输出的目的地,目的地可以是控制台或者文件。
Layout: 日志布局,格式化日志信息的输出。
4)日志级别: DEBUG < INFO < WARN < ERROR 。
log4j日志配置转logback: https://logback.qos.ch/translator/
日志官网: https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/#boot-features-logging
Logback 配置文件推荐使用: logback_spring.xml
注释:
<configuration> <!--三个子节点-->
<appender></appender>
<logger></logger>
<!--root节点放到最后-->
<root></root>
</configuration>
https://docs.spring.io/spring-data/elasticsearch/docs/3.1.8.RELEASE/reference/html/
github有版本匹配说明。
public Object testStream() {
User u1 = new User(1, "张三", "10010", 23, new Date());
User u2 = new User(2, "李四", "10011", 24, new Date());
User u3 = new User(3, "王五", "10012", 25, new Date());
User u4 = new User(4, "赵六", "10013", 26, new Date());
User u5 = new User(5, "周七", "10014", 27, new Date());
User u6 = new User(6, "郑八", "10015", 28, new Date());
List<User> userList = new ArrayList<>();
userList.add(u1);
userList.add(u2);
userList.add(u3);
userList.add(u4);
userList.add(u5);
userList.add(u6);
// 获取ids列表
List<String> ids = userList.stream().map(user -> String.valueOf(user.getId())).collect(Collectors.toList());
// 获取 id:name hashMap
Map<Integer, String> map = userList.stream().collect(Collectors.toMap(User::getId, User::getName));
// key 为 id, value 为 user 对象
Map<Integer, User> userMap = userList.stream().collect(Collectors.toMap(User::getId, user -> user));
// list中删除id为1的对象
userList.removeIf(user -> String.valueOf(user.getId()).equals("1"));
// 根据电话重新排序
Collections.sort(userList, (o1, o2) -> {
if (o1.getPhone().compareTo(o2.getPhone()) > 0) {
return 1;
} else if (o1.getPhone().compareTo(o2.getPhone()) < 0) {
return -1;
} else {
return 0;
}
});
// userList根据某个字段过滤排序
Map<String, User> filterMap = userList.stream().collect(Collectors.toMap(user -> "李四".equals(user.getName()) ? user.getPhone() : user.getName(), user -> user));
return filterMap;
}
-
什么是JMS: Java消息服务(java message service),Java平台中关于面向消息中间件的接口。
-
jms是一种与厂商无关的API,用来访问消息收发系统消息,它类似JDBC(Java Database Connectivity)。这里,JDBC可以用来访问许多不同关系数据库的API。
-
使用场景:
-
跨平台
-
多语言
-
多项目
-
解耦
-
分布式事务
-
流量控制
-
最终一致性
-
RPC调用
上下游对接,数据源变动->通知下属
-
-
概念
- JMS提供者: Apache ActiveMQ、RabbitMQ、Kafka、Notify、MetaQ、RocketMQ
- JMS生产者
- JMS消费者
- JMS消息
- JMS队列
- JMS主题
JMS消息通常有两种类型:点对点类型(point-to-point),发布/订阅类型(Publish/Subscribe)
-
编程模型
MQ中需要使用的一些类
- ConnectionFactory: 连接工厂,JMS用它创建连接。
- Connection: JMS客户端到JMS Provider的链接。
- Session: 一个发送或接受消息的线程。
- Destination: 消息目的地,消息发送给谁。
- MessageConsumer/MessageProducer: 消息接收者,消费者。
特性:
- Supports a variety of Cross Language Clients and Protocols from Java, C, C++, C#, Ruby, Perl, Python, PHP
- OpenWire for high performance clients in Java, C, C++, C#
- Stomp support so that clients can be written easily in C, Ruby, Perl, Python, PHP, ActionScript/Flash, Smalltalk to talk to ActiveMQ as well as any other popular Message Broker
- AMQP v1.0 support
- MQTT v3.1 support allowing for connections in an IoT environment.
- full support for the Enterprise Integration Patterns both in the JMS client and the Message Broker
- Supports many advanced features such as Message Groups, Virtual Destinations, Wildcards and Composite Destinations
- Fully supports JMS 1.1 and J2EE 1.4 with support for transient, persistent, transactional and XA messaging
- Spring Support so that ActiveMQ can be easily embedded into Spring applications and configured using Spring’s XML configuration mechanism
- Tested inside popular J2EE servers such as TomEE,Geronimo, JBoss, GlassFish and WebLogic
- Includes JCA 1.5 resource adaptors for inbound & outbound messaging so that ActiveMQ should auto-deploy in any J2EE 1.4 compliant server
- Supports pluggable transport protocols such as in-VM, TCP, SSL, NIO, UDP, multicast, JGroups and JXTA transports
- Supports very fast persistence using JDBC along with a high performance journal
- Designed for high performance clustering, client-server, peer based communication
- REST API to provide technology agnostic and language neutral web based API to messaging
- Ajax to support web streaming support to web browsers using pure DHTML, allowing web browsers to be part of the messaging fabric
- CXF and Axis Support so that ActiveMQ can be easily dropped into either of these web service stacks to provide reliable messaging
- Can be used as an in memory JMS provider, ideal for unit testing JMS
官网地址:http://activemq.apache.org/index.html
面板:
- Name : 队列名称。
- Number Of Pending Messages: 等待消费的消息个数。
- Number Of Consumers: 当前连接的消费者数目。
- Messages Enqueued: 进入消息队列的总个数,包括出队列的和待消费的,这个数量只增不减。
- Messages Dequeued: 已经消费的消息数量。
依赖:
<!--整合消息队列-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<!--如果配置线程池加入如下配置-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
</dependency>
配置文件:
# activeMQ配置
spring.activemq.broker-url=tcp://127.0.0.1:8161
# activeMQ集群配置
# spring.activemq.broker-url=failover:(tcp://localhost:616161,tcp://localhost:717171)
spring.activemq.user=admin
spring.activemq.password=admin
# 线程池的配置,需要添加线程池依赖,不添加开启会报错。
# spring.activemq.pool.enabled=true
# spring.activemq.pool.max-connections=100
springboot启动类添加:
@EnableJms // 开启支持jms
创建Service:
@Service
public class PorducerServiceImpl implements ProducerService {
@Autowired
private JmsMessagingTemplate jmsTemplate;
@Autowired
private Queue queue;
@Override
public void sendMessage(Destination destination, final String message) {
jmsTemplate.convertAndSend(destination, message);
}
@Override
public void sendMessage(final String message) {
jmsTemplate.convertAndSend((Destination) this.queue, message);
}
}
被实现的接口:
public interface ProducerService {
/**
* 指定消息队列,还有消息
* @param destination 指定消息队列
* @param message 消息
*/
void sendMessage(Destination destination, final String message);
/**
* 使用默认消息队列发送消息
* @param message 消息
*/
void sendMessage(final String message);
}
controller测试
@RestController
@RequestMapping(value = "/api/v1")
public class OrderController {
@Autowired
private ProducerService producerService;
@GetMapping(value = "order")
public Object order(String msg) {
Destination destination = new ActiveMQQueue("order.queue");
producerService.sendMessage(destination, msg);
return "Send OK";
}
@GetMapping(value = "common")
public Object common(String msg) {
producerService.sendMessage(msg);
return "Send OK";
}
}
注意,头文件一定引正确。
消费者实时监听对应的队列:
@Component
public class OrderConsumer {
@JmsListener(destination = "order.queue")
public void receiveQueue(String text) {
System.out.println("OrderConsumer收到的报文为:" + text);
}
}
# 开启发布订阅配置
spring.jms.pub-sub-domain=true
@Component
public class TopicSub {
@JmsListener(destination = "video.topic")
public void receive1(String text) {
System.out.println("video.topic 消费者: receive1 = " + text);
}
@JmsListener(destination = "video.topic")
public void receive2(String text) {
System.out.println("video.topic 消费者: receive2 = " + text);
}
@JmsListener(destination = "video.topic")
public void receive3(String text) {
System.out.println("video.topic 消费者: receive3 = " + text);
}
}
默认不同时支持两种模式(p2p和pub-sub),需要修改一些信息。在启动类中:
// 需要给topic定义独立的JmsListenerContainer
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setPubSubDomain(true);
bean.setConnectionFactory(activeMQConnectionFactory);
return bean;
}
并在订阅者中@JmsListener中添加如下代码:
@JmsListener(destination = "video.topic", containerFactory = "jmsListenerContainerTopic")
然后禁用pub-sub配置:
# 开启发布订阅配置,不使用配置文件控制
# spring.jms.pub-sub-domain=true
阿里开源给Apache的一个高性能消息队列中间件。
-
理解
-
基于事件驱动(Event-driven)
-
一系列事件被称为"流"
-
异步
-
非阻塞
-
观察者模式
例子:
int a = b + c; // 命令式编程: b和c变化,a不变化 int a = b + c; // 响应式编程: a的变化与b、c的变化相关(事件驱动)
-
-
https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/#boot-features-webflux