继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Spring Boot 简易使用指南

缥缈止盈
关注TA
已关注
手记 154
粉丝 34
获赞 148

文/ZYRzyr
原文链接:http://www.jianshu.com/p/d8fdd6efe2cb

前言

  本文将介绍Spring Boot的使用。
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。简而言之,使用Spring Boot,将极大的提高开发效率。
阅读本文需要熟悉以下技术:

  • Spring(必须)

  • MySQL(必须)

  • Maven(必须)

  • Hibernate(非必须)

  接下来,将使用IntelliJ IDEA创建一个Spring Boot工程,实现简单的增删改查功能,该工程包含以下几点内容:

  • 项目属性配置

  • Controller的使用

  • 数据库操作

  • 事务管理

  • AOP

  • 统一异常处理

  • 单元测试


创建工程

IntelliJ IDEA新建一个工程,选择如下图所示,点击Next(IDEA企业版才有此选项):

https://img.mukewang.com/5d5aa3e30001dd9f08950689.png

new project.png


Next后,输入项目信息之后,进入如下界面,选择如图选项后,一直Next到最后Finish


https://img3.mukewang.com/5d5aa3e70001913808760685.png

Web.png

Finish后,需要等待Maven下载相关依赖,此时可见如下目录结构:

https://img4.mukewang.com/5d5aa3ec00016b6205940578.png

目录.png


  删除其中选中的5项无用的文件,并将目录中的application.properties重命名为application.yml,此时,application.yml即为整个工程的配置文件,并且只有这一个。
  因为Spring Boot内置了Tomcat,所以无需额外进行配置,直接按快捷键Shift + F10即可运行此项目,在浏览器输入http://localhost:8080/,跳转后看见如下错误页面,则说明该工程已运行成功:

https://img.mukewang.com/5d5aa3f300010d3208670249.png

error page.png



依赖库

  本项目中要使用到JPAMySQLAOP,所以要在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>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

配置

  在实际的项目开发中,通常会有开发环境,测试环境,生产环境等,Spring Boot针对这点,也有很好的支持:

1.resources文件夹下新建两个文件:application-dev.ymlapplication-pro.yml,前者表示开发环境,后者表示生产环境。

2.application-dev.ymlapplication-pro.yml中就可写与各自环境相关的配置,如端口号,数据库等等。
application-dev.yml配置如下(注意格式):

server:
  port: 8081    //端口为8081spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver     //使用MySQL
    url: jdbc:mysql://localhost:3306/book_dev?useSSL=false   //使用开发用数据库book_dev
    username: root
    password: 123456

  jpa:
    hibernate:
      ddl-auto: create-drop           //自动生成数据表的方式
    show-sql: true

application-pro.yml配置如下(注意格式):

server:
  port: 8082     //端口为8081spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver     //使用MySQL
    url: jdbc:mysql://localhost:3306/book?useSSL=false    //使用开发用数据库book_dev
    username: root
    password: 123456

  jpa:
    hibernate:
      ddl-auto: update     //自动生成数据表的方式
    show-sql: true

3.此时需要在原来的application.yml文件中,指定工程启动时,运行哪个环境的配置文件,代码如下(注意格式):

spring:
  profiles:
    active: dev    //表示加载application-dev.yml中的配置//这里还可以写所有环境通用的配置

正式开始

  接下来将实现一个功能:提供一个可用的POST请求路径,输入参数namebook,提交成功后加入数据库中。
1.创建实体类User

import javax.persistence.*;import javax.validation.constraints.NotNull;import java.io.Serializable;@Entity    //表示该类是一个实体类,由于之前的配置中写了 ddl-auto,jpa会将类名作为表名自动生成表@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"name", "book"})})//表示同一行name和book作为唯一约束public class User implements Serializable {    @Id  //主键约束
    @GeneratedValue  //自增
    private Integer id;    @NotNull
    private String name;    @NotNull
    private String book;    public User() {
    }    public User(Integer id) {        this.id = id;
    }    public User(String name, String book) {        this.name = name;        this.book = book;
    }    public Integer getId() {        return id;
    }    public void setId(Integer id) {        this.id = id;
    }    public String getName() {        return name;
    }    public void setName(String name) {        this.name = name;
    }    public String getBook() {        return book;
    }    public void setBook(String book) {        this.book = book;
    }    @Override
    public String toString() {        return "User{" +                "id=" + id +                ", name='" + name + '\'' +                ", book='" + book + '\'' +                '}';
    }
}

2.创建数据库操作接口UserRepository:

import com.zyr.book.domain.User;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;import java.util.List;@Repository   public interface UserRepository extends JpaRepository<User, Integer> { //继承JpaRepository后,可直接使用其已有的方法,类似hibernate,且可在本类中自定义自己的操作。
    List<User> findByName(String name);
}

3.创建service接口UserService

import com.zyr.book.domain.User;public interface UserService {    User insertUser(String name, String book);
}

4.实现接口UserService,完成业务逻辑:

import com.zyr.book.domain.User;import com.zyr.book.enums.ApiErrorType;import com.zyr.book.exception.UserException;import com.zyr.book.repository.UserRepository;import com.zyr.book.service.UserService;import com.zyr.book.util.TextUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;@Transactional   //事物管理注解,当数据库操作失败后,自动回滚@Service("userService")public class UserServiceImpl implements UserService {    private UserRepository userRepository;    @Autowired     //使用构造器注入方式
    public UserServiceImpl(UserRepository userRepository) {        this.userRepository = userRepository;
    }    @Override
    public User insertUser(String name, String book) {        if (TextUtil.isEmpty(name)) {            throw new UserException(ApiErrorType.NULL_NAME);  //名字为空,抛出异常
        }        if (TextUtil.isEmpty(book)) {            throw new UserException(ApiErrorType.NULL_BOOK); //书籍为空,抛出异常
        }        return userRepository.save(new User(name, book)); 
    }
}

此处的错误处理均使用抛异常的方式,方便之后的统一异常处理
其中UserException如下:

import com.zyr.book.enums.ApiErrorType;public class UserException extends RuntimeException { //必须继承RuntimeException,在统一异常处理时才能被捕获

    private ApiErrorType apiErrorType;    public UserException(ApiErrorType apiErrorType) {        super(apiErrorType.getMessage());        this.apiErrorType = apiErrorType;
    }    public ApiErrorType getApiErrorType() {        return apiErrorType;
    }    public void setApiErrorType(ApiErrorType apiErrorType) {        this.apiErrorType = apiErrorType;
    }
}

其中ApiErrorType如下:

public enum ApiErrorType {
    UNKNOWN_ERROR("服务器异常"),
    EMPTY_BOOK("该用户没有书籍"),
    EMPTY_USER("无此用户"),
    NULL_NAME("姓名不能为空"),
    NULL_BOOK("书籍不能为空"),
    DUPLICATED_BOOK("该用户已有此书");    private String message;

    ApiErrorType(String message) {        this.message = message;
    }    public String getMessage() {        return message;
    }
}

5.定义Controller

import com.zyr.book.domain.User;import com.zyr.book.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class UserController {    private UserService userService;    @Autowired   //构造器注入方式
    public UserController(UserService userService) {        this.userService = userService;
    }    @PostMapping("/add")    public User add(User user) throws Exception {    //注意这里,方法抛出异常
        return userService.insertUser(user.getName(), user.getBook());
    }
}
  • 类上的@RestController,表示该类中的方法返回值,会自动转换成JSON格式;

  • 方法上的@PostMapping("/add"),表示此方法仅支持POST方式,相应的还有@GetMapping@PutMapping@DeleteMapping等等RESTful API的请求方式,括号里的add表示请求路径,此处的请求路径即为:http://localhost:8081/add

  • 方法上直接抛出异常,方便之后的统一异常处理


测试

  以上5步,已经完成了基本的业务代码,现在有3中方式进行测试刚才所写的功能是否有效:

  • 单元测试

  • IDEA自带的测试工具

  • 编写客户端调用该API

此处介绍前两种测试方式:

1.单元测试
  鼠标选中UserServiceImpl中的insertUser方法,右键—>Go To—>Test—>Create New Test...,IDEA自动创建测试类,在其中加入测试用例如下:

@RunWith(SpringRunner.class)@SpringBootTestpublic class UserServiceImplTest {    @Autowired
    private UserService userService;    @Test
    public void insertUser() throws Exception {
        User user = userService.insertUser("Bob", "书");
        assertEquals("Bob", user.getName());
        assertEquals("书", user.getBook());
    }
}

   同样的方式创建UserController类中add方法的测试用例:

import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import org.springframework.test.web.servlet.MockMvc;import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;import org.springframework.test.web.servlet.result.MockMvcResultMatchers;@RunWith(SpringRunner.class)@SpringBootTest@AutoConfigureMockMvc  public class UserControllerTest {    @Autowired
    private MockMvc mockMvc;  //模拟网络请求

    @Test
    public void testAdd() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.post("/add")
                .param("name", "用户A")
                .param("book", "一本书A"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content()
                        .string("{\"id\":1,\"name\":\"用户A\",\"book\":\"一本书A\"}"))
        ;
    }
}

创建完成之后,分别运行测试用例即可。

2.IDEA自带的测试工具
  打开IDEA上面的 Tools—>Test RESTful Web Service,打开之后如下图:

https://img2.mukewang.com/5d5aa40100012f4609050176.png

REST Client.png


其中HTTP method选择POSTHost/port输入http://localhost:8081Path输入add,切换到RequestRequest Parameters中输入参数,如下图:

https://img1.mukewang.com/5d5aa4050001c61c09000152.png

POST.png


输入完成之后,点击左侧第一个绿色的三角形按钮,即可完成一次POST模拟请求,请求成功后以JSON返回输入的参数:


{"id":1,"name":"Bob","book":"书"}

此时若是再此执行相同一次上面的操作,则会返回如下错误信息:

{    "timestamp":1510211138423,    "status":500,    "error":"Internal Server Error",    "exception":"org.springframework.dao.DataIntegrityViolationException",    "message":"could not execute statement; SQL [n/a]; constraint [UKl7798etvxnmv3iq4thmund7us]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",    "path":"/add"}

  返回这种错误信息的原因,就是前面在UserController里面的add方法直接抛出了异常。乍一看好像没问题,状态码是500说明服务器有问题,真实原因确实也是因为无法插入重复的数据出的错,但仔细一想,这样的错误信息,会让客户端产生误解,以为是服务器现在有问题而暂时无法使用,只能傻傻的等待,但其实是客户端的参数不对造成的误解,所以此处的状态码应该返回以4开头的4XX,来说明是客户端请求有误,并且要让客户端能一目了然的知道是什么地方出了错,并且这样做,也符合RESTful API的风格,由此,便引入了Spring Boot中的统一异常处理


统一异常处理

1.新建类Error,保存错误信息:

public class Error {    private String message;    public Error() {
    }    public Error(String message) {        this.message = message;
    }    public String getMessage() {        return message;
    }    public void setMessage(String message) {        this.message = message;
    }    @Override
    public String toString() {        return "Error{" +                "message='" + message + '\'' +                '}';
    }
}

2.新建类GlobalExceptionHandler,捕获所有异常:

import com.zyr.book.domain.Error;import com.zyr.book.enums.ApiErrorType;import com.zyr.book.exception.UserException;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.dao.DataIntegrityViolationException;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.HttpRequestMethodNotSupportedException;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvicepublic class GlobalExceptionHandler {    private final static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);    @ResponseBody    //此注解表示:以JSON形式返回数据
    @ExceptionHandler
    public ResponseEntity<Error> handle(Exception e) {  //方法名可随意
        if (e instanceof UserException) {   //捕获service中主动抛出的异常,并且获取其中的错误信息以返回给客户端
            return new ResponseEntity<>(new Error(((UserException) e).getApiErrorType().getMessage()),
                    HttpStatus.BAD_REQUEST);
        } else if (e instanceof DataIntegrityViolationException) {   //捕获数据库操作异常,此处为违反唯一性约束
            return new ResponseEntity<>(new Error(ApiErrorType.DUPLICATED_BOOK.getMessage()),
                    HttpStatus.FORBIDDEN);
        } else if (e instanceof HttpRequestMethodNotSupportedException) { //捕获请求方法异常
            return new ResponseEntity<>(new Error(e.getLocalizedMessage()),
                    HttpStatus.METHOD_NOT_ALLOWED);
        } else {
            logger.error("【系统异常】", e);            return new ResponseEntity<>(new Error(ApiErrorType.UNKNOWN_ERROR.getMessage()),
                    HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

  可在GlobalExceptionHandler方法handle中的if...else if中加入想要捕获的异常,自定义其返回信息与状态码。最后的else中,打印出了未捕获的异常,以方便Fix bug。
  以上完成之后,重启项目,在REST Client中重新提交两次相同的POST请求后,第二次即返回错误信息{"message":"该用户已有此书"}。如果用Jquery ajax进行访问,则需要在ajax的回调函数error中写自己的失败响应逻辑。


AOP

AOPAspect Oriented Programming,翻译为面向切面编程,可以简单的理解为:在一些方法的执行过程中,统一指定一个地方并做一些额外的事。比如方法A,B,C,可以在它们开始执行前、执行后、返回之后等等时候做一些统一的处理。接下来,以打印请求信息为例说明。
  新建类HttpAspect

import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;@Aspect   //此注解说明该类是一个切面类@Componentpublic class HttpAspect {    private static final Logger logger = LoggerFactory.getLogger(HttpAspect.class);    @Pointcut("execution(public * com.zyr.book.controller..*.*(..))")  //切入点,execution内的内容即为想要切入的地方,类似于正则匹配,此处切入的地方是com.zyr.book.controller包内的所有public方法
    public void log() {
    }    @Before("log()")  //直接调用上面定义的切入点log,表示在com.zyr.book.controller包内的所有public方法执行前,先执行此方法内的内容,即打印请求信息
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        logger.info("-------------------------------Request Log Begin----------------------------------------------");
        logger.info("url={}", request.getRequestURL());
        logger.info("method={}", request.getMethod());
        logger.info("ip={}", request.getRemoteAddr());
        logger.info("class_method={}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        logger.info("args={}", joinPoint.getArgs());
        logger.info("-------------------------------Request Log End------------------------------------------------");
    }    @AfterReturning(returning = "object", pointcut = "log()") //直接调用上面定义的切入点log,表示在com.zyr.book.controller包内的所有public方法执行完成并返回后,执行此方法的内容,即打印方法的返回值
    public void doAfterReturning(Object object) {        if (object != null) {
            logger.info("response={}", object.toString());
        }
    }
}

以上完成之后,重启项目,在REST Client中提交一次POST请求,即可在控制台看见打印的信息:

https://img.mukewang.com/5d5aa40c0001b90e08760245.png

log.png



跨域问题

  如果使用Jquery ajax进行访问,可能会有跨域问题,如果有,需要在新建工程时,自动生成的XXApplication,即有一个main方法的那个类(本Demo的是BookApplication)里加入跨域过滤器:

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());        return new CorsFilter(source);
    }    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");        return corsConfiguration;
    }

完整Demo地址

ZYRzyr的GitHub地址 = Spring Boot 简易使用指南
欢迎Start,Follow!





打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP