目录
- 目录
- 1. 学习网址
- 2. Maven配置
- 3. 属性配置
- 注解 说明 @Value @Component @ConfigurationProperties @SpringBootApplication @RunWith(SpringRunner.class) @SpringBootTest @Test
- 4. 启动原理解析
- 5. Thymeleaf
- 6. 静态资源和拦截器
- 7. Controller
- 8. Spring Data JPA
- 9. 事务管理
- 注解 说明 @Transactional 注解方法内的操作呈事务性,同时成功或者同时不执行
- 10. AOP
- 11. 日志
- 12. 单元测试
- 13. 热部署
- 14. Swagger UI开发接口文档
- 注解 说明 @Api() 注释接口,用于类 @ApiOperation() 用于方法 @ApiParam() @ApiModel() @ApiModelProperty() @ApiIgnore() @ApiImplicitParam() @ApiImplicitParams() @ApiResponse
- 15. JavaMailSender
- 16. Spring Security
- 17. RestTemplate
- 18. FreeMarker
1. 学习网址
- 程序员DD SpringBoot博客
- 嘟嘟独立博客 SpringBoot干货系列
- 慕课SpringBoot课程
- 路径:2小时学会SpringBoot –> SpringBoot进阶之Web进阶 –> SpringBoot 2.0深度实践-初遇SpringBoot –> 基于SpringBoot十分钟搞定后台管理平台 –> SpringBoot开发常用技术整合 –> SpringBoot热部署
2. Maven配置
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- pom版本 -->
<modelVersion>4.0.0</modelVersion>
<groupId>com.kingsong</groupId>
<artifactId>projectName</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<!-- Spring Boot父级依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
<relativePath/><!-- lookup parent from repository -->
</parent>
<!-- 如果不希望使用默认版本依赖,可在此处覆盖 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. 属性配置
server.port=8080
server.context-path=/projectName
- ### application.yml[推荐]
server:
port: 8080
context-path: /projectName
多环境配置
- resources
- application.yml
- application-dev.yml[开发环境]
- application-prod.yml[生产环境]
#application.yml->dev
spring:
profiles:
active: dev
#application.yml->prod
spring:
profiles:
active: prod
自定义配置
- 可在application.yml中配置,也可在src/main/resources/新建.yml文件
- config.yml
com:
kingsong:
config:
filePath: E:/save/
pre_fix: pic_
absolutePath: ${filePath}${pre_fix}
- bean类
@Configuration
@ConfigurationProperties(prefix="com.kingsong")
@PropertySource("classpath:config.yml")
public class ConfigBean {
private String filePath;
private String pre_fix;
private String absolutePath;
// Getter and Setter...
}
- 入口类添加注解
@SpringBootApplication
@EnableConfigurationProperties({ConfigBean.class})
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class,args);
}
}
- 在其他类中引用
public class Test {
@Autowired
ConfigBean configBean;
}
- ### 相关注解
注解 | 说明 |
---|---|
@Value | |
@Component | |
@ConfigurationProperties | |
@SpringBootApplication | |
@RunWith(SpringRunner.class) | |
@SpringBootTest | |
@Test |
4. 启动原理解析
/**
* 运行入口
* 要放在根目录下
*/
@SpringBootApplication
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebAppliction.class,args);
}
}
/**
* 测试入口
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class WebApplicationTests {
@Test
public void contextLoads() {
}
}
- ==@SpringBootApplication==
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
- 三个重要的注解:
- @Configuration: IOC容器的配置类
- @ComponentScan: 自动扫描组件
- @EnableAutoConfiguration: 加载@Configuration配置
5. Thymeleaf
- Spring Boot支持多种模版引擎:
- FreeMarker
- Groovy
- Thymeleaf[官方推荐]
- Mustache
- jsp[不推荐]
- 默认配置路径为:
src/main/resources/templates
5.1. 简介
Thymeleaf是一款用于渲染XML/XHTML/HTML5内容的模板引擎。类似JSP,Velocity,FreeMaker等,它也可以轻易的与SpringMVC等Web框架进行集成作为Web应用的模板引擎。与其它模板引擎相比,Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用。它的功能特性如下:
- Spring MVC中@Controller中的方法可以直接返回模板名称,接下来Thymeleaf模板引擎会自动进行渲染
- 模板中的表达式支持Spring表达式语言(Spring EL)
- 表单支持,并兼容Spring MVC的数据绑定与验证机制
- 国际化支持
5.2. 依赖配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
5.3. 默认参数配置
# THYMELEAF (ThymeleafAutoConfiguration)
#开启模板缓存(默认值:true)
spring.thymeleaf.cache=true
#Check that the template exists before rendering it.
spring.thymeleaf.check-template=true
#检查模板位置是否正确(默认值:true)
spring.thymeleaf.check-template-location=true
#Content-Type的值(默认值:text/html)
spring.thymeleaf.content-type=text/html
#开启MVC Thymeleaf视图解析(默认值:true)
spring.thymeleaf.enabled=true
#模板编码
spring.thymeleaf.encoding=UTF-8
#要被排除在解析之外的视图名称列表,用逗号分隔
spring.thymeleaf.excluded-view-names=
#要运用于模板之上的模板模式。另见StandardTemplate-ModeHandlers(默认值:HTML5)
spring.thymeleaf.mode=HTML5
#在构建URL时添加到视图名称前的前缀(默认值:classpath:/templates/)
spring.thymeleaf.prefix=classpath:/templates/
#在构建URL时添加到视图名称后的后缀(默认值:.html)
spring.thymeleaf.suffix=.html
#Thymeleaf模板解析器在解析器链中的顺序。默认情况下,它排第一位。顺序从1开始,只有在定义了额外的TemplateResolver Bean时才需要设置这个属性。
spring.thymeleaf.template-resolver-order=
#可解析的视图名称列表,用逗号分隔
spring.thymeleaf.view-names=
6. 静态资源和拦截器
Spring Boot项目中的classpath是指resources/,
6.1. 默认资源映射(按优先级排序)
- classpath:/MTEA-INF/resources
- classpath:/resources
- classpath:/static
- classpath:/public
6.2. 配置文件
# 默认值为 /**
spring.mvc.static-path-pattern=
# 默认值为 classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
spring.resources.static-locations=这里设置要指向的路径,多个使用英文逗号隔开
6.3. 自定义资源映射
@Configuration
public class MyWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
/**
* 配置静态访问资源
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/my/**").addResourceLocations("classpath:/my/");
super.addResourceHandlers(registry);
}
}
- 指定外部目录
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/my/**").addResourceLocations("file:E:/my/");
super.addResourceHandlers(registry);
}
6.4. 拦截器的实现和配置
- 拦截器
/**
* 自定义拦截器
*/
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean flag =true;
User user=(User)request.getSession().getAttribute("user");
if(null==user){
response.sendRedirect("toLogin");
flag = false;
}else{
flag = true;
}
return flag;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
- 在WebMvcConfigurerAdapter中添加
/**
* 添加拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns 用于添加拦截规则
// excludePathPatterns 用户排除拦截
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/toLogin","/login");
super.addInterceptors(registry);
}
7. Controller
7.1. 表单验证
- 实体类[接受参数,可以是DTO类]
@Entity
@Table(name="tb_user")
public class UserDTO {
@Id
private String number;
@Min(value=18, message="报错信息")
private Integer age;
}
- Controller
@PostMapping(value="/users")
public User userAdd(@Valid UserDTO userInfo, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
// 获取错误信息
// bindingResult.getFieldError().getDefaultMessage();
return null;
}
User user = new User();
user.setNumber(userInfo.getNumber());
user.setAge(userInfo.getAge());
return userRepository.save(user);
}
7.2. 相关注解
注解 | 说明 |
---|---|
@Controller | |
@RestController | @Controller+@ResponseBody |
@RequestMapping(value=”“,method=”“) | |
@PathVariable | 获取url中的数据 |
@RequestParam | 获取请求参数的值 |
@GetMapping | 组合注解,=@RequestMapping(method=RequestMethod.GET) |
@Valid | 表单验证 |
@Min | 最小值 |
@RequestMapping(value="/say/{id}")
public String say(@PathVariable("Id") Integer id ) {
// eg:url="http://say/100" -> id=100
}
@RequestMapping(value="/say")
public String say(@RequestParam(value="Id",required=false,defaultValue="0") Integer id) {
// eg:url="http:say?id=100" -> id=100
// url="http:say" -> id=0
// 若是@RquestParam("id"),当前端没有传入id值时,会报错
}
8. Spring Data JPA
8.1. pom.xml配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
8.2. application.yml配置
spring:
profiles:
active: dev
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/dbName?characterEncoding=utf8&useSSL=true
username: root
password: root
jpa:
hibernate:
ddl-auto: none
show-sql: true
8.3. repository实现
// JpsRepository<类名,主键类型>
public interface UserRepository extends JpaRepository<User,String> {
// 常规方法无需定义,直接使用,如save(),delete(),findAll()...
// 特殊需要,通过属性名查找
public List<User> findByAge(Integer age);
// 根据姓来排序并获取第一个User
User findFirstByOrderByLastnameAsc();
/**
* 分页查询
* Pageable需要三个参数:
* 1. page: 页码,默认0
* 2. size: 页大小,默认20
* 3. sort: 排序属性,升降序(默认升序)
*/
Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);
/**
* 分页查询+限制条件
*/
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
}
8.4. 解析Page类
- Page的结构
{
"content": [], // 返回的数据集
"last": [true | false], // 是否最后一页
"totalPages": , // 总页数
"totalElements": , // 总数据量
"number": , // 当前页码
"size": , // 页大小
"sort": [ // 排序相关
{
"direction": [ASC | DESC],
"property": ,
"ignoreCase": [true | false],
"nullHandling": "NATIVE",
"ascending": true
}
],
"first": [true | false], // 是否第一页
"numberOfElements": // 当前页的数据量
}
8.5. 方法名关键字
关键字 | 样例 | 持久化查询语句片段(JPQL snippet) |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection ages) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
Distinct | findDistinctByName(String name) | select distinct * from … where x.name=?1 |
8.6. 相关注解
注解 | 说明 |
---|---|
@Qualifier() | 区分传入相同类别参数 |
@PageableDefault() | 设置分页参数的默认值,如page,size |
9. 事务管理
9.1. 相关注解
注解 | 说明 |
---|---|
@Transactional | 注解方法内的操作呈事务性,同时成功或者同时不执行 |
10. AOP
10.1. AOP的初衷
- DRY:Don’t Repeat Yourself
- Soc:Separation of Concerns
- 水平分离:展示层->服务层->持久层
- 垂直分离:模块划分(订单、库存等)
- 切面分离:分离功能性需求与非功能性需求
10.2. 应用场景
权限控制、缓存控制、事务控制、审计日志、性能监控、分布式追踪、异常处理
10.3. 原理概述
- 织入时机
- 编译期(AspectJ)
- 类加载时(AspectJ 5+)
- 运行时(Spring AOP)
- 运行时织入
- 实现:代理
- 静态代理
- 【缺点】代码重复
- 动态代理
- 基于接口代理:JDK代理
- 基于继承代理:Cglib代理
- 示例
- 静态代理
/**
* 静态代理
*/
// 接口类
public interface Subject {
public void show();
}
// 真实类
public classs RealSubject implements Subject {
@Override
public void show() {
}
}
// 代理类
public class Proxy implements Subject {
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void show() {
// before
try {
realSubject.show();
} catch(Exception e) {
// AfterThrowing
} fianlly {
// after
}
}
}
// 客户端调用
public class Client {
public static void main(String[] args) {
Subject subject = new Proxy(new RealSubject);
subject.show();
}
}
- JDK代理
/**
* JDK代理
*/
// 代理类
public class JdkProxySubject implements InvocationHandler {
private RealSubject realSubject;
public JdkProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// before
Object result = null;
try {
result = method.invoke(realSubject,args);
} catch(Exception e) {
// AfterThrowing
} finally {
// after
}
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Subject subject = Proxy.newProxyInstance(Client.class.getClassLoader(),new Class[]{Subject.class},new JdkProxySubject(new RealSubject))
}
}
10.4. maven配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
10.5. 切面表达式
graph LR
A(expression)-->B(designators 指示器);
A(expression)-->C(wildcards 通配符);
A(expression)-->D(operators 运算符);
B(designators 指示器)-->E("execution()");
B(designators 指示器)-->F(...) ;
C(wildcards 通配符)-->G(*);
C(wildcards 通配符)-->H(..);
C(wildcards 通配符)-->I(+);
D(operators 运算符)-->J(&&);
D(operators 运算符)-->K("||");
D(operators 运算符)-->L(!);
graph LR
A(designators)-->B(匹配方法)
A-->C(匹配注解)
A-->D(匹配包/类型)
A-->E(匹配对象)
A-->F(匹配参数)
B-->G("execution()")
C-->H("@target()")
C-->I("@args()")
C-->J("@within()")
C-->K("@annotaion()")
D-->L("within()")
E-->M("this()")
E-->N("bean()")
E-->O("target()")
F-->P("args()")
execution格式
execution(
[modifier-pattern]
ret-type-pattern
[declaring-type-pattern]
name-pattern(param-pattern)
[throws-pattern]
)通配符
- *:匹配任意数量的字符
- +:匹配指定类及其子类
- ..:一般用于匹配任意数的子包或参数
- Advice注解
- @Before:前置通知
- @After(finally):后置通知,方法执行完之后
- @AfterReturning:返回通知,成功执行之后
- @AfterThrowing,异常通知,抛出异常之后
- @Around,环绕通知
10.6. 示例
@Aspect
@Component
public class AuthSercurity {
// 匹配方法
// 匹配service包下任何以Service结尾的类的所有方法
@Pointcut("execution(public * com.kingsong.service.*Service.*(..))")
public void matchCondition() {}
// 匹配注解
// 匹配方法标注有AdminOnly注解的方法
@PointCut("@annotation(com.kingsong.demo.security.AdminOnly)")
public void adminOnly() {}
// 匹配标注有Beta的类底下的方法,要求的annotation的RetentionPolicy级别为CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void annoWithinDemo() {}
// 匹配标注有Repository的类底下的方法,要求的annotation的RetentionPolicy级别为RUNTIME
@Pointcut("target(org.springframework.stereotype.Repository)")
public void annoTargetDemo() {}
// 匹配传入的参数类型标注有Repository注解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")
public void annoArgsDemo() {}
// 匹配包/类型
// 匹配UserController类里头的所有方法
@Pointcut("within(com.kingsong.controller.UserController)")
public void matchType() {}
// 匹配com.kingsong包及子包下所有类的方法
@Pointcut("within(com.kingsong.*)")
public void matchPackage() {}
// 匹配对象
// 匹配AOP对象的目标对象为指定类型的方法,即DemoDao的aop代理对象的方法
@Pointcut("this(com.kingsong.DemoDao)")
public void thisDemo() {}
// 匹配实现IDao接口的目标对象(而不是aop代理后的对象)的方法,即DemoDao的方法
@Pointcut("target(com.kingsong.IDao)")
public void targetDemo() {}
// 匹配所有以Service结尾的bean里头的方法
@Pointct("bean(*Service)")
public void beanDemo() {}
// 匹配参数
// 匹配任何以find开头而且只有一个Long参数的方法
@Pointcut("execution(* *..find*(Long))")
public void argDemo1() {}
// 匹配任何只有一个Long参数的方法
@Pointcut("args(Long)")
public void argsDemo2() {}
// 匹配任何以find开头的而且第一个参数为Long型的方法
@Pointcut("execution(* *..find*(Long,..))")
public void argsDemo3() {}
// 匹配第一个参数为Long类型的方法
@Pointcut("args(Long,..)")
public void argsDemo4() {}
// 前置通知
@Before(adminOnly)
public void checkAuthor() {
// 检验权限
}
// 成功执行通知
@AfterReturning(value=pointcut,returning=result)
public voiid afterDemo() {
}
// 环绕通知
@Around(pointcut)
public java.lang.object after(ProceedingJoinPoint joinPoint) {
java.lang.Object result = null;
try{
// afterReturning
result = joinPoint.proceed(joinPoint.getArgs());
} catch(Throwable e) {
// 处理异常
} finally {
// finally
}
}
}
@Service
public class UserService {
@AdminOnly
public void delete(User user) {
// ...
}
}
10.7. 相关注解
注解 | 说明 |
---|---|
@Aspect | 切面 |
@Pointcut | 切点 |
@Before | |
@After | |
@Transactional | 事务 |
@PreAuthorize | 安全 |
@Cacheable | 缓存 |
11. 日志
11.1. 简介
- Spring Boot在所有内部日志中使用Commons Logging,但是默认配置也提供了对常用日志的支持,如:Java Util Logging, Log4J, Log4J2和Logback。每种Logger都可以通过配置使用控制台或者文件输出日志内容。
- 默认情况下,Spring Boot会用Logback来记录日志,并用INFO级别输出到控制台
- 从上图可以看到,日志输出内容元素具体如下:
- 时间日期:精确到毫秒
- 日志级别:ERROR, WARN, INFO, DEBUG or TRACE
- 进程ID
- 分隔符:— 标识实际日志的开始
- 线程名:方括号括起来(可能会截断控制台输出)
- Logger名:通常使用源代码的类名
- 日志内容
11.2. 添加依赖
<!-- 实际开发中,spring-boot-starter已经包含了 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
11.3. 属性配置
# LOGGING
debug= true #开启日志
logging.config= # 加载日志配置文件
logging.exception-conversion-word=%wEx # Conversion word used when logging exceptions.
logging.file= # 设置文件,可以是绝对路径,也可以是相对路径
logging.file.max-history=0 # Maximum of archive log files to keep. Only supported with the default logback setup.
logging.file.max-size=10MB # Maximum log file size. Only supported with the default logback setup.
logging.level.*= # *号为包名或Logger名,指定日志级别,如logging.level.com.kingsong= DEBUG
logging.path= # 设置目录
logging.pattern.console= # Appender pattern for output to the console. Supported only with the default Logback setup.
logging.pattern.dateformat=yyyy-MM-dd HH:mm:ss.SSS # Appender pattern for log date format. Supported only with the default Logback setup.
logging.pattern.file= # Appender pattern for output to a file. Supported only with the default Logback setup.
logging.pattern.level=%5p # Appender pattern for log level. Supported only with the default Logback setup.
logging.register-shutdown-hook=false # Register a shutdown hook for the logging system when it is initialized.
11.4. 使用日志
private Logger logger = LoggerFactory.getLogger(this.getClass());
11.5. logback-spring.xml
12. 单元测试
12.1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
12.2. Service测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void findOneTest() {
User user = userService.findOne("001");
// 断言
Assert.assertEquals(new String("001"),user.getSex());
}
}
12.3. Controller测试
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mvc;
private MockHttpSession session;
@Before
public void setupMockMvc() {
User user = new User();
session.setAttribute("user",user);
}
@Test
public void userList() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/users")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.session(session))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value(200))
.andDo(MockMvcResultHandlers.print());
}
}
12.4. 新断言assertThat
- 语法:
assertThat( [value], [matcher statement] )
- 样例
import org.hamcrest.CoreMatchers.*;
/**字符相关匹配符*/
/**equalTo匹配符断言被测的testedValue等于expectedValue,
* equalTo可以断言数值之间,字符串之间和对象之间是否相等,相当于Object的equals方法
*/
assertThat(testedValue, equalTo(expectedValue));
/**equalToIgnoringCase匹配符断言被测的字符串testedString
*在忽略大小写的情况下等于expectedString
*/
assertThat(testedString, equalToIgnoringCase(expectedString));
/**equalToIgnoringWhiteSpace匹配符断言被测的字符串testedString
*在忽略头尾的任意个空格的情况下等于expectedString,
*注意:字符串中的空格不能被忽略
*/
assertThat(testedString, equalToIgnoringWhiteSpace(expectedString);
/**containsString匹配符断言被测的字符串testedString包含子字符串subString**/
assertThat(testedString, containsString(subString) );
/**endsWith匹配符断言被测的字符串testedString以子字符串suffix结尾*/
assertThat(testedString, endsWith(suffix));
/**startsWith匹配符断言被测的字符串testedString以子字符串prefix开始*/
assertThat(testedString, startsWith(prefix));
/**一般匹配符*/
/**nullValue()匹配符断言被测object的值为null*/
assertThat(object,nullValue());
/**notNullValue()匹配符断言被测object的值不为null*/
assertThat(object,notNullValue());
/**is匹配符断言被测的object等于后面给出匹配表达式*/
assertThat(testedString, is(equalTo(expectedValue)));
/**is匹配符简写应用之一,is(equalTo(x))的简写,断言testedValue等于expectedValue*/
assertThat(testedValue, is(expectedValue));
/**is匹配符简写应用之二,is(instanceOf(SomeClass.class))的简写,
*断言testedObject为Cheddar的实例
*/
assertThat(testedObject, is(Cheddar.class));
/**not匹配符和is匹配符正好相反,断言被测的object不等于后面给出的object*/
assertThat(testedString, not(expectedString));
/**allOf匹配符断言符合所有条件,相当于“与”(&&)*/
assertThat(testedNumber, allOf( greaterThan(8), lessThan(16) ) );
/**anyOf匹配符断言符合条件之一,相当于“或”(||)*/
assertThat(testedNumber, anyOf( greaterThan(16), lessThan(8) ) );
数值相关匹配符
/**closeTo匹配符断言被测的浮点型数testedDouble在20.0¡À0.5范围之内*/
assertThat(testedDouble, closeTo( 20.0, 0.5 ));
/**greaterThan匹配符断言被测的数值testedNumber大于16.0*/
assertThat(testedNumber, greaterThan(16.0));
/** lessThan匹配符断言被测的数值testedNumber小于16.0*/
assertThat(testedNumber, lessThan (16.0));
/** greaterThanOrEqualTo匹配符断言被测的数值testedNumber大于等于16.0*/
assertThat(testedNumber, greaterThanOrEqualTo (16.0));
/** lessThanOrEqualTo匹配符断言被测的testedNumber小于等于16.0*/
assertThat(testedNumber, lessThanOrEqualTo (16.0));
集合相关匹配符
/**hasEntry匹配符断言被测的Map对象mapObject含有一个键值为"key"对应元素值为"value"的Entry项*/
assertThat(mapObject, hasEntry("key", "value" ) );
/**hasItem匹配符表明被测的迭代对象iterableObject含有元素element项则测试通过*/
assertThat(iterableObject, hasItem (element));
/** hasKey匹配符断言被测的Map对象mapObject含有键值“key”*/
assertThat(mapObject, hasKey ("key"));
/** hasValue匹配符断言被测的Map对象mapObject含有元素值value*/
assertThat(mapObject, hasValue(value));
12.5. 相关注解
注解 | 说明 |
---|---|
@Transactional | 开启事务功能,测试执行完后进行回滚 |
@Rollback(false) | 关闭回滚 |
- 【注意】
- 执行
mvn clean package
时,自动执行单元测试 mvn clean package -Dmaven.test.skip=true
可跳过单元测试- 如果使用的数据库是Mysql,有时候会发现加了注解@Transactional 也不会回滚,要查看一下你的默认引擎是否是InnoDB,如果不是就要改成InnoDB。
- 【区别】
- MyISAM:MyISAM是MySQL5.5之前版本默认的数据库存储引擎。MYISAM提供高速存储和检索,以及全文搜索能力,适合数据仓库等查询频繁的应用。但不支持事务、也不支持外键。MyISAM格式的一个重要缺陷就是不能在表损坏后恢复数据。
- InnoDB:InnoDB是MySQL5.5版本的默认数据库存储引擎,不过InnoDB已被Oracle收购,MySQL自行开发的新存储引擎Falcon将在MySQL6.0版本引进。InnoDB具有提交、回滚和崩溃恢复能力的事务安全。但是比起MyISAM存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。尽管如此,但是InnoDB包括了对事务处理和外来键的支持,这两点都是MyISAM引擎所没有的。
- 执行
13. 热部署
13.1. spring-boot-devtools实现热部署
【特性】
- 默认值设置
- 自动重启
- livereload
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
- #### 属性配置
#devtools默认关闭所有模版缓存
#排除静态资源,静态资源无需重启
spring.devtools.restart.exclude=static/**,public/**
#如果想保留默认设置,同时增加新设置
spring.devtools.restart.additional-exclude属性
#监视额外路径(classpath:/路径外)
spring.devtools.restart.additional-paths=
#关闭自动重启
spring.devtools.restart.enabled= false
#关闭LiveReload服务器
spring.devtools.livereload.enabled= false
自动重启原理
自动重启的原理在于spring boot使用两个classloader:不改变的类(如第三方jar)由base类加载器加载,正在开发的类由restart类加载器加载。应用重启时,restart类加载器被扔掉重建,而base类加载器不变,这种方法意味着应用程序重新启动通常==比“冷启动”快得多==,因为base类加载器已经可用并已填充
自动重启触发
当开启devtools后,classpath中的文件变化会导致应用自动重启。
不同的IDE效果不一样,Eclipse中保存文件即可引起classpath更新(注:需要打开自动编译),从而触发重启。而IDEA则需要自己手动CTRL+F9重新编译一下LiveReload
DevTools内置了一个LiveReload服务,可以在资源变化时用来触发浏览器刷新。需要浏览器安装了LiveReload插件,并且启动这个插件
14. Swagger UI开发接口文档
添加依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.2.2</version>
</dependency>
创建Swagger2配置类
在Application.java同级创建Swagger2的配置类
@Configuration
@EnableSwagger2
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.didispace.web"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Spring Boot中使用Swagger2构建RESTful APIs")
.description("更多Spring Boot相关文章请关注:http://blog.didispace.com/")
.termsOfServiceUrl("http://blog.didispace.com/")
.contact("程序猿DD")
.version("1.0")
.build();
}
}
访问
【注意】springboot配置swagger要在application.yml中注释掉static-location
相关注解
注解 | 说明 |
---|---|
@Api() | 注释接口,用于类 |
@ApiOperation() | 用于方法 |
@ApiParam() | |
@ApiModel() | |
@ApiModelProperty() | |
@ApiIgnore() | |
@ApiImplicitParam() | |
@ApiImplicitParams() | |
@ApiResponse |
15. JavaMailSender
15.1. maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
15.2. 属性配置
spring.mail.host=smtp.qq.com
spring.mail.username=用户名
spring.mail.password=密码
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
15.3. 测试用例
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {
@Autowired
private JavaMailSender mailSender;
@Test
public void sendSimpleMail() throws Exception {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("dyc87112@qq.com");
message.setTo("dyc87112@qq.com");
message.setSubject("主题:简单邮件");
message.setText("测试邮件内容");
mailSender.send(message);
}
}
16. Spring Security
16.1. maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
16.2. WebSecurityConfig配置
@Configuration
@EnableWebSecurity // 开启Spring Security功能
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll() // 不需认证的URL
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login") // 需要登录时跳转的URL
.permitAll()
.and()
.logout()
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication() .withUser("user").password("password").roles("USER");
}
}
17. RestTemplate
java访问url
配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* RestTemplate配置类
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);//单位为ms
factory.setConnectTimeout(5000);//单位为ms
return factory;
}
}
使用
在需要访问url的类中注入
@Autowired
private RestTemplate restTemplate;
- 发送Get请求
//get json数据
JSONObject json = restTemplate.getForEntity(url, JSONObject.class).getBody();
- 发送Post请求
//post json数据
JSONObject postData = new JSONObject();
postData.put("data", "request for post");
JSONObject json = restTemplate.postForEntity(url, postData, JSONObject.class).getBody();
- 设置请求头
//post json string data
//return string
HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(type);
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
JSONObject jsonObj = JSONObject.parseObject(paras);
HttpEntity<String> formEntity = new HttpEntity<String>(jsonObj.toString(), headers);
String result = restTemplate.postForObject(url, formEntity, String.class);
18. FreeMarker
- ### 概述
- ### 原理
指令
取值
- 基本类型
${var}
- Date类型格式化
${date?String('yyyy-MM-dd')}
- boolean类型
${boolean?String('yes','no')
- 转义html
${var?html}
- 封装对象
${User.name}
- 为null或不存在的对象
${var!'默认值'}
- 集合类型
- list
<#list myList as item> ${item} </#list>
- map
<#list map?keys as key> ${key}:${map[key]} </#list>
- 基本类型
定义
<#assign expression />
条件判断
// if elseif else
<#if var == 100> // 相等
...
<#elseif var > 100> // 大于
...
<#elseif var??> // 是否存在或为null,也可<#elseif var?exists>
...
<#else>
...
</#if>
// switch
<#switch var>
<#case 1>
...
<#break>
<#case 2>
...
<#break>
<#default>
...
</#switch>
- 字符串操作
- 连接
${str1 + str2}
- 截取
${str1?substring(from,to)}
- 长度
${str1?length}
- 大写
${str1?upper_case}
- 小写
${str1?lower_case}
- index_of
${str1?index_of('w')}
- last_index_of
${str1?last_index_of('w')}
- replace
${str1?replace('source','replace')}
- 连接
- 自定义函数
public class SortMethod implements TemplateMethodModeEx {
@Override
public Object exec(List arguments) throws TemplateModelException {
// 获取第一个参数
SimpleSequence arg0 = (SimpleSequence)arguments.get(0);
List<BigDecimal> list = arg0.toList();
Collections.sort(list, new Comparator<BigDecimal>(){
@Override
public int compare(BigDecimal o1, BigDecimal o2) {
return o1.intValue() - o2.intValue(); // 升序
}
});
return list;
}
}
@Controller
public class myController {
@RequestMapping(value="/list")
public ModelAndView index() {
ModelAndView mv = new ModeAndView("index");
mv.addObject("sort_int",new SortMethod());
return mv;
}
}
// index.ftl
<html>
<body>
<#assign myList=[1,2,3,4,5,6] />
<ul>
<#list sort_int(myList) as item>
${item},
</#list>
</ul>
</body>
</html>
- 【注意】
- freemarker不支持boolean类型输出true/false
- freemarker不支持输出java.util.Date,只支持java.sql.Date