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应用,也讲解了各种注解类、方法或者方法的参数的注释类型

相关文章
|
8天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
132 73
|
3天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
35 21
|
8天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
8天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
136 2
|
2月前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
58 2
|
8月前
|
设计模式 前端开发 JavaScript
Spring MVC(一)【什么是Spring MVC】
Spring MVC(一)【什么是Spring MVC】
|
7月前
|
设计模式 前端开发 Java
【Spring MVC】快速学习使用Spring MVC的注解及三层架构
【Spring MVC】快速学习使用Spring MVC的注解及三层架构
114 1
|
7月前
|
前端开发 Java 应用服务中间件
Spring框架第六章(SpringMVC概括及基于JDK21与Tomcat10创建SpringMVC程序)
Spring框架第六章(SpringMVC概括及基于JDK21与Tomcat10创建SpringMVC程序)
|
8月前
|
前端开发 Java 关系型数据库
基于ssm框架旅游网旅游社交平台前后台管理系统(spring+springmvc+mybatis+maven+tomcat+html)
基于ssm框架旅游网旅游社交平台前后台管理系统(spring+springmvc+mybatis+maven+tomcat+html)