VS Code 开发Spring Boot 之基础开发(一)

IDEA事实上的开发工具,但平时自己业余时间其实有更好的选择,IDEA消耗的内存非常高,内存羞涩的人可以考虑更轻量的VS Code,足以囊括平时开发需要的功能,启动也快,更重要是它免费!(IDEA现在也不贵,但有公司免费用,肯定用公司嘛)

1、环境配置

首先离不开环境,JDK已经默认大家拥有

之后安装以下Extension即可

Spring Boot Dashboard
Language Support for Java(TM) by Red Hat
Spring Boot Extension Pack
Spring Boot Tools
Spring Initializr Java Support

前面两个是必须的,剩下的可以选装,装了有更多功能

剩下的插件会自动配置好,像JDK Path这些(MacOS)。

2、开发

调用VS Code命令行,输入spring,选择Spring Initializer: Generate a Maven Project,之后一路选择熟悉的选项,pom.xml里面的依赖选择常用的即可,反正最后都可以编辑,而且这编辑是我用过最舒服的,搜索好选择添加即可,不用手动添加;即使你建立工程后再度添加也是同样选择,这里除了常规选择,我加了H2 Database

最后建立在任一文件地址中,最后如图所示,是不是和IDEA差别不大?

有工程目录,有Dependencies,有Maven,最后有个Spring-Boot DashBoard方便启动,我们接着在java/com/example/demo下创建新的package,实际上创建文件夹,然后按照平常开发那样创建controller也有相同效果

@RestController
public class HelloController {
    
    @RequestMapping("/yes")
    public String sayYes() {
        return "Yes, I am Hsucy";
    }

}

然后MAVEN PROJECTS那里管理运行clean、package命令,再在SRPING-BOOT DASHBOARD右键点击运行即可,甚至可以方便在Application.java运行

一个初步的框架出来,如果开发个人项目,VS code非常方便,但公司项目还是使用IDEA好,毕竟老代码多,多人协作需要Review。

3、集成测试Mvc和HTTP Call

Mvc测试老规矩,没什么好变化,如果写了几个案例,可以照抄,平常用得多是@Before和@After注解

@SpringBootTest
@WebAppConfiguration
public class HelloControllerTest {

    private MockMvc mvc;

    @Test
    public void getYes() throws Exception {
        mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();

        mvc.perform(MockMvcRequestBuilders.get("/yes").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
                .andExpect(content().string(equalTo("Yes, I am Hsucy")));

    }

}

运行成功如下有提示

失败则是很明显提示

经典的Expected but was

HTTP Call这块则可以不用postman,使用Rest Client插件,更加方便。

创建sample.http,.http后缀文件

###Yes API Test
GET http://localhost:8080/yes  HTTP/1.1

更多用法插件内有说明,然后点击Send Request即可,有个Response的页面弹出,内容类似如下

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 15
Connection: close

Yes, I am Hsucy

4、日志管理

经典的logback.xml文件搞定

<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="false">
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="/log" />
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="FILE" />
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

5、业务

平常还是业务处理多,架构方面我等小虾还是只能在外面观望修补,利用内存做存储,就不使用数据库,原理一样

@RestController
@RequestMapping(value = "/api/car")
public class CarController {

    private static Map<String, Car> CarMap = new HashMap<>();

    static {
        Car toyota = new Car();
        toyota.setId("1");
        toyota.setName("toyata");
        toyota.setAge(1);
        toyota.setType("A");
        CarMap.put(toyota.getId(), toyota);

        Car honda = new Car();
        honda.setId("2");
        honda.setName("honda");
        honda.setAge(2);
        honda.setType("B");
        CarMap.put(honda.getId(), honda);
    }

    @GetMapping(value = "")
    public ResponseEntity<Object> getCar() {
        return new ResponseEntity<>(CarMap.values(), HttpStatus.OK);
    }

    @GetMapping(value = "/{carId}")
    public ResponseEntity<Object> getCarDetail(@PathVariable String carId) {
        return new ResponseEntity<>(CarMap.get(carId), HttpStatus.OK);
    }

    @PostMapping(value = "/create")
    public ResponseEntity<Object> addCar(@RequestBody Car car) {
        CarMap.put(car.getId(), car);
        return new ResponseEntity<>("Car create successfully", HttpStatus.CREATED);
    }

    @PutMapping(value = "/{carId}")
    public ResponseEntity<Object> updateCar(@PathVariable String carId, @RequestBody Car car) {
        CarMap.remove(carId);
        car.setId(carId);
        CarMap.put(carId, car);
        return new ResponseEntity<>("Car update successfully", HttpStatus.OK);
    }

    @RequestMapping(value = "/{carId}", method = RequestMethod.DELETE)
    public ResponseEntity<Object> deleteCar(@PathVariable String carId) {
        CarMap.remove(carId);
        return new ResponseEntity<>("Car delete successfully", HttpStatus.OK);
    } 
}

sample.http案例

###Yes API 
GET  http://localhost:8080/yes HTTP/1.1

###Car API
GET  http://localhost:8080/api/car HTTP/1.1

###Car API Detail
GET http://localhost:8080/api/car/1 HTTP/1.1

###Car API Detail
GET http://localhost:8080/api/car/2 HTTP/1.1

###Car API Detail
GET http://localhost:8080/api/car/3 HTTP/1.1

###Car API Detail Create
POST http://localhost:8080/api/car/create HTTP/1.1
Content-Type: application/json

{
    "id": "3",
    "name": "nissan",
    "type": "C",
    "age": 4
}

###Car API Detail Update
PUT http://localhost:8080/api/car/1 HTTP/1.1
Content-Type: application/json

{
    "id": "1",
    "name": "toyota",
    "type": "C",
    "age": 1
}

###Car API Detail Delete
DELETE  http://localhost:8080/api/car/1 HTTP/1.1

6、异常,拦截,过滤

三者都是保证程序健壮性的关键功能

异常Exception

现实开发中Exception处理少不了,有时需要对Exception做一些特殊处理,返回不同的内容,这种情况一般使用AOP来解决,先创建一个异常类,从这个异常类出发都统一抛出错误

CarNotFoundException.java

public class CarNotFoundException extends RuntimeException {
    private static final long serialVersionUID = 1L;
}

之后异常抛出处理

CarExceptionController.java

import com.example.demo.exception.CarNotFoundException;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class CarExceptionController {

    @ExceptionHandler(value = CarNotFoundException.class)
    public ResponseEntity<Object> exception(CarNotFoundException carNotFoundException) {
        return new ResponseEntity<>("Car not found", HttpStatus.NOT_FOUND);
    }

}

在Controller对异常做抛出,使用AOP处理

CarController.java

@GetMapping(value = "/{carId}")
public ResponseEntity<Object> getCarDetail(@PathVariable String carId) {
        if (!CarMap.containsKey(carId)) {
            throw new CarNotFoundException();
        }
        return new ResponseEntity<>(CarMap.get(carId), HttpStatus.OK);
 }

就这样简单完成一个异常处理,可以为每种情况做特殊处理,但很多公司开发其实还是简单粗暴。

拦截Interceptor

拦截和过滤常常混淆,使用上也比较相似,但两者一个是Interceptor,一个是Filter,从英文看就看出区别了吧,前者是加法,后者是减法,一个处理数据,一个是过滤不正常的数据,拦截用得比较多是在鉴权或者要对你想对某个流程做特殊处理,比如增强什么功能。真要说,两者功能比较类似,Filter更加广泛,囊括Interceptor都行

CarInterceptor.java

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component
public class CarInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("pre Handle");
        return true;
        // return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("post Handle");
        // HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("after Handle");
        // HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

}

CarInterceptorConfig.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Component
public class CarInterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private CarInterceptor carInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(carInterceptor);
        // WebMvcConfigurer.super.addInterceptors(registry);
    }

}

从这里可以看出核心是WebMvcConfigurer,把他们变成一个Bean,在里面注册我们想处理的Interceptor,可以精确到具体的Controller。

Filter

核心是javax.servlet.Filter

@Component
public class CarFilter implements Filter {

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("Host" + request.getRemoteHost());
        System.out.println("Addr" + request.getRemoteAddr());
        chain.doFilter(request, response);     
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }
}

实现这个接口,重写即可

Show Comments