Spring MVC 中的常用视图

Spring MVC 支持的视图

  • Jackson-based JSON / XML
  • Thymeleaf & FreeMarker

用的比较多的是Jackson-based JSON、XML,后台更多的是提供数据为主。在做boss系统的时候,并不需要绚丽的前端,我们使用模板引擎来做页面在系统上。springboot提供了一些springmvc支持的模板引擎Thymeleaf & FreeMarker。

配置 MessageConverter

  • 通过 WebMvcConfigurer 的 configureMessageConverters()配置
    • Spring Boot 自动查找 HttpMessageConverters 进行注册
    springboot提供了简单的方法,比如说,在应用程序上下文当中,去寻找HttpMessageConverters 并自动把它注册进去。下面,为springmvc当中,自动配置Jackson和JSON / XML的示例:
 public class WebConfiguration implements WebMvcConfigurer{
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters){
            Jackson2ObjectMapperBuilder bulider = new Jackson2ObjectMapperBuilder()
                    .indentOutput(true)
                    .dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
                    .modulesToInstall(new ParameterNamesModule());
            converters.add(new MappingJackson2HttpMessageConverter(bulider.build()));
            converters.add(new MappingJackson2XmlHttpMessageConverter(bulider.createXmlMapper(true).build()));
        }
    }

通过converters.add的这样一个方法,增加了两个Converter进去。

Spring Boot 对 Jackson 的支持

  • JacksonAutoConfiguration
    • Spring Boot 通过 @JsonComponent(序列化器和反序列化器) 注册 JSON 序列化组件
    • Jackson2ObjectMapperBuilderCustomizer (自定义器,在spring的上下文当中,去注册一个Customizer类型的bean)
  • JacksonHttpMessageConvertersConfiguration
    • 增加 jackson-dataformat-xml 以支持 XML 序列化

我们进入WebMvcAutoConfiguration类中,找到WebMvcAutoConfigurationAdapter方法。
在这里插入图片描述

springboot给我们做好了一个配置类configureMessageConverters。
将spring上下文中Converter存到HttpMessageConverter。然后通过converters.addAll进行配置。
在configureMessageConverters中,我们可以看到springboot给我们配了许多的 ViewResolver()。

我们进入JacksonAutoConfiguration类。
在里面添加了JsonComponentModule的一个Module,这个Module处理JsonComponent注解
在这里插入图片描述
我们进入JsonComponentModule。在addJsonBean中,将Serializer添加进去。

例子代码

在这里插入图片描述

@Controller
@RequestMapping("/coffee")
@Slf4j
public class CoffeeController {
    @Autowired
    private CoffeeService coffeeService;

//    @PostMapping(path = "/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
//    @ResponseBody
//    @ResponseStatus(HttpStatus.CREATED)
//    public Coffee addCoffee(@Valid NewCoffeeRequest newCoffee,
//                            BindingResult result) {
//        if (result.hasErrors()) {
//            // 这里先简单处理一下,后续讲到异常处理时会改
//            log.warn("Binding Errors: {}", result);
//            return null;
//        }
//        return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
//    }

    @PostMapping(path = "/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    @ResponseBody
    @ResponseStatus(HttpStatus.CREATED)
    public Coffee addCoffeeWithoutBindingResult(@Valid NewCoffeeRequest newCoffee) {
        return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
    }

    @PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) //这里 接收的是一个JSON类型
    @ResponseBody
    @ResponseStatus(HttpStatus.CREATED) //以Json方式来做Coffee的一个新增咖啡的方法
    public Coffee addJsonCoffeeWithoutBindingResult(@Valid @RequestBody  NewCoffeeRequest newCoffee) { //将Request的内容绑到newCoffee上面  用Valid注解 替我们做一个校验
        return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
    }

    @PostMapping(path = "/", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @ResponseBody
    @ResponseStatus(HttpStatus.CREATED)
    public List<Coffee> batchAddCoffee(@RequestParam("file") MultipartFile file) {
        List<Coffee> coffees = new ArrayList<>();
        if (!file.isEmpty()) {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(
                        new InputStreamReader(file.getInputStream()));
                String str;
                while ((str = reader.readLine()) != null) {
                    String[] arr = StringUtils.split(str, " ");
                    if (arr != null && arr.length == 2) {
                        coffees.add(coffeeService.saveCoffee(arr[0],
                                Money.of(CurrencyUnit.of("CNY"),
                                        NumberUtils.createBigDecimal(arr[1]))));
                    }
                }
            } catch (IOException e) {
                log.error("exception", e);
            } finally {
                IOUtils.closeQuietly(reader);
            }
        }
        return coffees;
    }

    @GetMapping(path = "/", params = "!name")
    @ResponseBody
    public List<Coffee> getAll() {
        return coffeeService.getAllCoffee();
    }

    @RequestMapping(path = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public Coffee getById(@PathVariable Long id) {  // 即可以返回json类型的 也可以返回xml类型的
        Coffee coffee = coffeeService.getCoffee(id);
        log.info("Coffee {}:", coffee);
        return coffee;
    }

    @GetMapping(path = "/", params = "name")
    @ResponseBody
    public Coffee getByName(@RequestParam String name) {
        return coffeeService.getCoffee(name);
    }


}
@MappedSuperclass
@Data
@NoArgsConstructor
@AllArgsConstructor
// 增加了jackson-datatype-hibernate5就不需要这个Ignore了
//@JsonIgnoreProperties(value = {"hibernateLazyInitializer"}) 可以支持这个类的代理类  在此标记不生成json对象的属性
//
//因为jsonplugin用的是java的内审机制.hibernate会给被管理的pojo加入一个hibernateLazyInitializer属性,jsonplugin会把hibernateLazyInitializer也拿出来操作,并读取里面一个不能被反射操作的属性就产生了这个异常.

public class BaseEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(updatable = false)
    @CreationTimestamp
    private Date createTime;
    @UpdateTimestamp
    private Date updateTime;
}

@JsonComponent //springboot自动的将我们的序列化器和反序列化器  这个是序列化
public class MoneySerializer extends StdSerializer<Money> {
    protected MoneySerializer() {
        super(Money.class);
    }

    @Override
    public void serialize(Money money, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeNumber(money.getAmount());
    }
}


@JsonComponent //反序列化
public class MoneyDeserializer extends StdDeserializer<Money> {
    protected MoneyDeserializer() {
        super(Money.class);
    }

    @Override
    public Money deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        return Money.of(CurrencyUnit.of("CNY"), p.getDecimalValue());
    }
}
@SpringBootApplication

@EnableJpaRepositories
@EnableCaching
public class WaiterServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(WaiterServiceApplication.class, args);
	}

	@Bean
	public Hibernate5Module hibernate5Module() {
		return new Hibernate5Module();
	} //这是个Jackson的模块 springboot自动帮我们注册进去

	//@Bean
	//public Jackson2ObjectMapperBuilderCustomizer jacksonBuilderCustomizer() {
	//	return builder -> builder.indentOutput(true); ////对builder做一个输出 我们希望它有一个缩进  这行代码做了这个接口的内部匿名实现类
	//}
}

结果

查询咖啡
在这里插入图片描述
增加了一个json对应的序列化器之后,price返回的是一个数字

如果我们希望返回的是一个xml,我们需要加个头
在这里插入图片描述

使用getbyid查询咖啡
在这里插入图片描述

创建咖啡
在这里插入图片描述
Content-Type为application/json

使用 Thymeleaf 模板引擎

一共有两种配置

添加 Thymeleaf 依赖
• org.springframework.boot:spring-boot-starter-thymeleaf
在start.spring.io中勾选

Spring Boot 的自动配置

  • ThymeleafAutoConfiguration
    • ThymeleafViewResolver

springboot中,有ThymeleafAutoConfiguration 类完成相关配置。

Thymeleaf 的一些默认配置

• spring.thymeleaf.cache=true
模板可以被缓存
• spring.thymeleaf.check-template=true
是否校验模板
• spring.thymeleaf.check-template-location=true
是否检查模板位置是否正确
• spring.thymeleaf.enabled=true
是否启用MVC-Thymeleaf视图
• spring.thymeleaf.encoding=UTF-8
采用utf-8编码,避免页面乱码
• spring.thymeleaf.mode=HTML
页面是HTML类型。
• spring.thymeleaf.servlet.content-type=text/html
文档MIME类型是text/html,也就是html格式的文档。
• spring.thymeleaf.prefix=classpath:/templates/
表示html页面在resources文件夹下的templates文件夹中。即为页面前缀
• spring.thymeleaf.suffix=.html
表示页面后缀是html格式

例子

目录如下:
在这里插入图片描述
主要修改的方法为CoffeeOrderController

@Controller //不光有Request接口 还有与视图相关 modelandview有视图的方法
@RequestMapping("/order")
@Slf4j
public class CoffeeOrderController {
    @Autowired
    private CoffeeOrderService orderService;
    @Autowired
    private CoffeeService coffeeService;


    @GetMapping("/{id}")
    @ResponseBody //没有了@RestController 需要加@ResponseBody
    public CoffeeOrder getOrder(@PathVariable("id") Long id) {
        return orderService.get(id);
    }

    @PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    @ResponseStatus(HttpStatus.CREATED)
    public CoffeeOrder create(@RequestBody NewOrderRequest newOrder) {
        log.info("Receive new Order {}", newOrder);
        Coffee[] coffeeList = coffeeService.getCoffeeByName(newOrder.getItems())
                .toArray(new Coffee[] {});
        return orderService.createOrder(newOrder.getCustomer(), coffeeList);
    }

    @ModelAttribute // List会作为model的一个属性传入
    public List<Coffee> coffeeList() {
        return coffeeService.getAllCoffee();
    }

    @GetMapping(path = "/")//访问根路径 会去呈现一个视图  会拼上suffix和prefix属性的值 变成一个具体的视图的模板的位置
    public ModelAndView showCreateForm() {
        return new ModelAndView("create-order-form");
    }

    @PostMapping(path = "/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) //form表单的形式
    public String createOrder(@Valid NewOrderRequest newOrder,
                                    BindingResult result, ModelMap map) { //做表单的绑定和校验
        if (result.hasErrors()) { //检查绑定的结果是否有错误
            log.warn("Binding Result: {}", result);
            map.addAttribute("message", result.toString());//将错误信息放到modelandview当中
            return "create-order-form"; //返回视图名 做视图呈现
        }

        log.info("Receive new Order {}", newOrder);//新建订单
        Coffee[] coffeeList = coffeeService.getCoffeeByName(newOrder.getItems())
                .toArray(new Coffee[] {});
        CoffeeOrder order = orderService.createOrder(newOrder.getCustomer(), coffeeList);
        return "redirect:/order/" + order.getId(); //做重定向 访问getOrder方法
    }
}

create-order-form.html代码:

<html xmlns:th="http://www.thymeleaf.org">
    <body>
        <h2>Coffee Available Today</h2>
        <table>
            <thead>
                <tr>
                    <th>Coffee Name</th>
                    <th>Price</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="coffee : ${coffeeList}">
                    <td th:text="${coffee.name}">Espresso</td>
                    <td th:text="${coffee.price}">CNY 12.0</td>
                </tr>
            </tbody>
        </table>
        <h2>Your Order</h2>
        <form action="#" th:action="@{/order/}" method="post">
            <label>Customer Name</label>
            <input type="text" name="customer" />
            <ul>
                <li th:each="coffee : ${coffeeList}">
                    <input type="checkbox" name="items" th:value="${coffee.name}" />
                    <label th:text="${coffee.name}">Espresso</label>
                </li>
            </ul>
            <input type="submit" value="Submit"/>
            <p th:text="${message}">Message</p>
        </form>
    </body>
</html>

访问http://localhost:8080/order/,并创建订单:
在这里插入图片描述
我们可以看到,载入界面之后,会根据coffeeList方法,打印出所有的coffee,提交之后为:
在这里插入图片描述
提交成功之后,重定向到redirect:/order/id 上并访问getOrder方法。

如果提交错误,将会把错误信息打印出来:
在这里插入图片描述
此错误为没有选择咖啡。

发布了59 篇原创文章 · 获赞 6 · 访问量 957

猜你喜欢

转载自blog.csdn.net/weixin_43790623/article/details/103482439