Springboot学习笔记

这篇博客详细介绍了SpringBoot的学习过程,包括Maven配置、属性配置、Thymeleaf模板引擎的使用,以及Spring Data JPA、AOP、日志、单元测试、热部署等多个核心模块的配置和原理。同时,还探讨了Spring Security、RestTemplate和FreeMarker等技术的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



目录


1. 学习网址


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
  1. ### application.yml[推荐]
server:
    port: 8080
    context-path: /projectName
  1. 多环境配置

    • resources
    • application.yml
    • application-dev.yml[开发环境]
    • application-prod.yml[生产环境]
#application.yml->dev
spring:
    profiles:
        active: dev

#application.yml->prod
spring:
    profiles:
        active: prod
  1. 自定义配置

    • 可在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;
}
  1. ### 相关注解

注解说明
@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() {

    }
}
  1. ==@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)
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection ages)… where x.age not in ?1
TruefindByActiveTrue()… where x.active = true
FalsefindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)
DistinctfindDistinctByName(String name)select distinct * from … where x.name=?1

8.6. 相关注解

注解说明
@Qualifier()区分传入相同类别参数
@PageableDefault()设置分页参数的默认值,如page,size

9. 事务管理

9.1. 相关注解

注解说明
@Transactional注解方法内的操作呈事务性,同时成功或者同时不执行

10. AOP

10.1. AOP的初衷

  1. DRY:Don’t Repeat Yourself
  2. Soc:Separation of Concerns
    1. 水平分离:展示层->服务层->持久层
    2. 垂直分离:模块划分(订单、库存等)
    3. 切面分离:分离功能性需求与非功能性需求

10.2. 应用场景

权限控制、缓存控制、事务控制、审计日志、性能监控、分布式追踪、异常处理

10.3. 原理概述

  • 织入时机
    1. 编译期(AspectJ)
    2. 类加载时(AspectJ 5+)
    3. 运行时(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级别输出到控制台
    SpringBoot_Log
  • 从上图可以看到,日志输出内容元素具体如下:
    • 时间日期:精确到毫秒
    • 日志级别: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();
    }

}

注解说明
@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

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 &gt; 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值