Spring MVC-04循序渐进之基于注解的控制器(下)

简介: Spring MVC-04循序渐进之基于注解的控制器(下)

使用@Autowired和@Service进行依赖注入


使用Spring框架的一个好处是容易进行依赖注入,将依赖注入到Spring MVC控制器的最简单的方法是通过注解@Autowired到字段或者方法。Autowired注解类型属于org.springframework.beans.factory.annotation包

分两步

  1. 在配置文件中添加context:component-scan元素扫描依赖基本包
  2. 标注注解,如果是服务层,标注@Service

我们新建个maven工程来演示该功能

20180121151928677.jpg


首先我们先看下Controller类

package com.artisan.springmvc.controller;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.artisan.springmvc.domain.Product;
import com.artisan.springmvc.form.ProductForm;
import com.artisan.springmvc.service.ProductService;
@Controller
public class ProductController {
    private static final Logger logger = Logger.getLogger(ProductController.class);
    @Autowired
    private ProductService productService;
    @RequestMapping(value = "/product_input")
    public String inputProduct() {
        logger.info("inputProduct called ....");
        return "ProductForm";
    }
    @RequestMapping(value = "/product_save", method = RequestMethod.POST)
    public String saveProduct(ProductForm productForm, RedirectAttributes attributes) {
        logger.info("saveProduct called ....");
        // no need to create and instantiate a ProductForm
        // create Product
        Product product = new Product();
        product.setName(productForm.getName());
        product.setDescription(productForm.getDescription());
        try {
            product.setPrice(Float.parseFloat(productForm.getPrice()));
        } catch (NumberFormatException e) {
        }
        // add product
        Product savedProduct = productService.add(product);
        attributes.addFlashAttribute("message", "The product has been saved successfully");
        return "redirect:/product_view/" + savedProduct.getId();
    }
    /**
     * 
     * @param id
     * @param model
     * @return
     * @PathVariable用来获得请求url中的动态参数
     */
    @RequestMapping(value = "/product_view/{id}")
    public String getProductById(@PathVariable Long id, Model model) {
        logger.info("getProductById called ....");
        Product product = productService.get(id);
        model.addAttribute("product", product);
        return "ProductView";
    }
}


该ProductController类与上个例子中的ProductController类相比,做了一些调整

1. 首先自动注入了ProductService ,加了@AutoWired注解

    private ProductService productService;
    public ProductService getProductService() {
        return productService;
    }
    @Autowired
    public void setProductService(ProductService productService) {
        this.productService = productService;
    }


或者

@Autowired
private ProductService productService;


productService是一个提供跟踪处理产品方法的接口,为productService字段或者set方法注入@Autowired会使ProductService的一个实例被注入到ProductController实例中。


为了使类能够被Spring扫描到,必须要标注@Service


ProductService接口

package com.artisan.springmvc.service;
import com.artisan.springmvc.domain.Product;
public interface ProductService {
    Product add(Product product);
    Product get(long id);
}

ProductServiceImpl类

package com.artisan.springmvc.service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Service;
import com.artisan.springmvc.domain.Product;
/**
 * 
 * @author Mr.Yang
 * 标注了@Service的服务层
 *
 */
@Service
public class ProductServiceImpl implements ProductService {
    private Map<Long, Product> products = new HashMap<Long, Product>();
    // 使用AtomicLong能让long的操作在多线程下保持原子型
    private AtomicLong generator = new AtomicLong();
    public ProductServiceImpl() {
        Product product = new Product();
        add(product);
    }
    @Override
    public Product add(Product product) {
        long newId = generator.incrementAndGet();
        product.setId(newId);
        products.put(newId, product);
        return product;
    }
    @Override
    public Product get(long id) {
        return products.get(id);
    }
}


2. 然后在SpringMVC配置文件中有两个component-scan元素,一个是用于扫描控制器类,另一个新增加的为扫描服务类

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd     
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.artisan.springmvc.controller"/>
    <context:component-scan base-package="com.artisan.springmvc.service"/>
    <mvc:annotation-driven/>
    <mvc:resources mapping="/css/**" location="/css/"/>
    <mvc:resources mapping="/*.html" location="/"/>
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>


重定向和Flash属性


经常写Servlet/JSP的童鞋都知道转发和重定向的区别


转发比重定向要快,因为转发不经过客户端,而重定向会经过客户端。但是有时候采用重定向会更好,比如需要重定向到一个外部网站,则无法使用转发


另外一个使用重定向的场景是避免在用户重新加载页面的时候再次调用相同的动作 ,比如,这个示例中, 当提交产品表单时,saveProduct方法会被调用,并执行相应的动作。在真实应用中,这些所述产品会加入到数据库中。但是如果提交表单后重新加载页面,saveProduct会被再此调用,同样的产品可能被再此添加。为了避免这种情况,提交表单后,你可能更愿意将用户重定向到一个不同的页面。这个网页任意加载都没有副作用。我们这个示例中,提交表单后,将用户重定向到一个ViewProduct页面.


在这个示例中,ProductController#saveProduct方法 返回

return "redirect:/product_view/" + savedProduct.getId();


这里使用重定向,而不是转发来防止当用户重新加载页面时,saveProduct被二次调用。


使用重定向有个不方便的地方:无法轻松的传值给目标页面,而转发则可以简单的将属性添加到Model中,使目标页面轻松访问。由于重定向经过客户端,所以Model中的一切都在重定向时丢失了。 幸运的是Spring3.1版本及更高的版本通过Flash属性提供了一种重定向传值的方法


要使用Flash属性,必须在Spring MVC的配置文件中有一个<mvc:annotation-driven/>元素,然后,还必须在方法上添加一个新的参数类型 org.springframework.web.servlet.mvc.support.RedirectAttributes


如下所示

20180121164349072.jpg


请求参数和路径变量


获取请求参数


请求参数和路径变量都可以用于发送值给服务器,二者都是URL的一部分。 请求参数采用key=value形式,并用&分割。


比如

http://localhost:8080/chapter04b/productRetrive?productId=5


在传统的Servlet编程中,可以使用HttpServletRequest的getParameter方法来获取一个请求参数值

String productId=httpServletRequest.getParameter("productId");


Spring MVC则提供了一个更简单的方法来获取请求参数的值:org.springframework.web.bind.annotation.RequestParam注释类型来获取注释方法参数,比如

public void sendProduct(@RequestParam int productId)


获取路径变量


路径变量类似请求参数,但是没有key部分,只有一个值, 比如我们这个示例中 product_view动作映射到如下URL

/product_view/productId


其中productId表示产品标识符的整数。在SpringMVC中,productId被称作路径变量,用来发送一个值到服务器

接下来我们看下viewProduct方法演示了一个路径变量的使用

/**
     * 
     * @param id
     * @param model
     * @return
     * @PathVariable用来获得请求url中的动态参数
     */
    @RequestMapping(value = "/product_view/{id}")
    public String viewProduct(@PathVariable Long id, Model model) {
        logger.info("getProductById called ....");
        Product product = productService.get(id);
        model.addAttribute("product", product);
        return "ProductView";
    }


为了使用路径变量,首先需要在RequestMapping注解的值属性中添加一个变量,该变量必须放在花括号之间,例如下面的RequestMapping注解定义一个名为id的路径变量

@RequestMapping(value = "/product_view/{id}")



然后在方法签名中添加一个同名变量,并添加上@PathVariable注解。 当viewProduct方法别调用时,请求URL的id值将被复制到路径变量中,并可以在方法中使用。 路径变量的类型可以不是字符串,Spring MVC将尽量转换为非字符串类型,这个强大的功能,后续在数据绑定和表单参数中详解。

可以在请求映射中使用多个路径变量,比如userId和orderId两个路径变量

@RequestMapping(value="/produtc_view/{userId}/{orderId}")


直接在浏览器中输入URL,来测试viewProduct方法的路径变量

http://localhost:8080/chapter04b/product_view/5


使用路径变量有可能出现的问题


有时候,使用路径变量会遇到一个小问题:在某些情况下,浏览器可能会误解路径变量。 考虑下面的URL

http://example.com/context/abc


浏览器会(正确)认为abc是一个动作。 任何静态文件路径的解析,比如css文件,将使用http://example.com/context作为基本路径。


这就是说,若服务器发送的网页包含如下img元素 <img src='logo.png'/>,改浏览器将试图通过http://example.com/context/logo.png来加载logo.png


然而,若一个应用程序被部署为默认上线文(默认上下文是一个空字符串),则对于同一个目标的URL,会是这样

http://example.com/abc


下面是带有路径变量的URL

http://example.com/abc/1


这种情况下,浏览器会认为abc是上下文,没有动作。 如果在页面中使用<img src='logo.png'/>,浏览器将试图通过http://example.com/abc/logo.png来寻找图片,并且它将找不到图片。


幸运的是,我们有个简单的解决方法,即通过JSTL标记的URL。 标签会通过正确解析URL来修复该问题。


比如我们该案例中的css加载

<style type="text/css">@import url(css/main.css);</style>


改为

<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>


若程序部署为默认的上下文,链接标签会将改URL转换成如下形式

<style type="text/css">@import url(css/main.css);</style>


若程序不在默认上下文中,则会被转换为

<style type="text/css">@import url("<c:url value="/chapter04b/css/main.css"/>");</style>


我们这个示例中的上下文为chapter04b , 通过f12查看如下方式加载css

http://localhost:8080/chapter04b/css/main.css


20180121174111560.jpg



如果写成@import url(css/main.css);将加载不到css的样式。


@ModelAttribute


前面讲到Spring MVC在每次调用请求处理方法时,都会创建Model类型的一个实例。若打算使用该实例,则可以在方法中添加一个Model类型的参数。事实上还可以使用在方法中添加ModelAttribute注释类型来访问Model实例。 该注释类型也是org.springframework.web.bind.annotation包的成员。


@ModelAttribute的第一个用途


可以用@ModelAttribute来注释方法参数或者方法。


带@ModelAttribute注解的方法会将其输入的或创建的参数对象添加到Model对象中(若方法中没有显式添加)。

比如,Spring MVC将在每次调用submitOrder方法时创建一个Order实例


@RequestMapping(value="/submitOrder",method=RequestMethod.Post)
public String submitOrder(@ModelAttribute("newOrder") Order order,Model model){
    ...
}

输入或者创建的Order实例将用newOrder键值添加到Model对象中,如果未定义键值名,则使用该对象类型的名称。

比如,每次调用如下方法,会使用键值order将Order实例添加到Model对象中

@RequestMapping(value="/submitOrder",method=RequestMethod.Post)
public String submitOrder(@ModelAttribute Order order,Model model){
    ...
}

@ModelAttribute的第二个用途


@ModelAttribute的第二个用途是标注一个非请求的处理方法。 被@ModelAttribute注释的方法会在每次调用该控制器类的请求处理方法时被调用。


这就意味着,如果一个控制器类有两个请求处理方法,以及一个带有@ModelAttribute注解的方法,该方法的调用次数就会比每个处理请求方法更加频繁。


Spring MVC会在调用请求处理方法之前调用带有@ModelAttribute注解的方法,带@ModelAttribute注解的方法可以返回一个对象或者一个void类型,


如果返回一个对象,则返回对象会自动添加到Model中

@ModelAttribute
public Product addProduct(@RequestParam String productId){
    return productService.get(produtId)
}

若返回的是void,这还必须添加一个Model类型的参数,并自行将实例添加到Model中

@ModelAttribute
public void populateModel(@RequestParam String id,Model model){
    model.addAttribute(new Account(id));
}


总结


这里介绍了如何编写基于注解的控制器的Spring MVC应用,也讲解了各种注解类、方法或者方法的参数的注释类型

相关文章
|
20天前
|
Java 开发者 Spring
【SpringBoot 异步魔法】@Async 注解:揭秘 SpringBoot 中异步方法的终极奥秘!
【8月更文挑战第25天】异步编程对于提升软件应用的性能至关重要,尤其是在高并发环境下。Spring Boot 通过 `@Async` 注解简化了异步方法的实现。本文详细介绍了 `@Async` 的基本用法及配置步骤,并提供了示例代码展示如何在 Spring Boot 项目中创建与管理异步任务,包括自定义线程池、使用 `CompletableFuture` 处理结果及异常情况,帮助开发者更好地理解和运用这一关键特性。
93 1
|
29天前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
16天前
|
缓存 Java 数据库连接
Spring Boot奇迹时刻:@PostConstruct注解如何成为应用初始化的关键先生?
【8月更文挑战第29天】作为一名Java开发工程师,我一直对Spring Boot的便捷性和灵活性着迷。本文将深入探讨@PostConstruct注解在Spring Boot中的应用场景,展示其在资源加载、数据初始化及第三方库初始化等方面的作用。
42 0
|
1天前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
29天前
|
Java 数据安全/隐私保护 Spring
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
|
29天前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
16天前
|
监控 安全 Java
【开发者必备】Spring Boot中自定义注解与处理器的神奇魔力:一键解锁代码新高度!
【8月更文挑战第29天】本文介绍如何在Spring Boot中利用自定义注解与处理器增强应用功能。通过定义如`@CustomProcessor`注解并结合`BeanPostProcessor`实现特定逻辑处理,如业务逻辑封装、配置管理及元数据分析等,从而提升代码整洁度与可维护性。文章详细展示了从注解定义、处理器编写到实际应用的具体步骤,并提供了实战案例,帮助开发者更好地理解和运用这一强大特性,以实现代码的高效组织与优化。
30 0
|
29天前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
29 0
|
4月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
145 0
|
4月前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
63 0