第一个SpringBoot程序
2021-05-19 09:29
标签:lan 创建 增强 equal on() 异常捕获 spel alter cal 例子来自慕课网廖师兄的免费课程 2小时学会SpringBoot Spring Boot进阶之Web进阶 使用IDEA新建工程,选择SpringBoot Initializr,勾选Web一路next就搭建了一个最简单的SpringBoot工程。如下: @SpringBootApplication整合了三个常用的注解,分别是: SpringBoot的配置文件可以使用xml和yml格式,比如使用yml格式 可以使用注解 Spring提供了两种在运行时求值的方式: 如果cupSize和age都是属于同一类属性下的子属性,比如都属于girl。 那么可以写成下面的形式: 在java中注入时,也不用一个个属性注入,可以注入girl的全部属性。不过需要将girl的属性抽象成一个java类。 属性配置方式 环境配置: 可以建立多个application-xxx.yml文件,然后在application.yml文件中配置其中一个环境. 比如我有application-dev.yml文件表示开发环境下的配置文件,application-prod.yml文件表示生产环境下的配置文件。那么再按application.yml中配置如下 就表示使用application-dev.yml中的配置。 本例子使用JPA和MySQL,所以在pom中加入如下依赖 在application.yml中配置数据源和jpa相关。 JPA(Java Persistence API),即Java持久化API,Hibernate实现了这个规范。 @GeneratedValue有几种策略 Repository提供了最基本的数据访问功能,通过新建一个接口继承 泛型中的Girl表示该Repository可以访问由Girl映射的数据表,Integer表示ID的数据类型。 写一个Controller,处理各种请求来看JPA是如何与数据库交互的。 没有写一句SQL语句,就完成了对girl表的增删改查,用起来还是很舒服的。 下面的insertTwo方法插入两条数据,如果不进行事务管理,则插入girlA成功,插入girlB失败。加上@Transactional注解后(有两个同名注解,导入spring的),要么两条数据都插入成功,要么两条都插入失败。因为在本例中会出现异常,所以两条都插入失败。 因为Hibernate创建的表默认引擎是MyISAM,所以如果发现事务没有作用,要手动修改引擎为InnoDB。 在上面的例子中如果要对年龄作限制,比如小于18岁的girl不能添加。可以在实体类中对其中的字段属性使用注解来加以限制。 这句代码限制了girl的年龄不能低于18岁。在Controller中修改添加女生的逻辑 @Valid可以对对象进行验证,加了@Valid注解的参数,其后要紧跟着BindingResult或者Errors(前者是后者的实现类),用于保存验证结果。如果对象中有属性不满足验证条件,其结果将体现中BindingResult中。 首先在pom中添加依赖 然后编写切面 因为在方法调用的前后都要对相同的方法进行通知,为了避免代码冗余,把@Before和@After的execution表达式抽取成切点。 表示对GirlController中所有public的任意返回值、任意参数的方法进行通知。注意该注解需要用在方法上,所以 该切面使用了slf4j的日志。当请求http://localhost:8080/addGirl时,控制台输出以下日志,可以显示比 输出的 现在修改doBefore方法,使它能从Request域中获取请求url、IP地址、请求方法、请求中传递的参数。 在通知方法中可以声明一个JoinPoint类型的参数,通过JoinPoint可以访问连接点的细节。 还新增了一个@AfterReturning的通知,在方法成功返回后执行(若抛出异常将不会执行该通知),和@After的区别在于:被增强的方法不论是执行成功还是抛出异常,@After通知方法都会得到执行。 AOP中 @Before @After @AfterThrowing @AfterReturning的执行顺序如下: 可知@AfterReturning的执行在@After之后。 如果请求http://localhost:8080/addGirl,将输出以下日志(日志一些无关紧要的内容已被删除) 前面的addGirl方法,当验证不通过时,返回null并在控制台打印相关信息;当验证通过又返回Girl。返回值不统一,而且如果我们希望将错误信息显示在页面,怎么办呢? 可定义一个 再写一个工具类,可在成功和异常时候设置对应的状态和信息,可有效减少重复代码。 于是我们的addGirl方法可以重构成下面的样子 现在新增一个检查年龄的逻辑,小于14岁的认为在上小学,14~17岁认为在上初中,这两种情况都不允许其进入,当检查到年龄不符合要求时,抛出异常。 在GirlService中 注意上面使用枚举来统一管理各种code对应的msg。 GirlException是个自定义异常类,除了message还把code整合进去了。 在Controller中只是简单调用下Service中的方法而已 如果现在启动程序,请求http://localhost:8080/girlAge/22, 将按照自定义异常,但是返回的结果其格式是下面这样的: 因为系统内部发生了错误,不断往上抛异常就会得到上面的信息。如果要保持不管在什么情况下统一返回 则需要对异常做一个捕获,取出有用的message部分,然后再封装成Result对象,再返回给浏览器。为此新建一个异常捕获类 该类使用了注解@ControllerAdvice,@ControllerAdvice会作用在所有注解了@RequestMapping的控制器的方法上,再配合@ExceptionHandler,用于全局处理控制器里的异常。@ExceptionHandler(Exception.class)表示可以处理Exception类及其子类。 因为除了会抛出自定义异常GirlException外,还有可能因为系统原因抛出其他类型的异常(如空指针异常),因此针对不同类型的异常返回不同的状态码,上面使用了instanceof来判断异常类型。如果不是GirlException,被统一归类为未知错误,但是各种异常都显示未知错误不便于排查问题,因此在可控制台输出了异常原因来加以区分。 SpringBoot中进行单元测试十分便捷,SpringBoot中默认使用了Junit4。 在src/test下可以创建单元测试类,当然更简单的方法是在IDEA下右键,Go To -> Test Subject,然后选择想要进行测试的方法即可。 下面的单元测试针对service层,主要是判断某数据库中某id的girl,其年龄实际值和预期值是否一致。有两个比较关键的注解 然后针对Controller层,对某次请求进行测试,这里使用到了MockMvc。 第一条测试模拟以get方法请求 2018.10.4 第一个SpringBoot程序 标签:lan 创建 增强 equal on() 异常捕获 spel alter cal 原文地址:https://www.cnblogs.com/sun-haiyu/p/9742713.html第一个SpringBoot程序
package com.shy.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
配置文件相关
# 自定义属性
cupSize: b
age: 18
# 可以在yml里通过${}来引用
content: "cupSize: ${cupSize}, age: ${age}"
# 指定端口为8080,(不配置默认8080)
server:
port: 8080
@Value("${...}")
获取配置文件中的值,@Value和@Autowired注解作用类似。
${...}
#{...}
girl:
cupSize: b
age: 18
package com.shy.springboot.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 读取配置文件的信息并自动封装成实体类
* 注入SpringBoot配置文件中前缀是"girl"的全部属性
*/
@Component
@ConfigurationProperties(prefix = "girl")
public class GirlProperties {
private String cupSize;
private Integer age;
public String getCupSize() {
return cupSize;
}
public Integer getAge() {
return age;
}
}
spring:
profiles:
active: prod
一些常用注解
package com.shy.springboot.controller;
import com.shy.springboot.config.GirlProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/girl")
public class Hello {
/**
* 表示value中的值都可作为url路径,如果不指定请求方法method,那么GET和POST方式都可以,但是一般不推荐
*/
@RequestMapping(value = {"/hello", "/hi"}, method = RequestMethod.GET)
public String hello() {
return "Hello";
}
}
package com.shy.springboot.controller;
import com.shy.springboot.config.GirlProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/girl")
public class Hello {
// 可以响应 http://localhost:8080/girl/hello/xx
@RequestMapping(value = {"/hello/{id}"}, method = RequestMethod.GET)
public String hello(@PathVariable("id") Integer id) {
return "My id is " + id;
}
// 可以响应 http://localhost:8080/girl/hello?id=xx
// required = false表示这个参数可以为空,defaultValue表示当参数为空时的默认值,因此访问http://localhost:8080/girl/hello,将使用默认值1。
@RequestMapping(value = {"/hello"}, method = RequestMethod.GET)
public String hello2(@RequestParam(value = "id", required = false,defaultValue = "1") Integer id) {
return "My id is " + id;
}
}
数据库配置
spring:
profiles:
active: dev
# 以下使用了jpa和mysql
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/dbgirl
username: root
password: admin
jpa:
hibernate:
ddl-auto: create
show-sql: true
package com.shy.springboot.database;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Girl {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Integer age;
private String cupSize;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getCupSize() {
return cupSize;
}
public void setCupSize(String cupSize) {
this.cupSize = cupSize;
}
public Girl() {
}
}
jpa.hibernate.ddl-auto
,共有五种配置方式。
Controller和几个简单的请求
JpaRepository
,可以直接使用接口中现成的方法来实现对数据的访问。package com.shy.springboot.database;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface GirlRepo extends JpaRepository
package com.shy.springboot.controller;
import com.shy.springboot.database.Girl;
import com.shy.springboot.database.GirlRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
public class GirlController {
@Autowired
private GirlRepo girlRepo;
/**
* 查询所有女生
* @return
*/
@GetMapping("/girls")
public List
事务管理
// Service中
package com.shy.springboot.service;
import com.shy.springboot.database.Girl;
import com.shy.springboot.database.GirlRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class GirlService {
@Autowired
private GirlRepo girlRepo;
@Transactional
public void insertTwo() {
Girl girlA = new Girl();
girlA.setCupSize("B");
girlA.setAge(18);
girlRepo.save(girlA);
// 除0异常,退出
int a = 3 / 0;
Girl girlB = new Girl();
girlB.setCupSize("C");
girlB.setAge(20);
girlRepo.save(girlB);
}
}
// Controller中
@PostMapping("/girls/insertTwo")
public void insertTwo() {
girlService.insertTwo();
}
ALTER TABLE xxx ENGINE=INNODB;
表单验证
@Min(value = 18, message = "未满18岁不得入内!")
private Integer age;
/**
* 添加一个女生
* @return
*/
@PostMapping("/addGirl")
public Girl addGirl(@Valid Girl girl, BindingResult result) {
if (result.hasErrors()) {
System.out.println(result.getFieldError().getDefaultMessage());
return null;
}
return girlRepo.save(girl);
}
AOP
package com.shy.springboot.aspect;
import org.aspectj.lang.annotation.After;
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;
@Aspect
@Component
public class HttpAspect {
private static final Logger LOG = LoggerFactory.getLogger(HttpAspect.class);
@Pointcut("execution(public * com.shy.springboot.controller.GirlController.*(..))")
public void log() {}
@Before("log()")
public void doBefore() {
LOG.info("我在方法调用前执行");
}
@After("log()")
public void doAfter() {
LOG.info("我在方法调用后执行");
}
}
@Pointcut("execution(public * com.shy.springboot.controller.GirlController.*(..))")
public log() {}
在这里只是起一个标识作用,供@Pointcut依附,所以它的方法体是空的。System.out.println()
更详细的信息。2018-10-03 10:03:46.899 INFO 1892 --- [nio-8080-exec-3] com.shy.springboot.aspect.HttpAspect : 我在方法调用前执行
Hibernate: insert into girl (age, cup_size) values (?, ?)
2018-10-03 10:03:47.031 INFO 1892 --- [nio-8080-exec-3] com.shy.springboot.aspect.HttpAspect : 我在方法调用后执行
Hibernate: insert into girl (age, cup_size) values (?, ?)
表示了Controller中addGirl方法的执行,在其前后分别输出了@Before和@After执行的逻辑,所以AOP确实是生效了的。@Aspect
@Component
public class HttpAspect {
private static final Logger LOG = LoggerFactory.getLogger(HttpAspect.class);
@Pointcut("execution(public * com.shy.springboot.controller.GirlController.*(..))")
public void log() {}
@Before("log()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// url
LOG.info("url={}", request.getRequestURI());
// ip
LOG.info("IP={}", request.getRemoteAddr());
// method
LOG.info("method={}", request.getMethod());
// 参数
LOG.info("args={}", joinPoint.getArgs());
// class-method
LOG.info("class_method={}", joinPoint.getSignature().getDeclaringTypeName() + " " + joinPoint.getSignature().getName());
LOG.info("我在方法调用前执行");
}
@AfterReturning(value = "log()",returning = "obj")
public void doAfterReturning(Object obj) {
if (obj != null) {
LOG.info("Girl={}", obj.toString());
}
}
@After("log()")
public void doAfter() {
LOG.info("我在方法调用后执行");
}
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
try {
// @Before
result = method.invoke(target, args);
// @After
return result;
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
// @AfterThrowing
throw targetException;
} finally {
// @AfterReturning
}
}
url=/addGirl
IP=0:0:0:0:0:0:0:1
method=POST
args=Girl{id=null, age=26, cupSize=‘C‘}
class_method=com.shy.springboot.controller.GirlController addGirl
我在方法调用前执行
Hibernate: insert into girl (age, cup_size) values (?, ?)
我在方法调用后执行
Girl=Girl{id=27, age=26, cupSize=‘C‘}
统一异常处理
Result
,将要呈现的信息统一化,分别是错误码code,错误信息msg和承载的对象T,这样不管是成功还是发生各种各样的异常,都可以返回统一的Result对象。package com.shy.springboot.domain;
public class Result
package com.shy.springboot.util;
import com.shy.springboot.domain.Result;
public class ResultUtil {
public static Result success(Object obj) {
Result result = new Result();
result.setMsg("成功");
result.setCode(0);
result.setData(obj);
return result;
}
public static Result success() {
return success(null);
}
public static Result error(Integer code, String msg) {
Result result = new Result();
result.setMsg(msg);
result.setCode(code);
return result;
}
}
@PostMapping("/addGirl")
public Result
public void checkAge(Integer id) {
Girl girl = girlRepo.findById(id).get();
int age = girl.getAge();
if (age
package com.shy.springboot.enums;
public enum ResultEnum {
SUCCESS(0, "成功"),
ERROR(-1, "未知错误"),
PRIMARY_SCHOOL(100, "你可能还在上小学"),
MIDDLE_SCHOOL(101, "你可能还在上初中");
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
package com.shy.springboot.exception;
import com.shy.springboot.enums.ResultEnum;
public class GirlException extends RuntimeException{
private Integer code;
public GirlException(ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
@GetMapping("/girlAge/{id}")
public void getAge(@PathVariable("id") Integer id) {
girlService.checkAge(id);
}
{
timestamp: 14XXXXXXX,
status: 500,
exception: XXX,
message: XXX,
path: "/girlAge/22"
}
Result
中的信息,像下面这样:{
code: xxx,
msg: xxx,
data: XXX
}
package com.shy.springboot.handle;
import com.shy.springboot.domain.Result;
import com.shy.springboot.exception.GirlException;
import com.shy.springboot.util.ResultUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* ControllerAdvice注解将作用在所有注解了@RequestMapping的控制器的方法上。
* 配合@ExceptionHandler,用于全局处理控制器里的异常
*/
@ControllerAdvice
public class ExceptionHandle {
private static final Logger LOG = LoggerFactory.getLogger(ExceptionHandle.class);
@ExceptionHandler(Exception.class)
@ResponseBody
public Result handle(Exception e) {
if (e instanceof GirlException) {
GirlException exception = (GirlException) e;
return ResultUtil.error(exception.getCode(),exception.getMessage());
}
LOG.error("系统异常:{}", e.getMessage());
return ResultUtil.error(-1,"未知错误");
}
}
单元测试
package com.shy.springboot.service;
import com.shy.springboot.domain.Girl;
import com.shy.springboot.repository.GirlRepo;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* RunWith(SpringRunner.class),让测试运行于Spring测试环境
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class GirlServiceTest {
@Autowired
private GirlRepo girlRepo;
@Test
public void findOne() {
Girl girl = girlRepo.findById(22).get();
Assert.assertEquals(new Integer(14), girl.getAge());
}
}
package com.shy.springboot.controller;
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 GirlControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void girls() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/girls")).andExpect(MockMvcResultMatchers.status().isOk());
/* 下面这条测试不能通过 */
// mockMvc.perform(MockMvcRequestBuilders.get("/girls")).andExpect(MockMvcResultMatchers.content().string("abc"));
}
}
/girls
,并期望状态码是200 OK。注释掉的第二条测试期望响应的内容是abc,然而我们返回的是json格式,所以肯定不能通过测试的。
下一篇:python--tkinter