欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

开发颠覆者SpringBoot实战---------SpringMVC的基础学习

程序员文章站 2022-05-24 11:17:25
...

pom.xml文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>myspringboot</groupId>
  <artifactId>springmvc4</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <!-- 全局配置 -->
  <properties>
    <!-- java编译级别 -->
    <java.version>1.7</java.version>
    <!-- 编码 -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <!-- web -->
    <jsp.version>2.2</jsp.version>
    <jstl.version>1.2</jstl.version>
    <servlet.version>3.1.0</servlet.version>
    <!-- spring -->
    <spring-framework.version>4.1.5.RELEASE</spring-framework.version>
    <!-- logging -->
    <logback.version>1.0.13</logback.version>
    <slf4j.version>1.7.5</slf4j.version>
  </properties>
  <dependencies>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-web-api</artifactId>
        <version>7.0</version>
        <scope>provided</scope>
    </dependency>
    <!-- springmvc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring-framework.version}</version>
    </dependency>
    <!-- 其他web依赖 -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>${jstl.version}</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>${servlet.version}</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>${jsp.version}</version>
        <scope>provided</scope>
    </dependency>
    <!-- spring and transactions -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>${spring-framework.version}</version>
    </dependency>
    <!-- 使用sl4j和logback作为日志) -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.16</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>${logback.version}</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>${logback.version}</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-access</artifactId>
        <version>${logback.version}</version>
    </dependency>
    <!-- 对象和json或xml的转换 -->
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>2.5.3</version>
    </dependency>
    <!-- 文件上传 -->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
    <!-- 非必要,简化io操作 -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.3</version>
    </dependency>

  </dependencies>
  <!-- maven编译级别 -->
  <build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
                <source>${java.version}</source>
                <target>${java.version}</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>2.3</version>
            <configuration>
                <failOnMissingWebXml>false</failOnMissingWebXml>
            </configuration>
        </plugin>
    </plugins>
  </build>
</project>

一、简单控制器

创建一个index.jsp文件在src/main/resources/views下。

/**
 * springmvc配置
 * @author think
 */
@Configuration
@EnableWebMvc
@ComponentScan("com")
public class MyMvcConfig {
    @Bean
    public InternalResourceViewResolver viewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/classes/views/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }
}

/**
 * web配置
 * @author think
 */
public class WebInitializer implements WebApplicationInitializer{
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(MyMvcConfig.class);
        context.setServletContext(servletContext);//与配置类关联
        Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context));//注册springmvc的DispatcherServlet
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);
    }
}

/**
 * 简单的一个控制器
 * @author think
 */
@Controller
public class ControllerTest {
    @RequestMapping("/index")//配制方法与url的映射
    public String hello(){
        return "index";
    }
}

WebApplicationInitializer是spring提供用来配置servlet3.0的接口,从而替代web.xml,此接口会自动SpringServletContainerInitializer(用来启动serlvet3.0容器)获取到。

二、springmvc常用注解

1、@Controller声明是一个控制器,dispatcherservlet会自动扫描此注解的类,并将web讲求映射到有@RequestMapping的方法上。
2、在声明普通bean时,使用@Component、@Service、@Repository、@Controller是等同的,但是在作为springmvc的控制器bean时,只能使用@Controller。
3、@RequestMapping用来映射web请求、处理类和方法的,支持servlet的request和response作为参数,也支持对request和response的媒体类型进行配置。
4、@ResponseBody支持将返回值放在response体内,而不是返回一个页面,在很多基于ajax的程序时,可以用此注解返回数据,而不是页面。
5、@RequestBody允许参数再request体内,而不是在地址后面
6、@PathVariable用来接收路径参数,如/news/001,可以接收001作文参数
7、@RestController是组合注解,组合了@Controller和@ResponseBody

简单使用:

/**
 * 实体类
 * @author think
 */
public class Obj {
    private Long id;
    private String name;
    public Obj() {//对象转换json时候需要此空参构造
        super();
    }
    public Obj(Long id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Obj [id=" + id + ", name=" + name + "]";
    }
}

/**
 * 参数转化
 * @author think
 */
@Controller
@RequestMapping("/obj")//映射此类的访问路径是/obj
public class ObjController {
    @RequestMapping(produces = "text/plain;charset=UTF-8")//使用此类的访问路径,produces设置返回的response的媒体类型和字符集
    public @ResponseBody String normal(HttpServletRequest request){
        return "url:" + request.getRequestURL();
    }
    @RequestMapping(value = "/{pathVar}", produces = "text/plain;charset=UTF-8")//value接收的路径参数,结合@PathVariable获取访问路径参数
    public @ResponseBody String pathVar(@PathVariable String pathVar, HttpServletRequest request){
        return "url:" + request.getRequestURL() + ",路径参数:" + pathVar;
    }
    @RequestMapping(value = "/requestParam", produces = "text/plain;charset=UTF-8")//常规的request作为参数,获取路径后面拼接的id=xx
    public @ResponseBody String requestParam(Long id, HttpServletRequest request){
        return "url:" + request.getRequestURL() + ",id:" + id;
    }
    @RequestMapping(value = "/turn2Obj", produces = "application/json;charset=UTF-8")//对象自动转换json,返回的媒体对象为json
    @ResponseBody//可以用在方法上也可以用在方法中
    public Obj turn2Obj(Obj obj, HttpServletRequest request){
        return new Obj(obj.getId(), obj.getName());
    }
    @RequestMapping(value = {"/name1", "/name2"}, produces = "text/plain;charset=UTF-8")//不同路径映射到相同的方法
    public @ResponseBody String multiPath(HttpServletRequest request){
        return "url:" + request.getRequestURL();
    }
}

三、静态资源映射和拦截器

SpringMVC的定制配置需要我们的配置类继承一个WebMvcConfigurerAdapter类,并在此类使用@EnableWebMvc注解来开启对SpringMVC的配置支持,这个累里面有许多好用的方法,这样我们可以重写这个类的方法来完成我们的常用配置。

创建一个静态文件demo.txt在src/main/resource/assets下。

/**
 * 自定义拦截器
 * @author think
 */
public class Interceptor extends HandlerInterceptorAdapter{
    //请求发生前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        return true;
    }
    //请求发生后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        Long startTime = (Long) request.getAttribute("startTime");
        request.removeAttribute("startTime");
        Long endTime = System.currentTimeMillis();
        System.out.println("执行时间:" + (endTime - startTime));
        request.setAttribute("handlingTime", endTime - startTime);
    }
}

/**
 * 配置
 * @author think
 */
@Configuration
@EnableWebMvc//开启springmvc支持,没有此句,重写WebMvcConfigurer无效
@ComponentScan("com")
public class MyMvcConfig implements WebMvcConfigurer{
    @Bean
    public InternalResourceViewResolver viewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/classes/views/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }
    @Bean//配置拦截器的bean
    public Interceptor getInterceptor(){
        return new Interceptor();
    }
    @Override//静态资源映射
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //addResourceHandler对外暴露的访问路径,addResourceLocations文件放置的目录
        registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");
    }
    @Override//注册拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getInterceptor());
    }
}

这样就可以访问项目目录下的静态文件资源,同时可以对请求进行拦截。

四、@ControllerAdvice

@ControllerAdvice作为一个全局配置的控制器,其组合了@Component,自动注册为spring的bean,所有配置信息都写在本类里面,注解了@Controller类的方法可以使用@ExceptionHandler,@ModelAttribute,@InitBinder,对所有注解了@RequestMapping的控制器都有效。

创建一个处理异常的页面error.jsp文件在src/main/resources/views下。

/**
 * 全局配置
 * @author think
 */
@ControllerAdvice
public class ExceptionHandlerAdvice {
    @ExceptionHandler(value = Exception.class)//全局异常配置
    public ModelAndView exception(Exception exception, WebRequest request){
        ModelAndView modelAndView = new ModelAndView("error");
        modelAndView.addObject("errorMessage", exception.getMessage());
        return modelAndView;
    }
    @ModelAttribute//自动绑定键值对到model中,所有@RequestMapping的能获得此处的值
    public void addAttributes(Model model){
        model.addAttribute("msg", "额外信息");
    }
    @InitBinder//自动绑定前端的请求参数到model中
    public void initBinder(WebDataBinder binder){
        binder.setDisallowedFields("id");//将request参数封装为对象的时候忽略id这个属性,正常获取id参数还是可以获取到的。
    }
}

@Controller
public class AdviceController {
    @RequestMapping("advice")
    public String getGlobal(@ModelAttribute("msg") String msg, Obj obj, String id){
        throw new IllegalArgumentException("异常,msg:" + msg + ",obj:" + obj + ",id:" + id);
    }
}

发送 “springmvc4/advice?id=55&name=名字” 请求后,在error.jsp中用${errorMessage }就可以获取到:
开发颠覆者SpringBoot实战---------SpringMVC的基础学习

五、其他配置

1、快捷返回页面ViewController

//简单的返回页面
@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/index").setViewName("/index");
    super.addViewControllers(registry);
}

用上面的代码可以替代掉下面的代码,从而达到一个简化的目的,前提是这只是一个简单的页面转向,不涉及到任何的逻辑。

@Controller
public class ControllerTest {
    @RequestMapping("/index")
    public String hello(){
        return "index";
    }
}

2、路径匹配参数配置

在SpringMVC中,请求路径如果带有“.”的话,后面的值将会被忽略,例如访问“/springmvc4/obj/pathvar.33”时,是无法获取“obj/”后面的完整参数。通过重写configurePathMatch将设置为不忽略“.”后面的参数:

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    configurer.setUseSuffixPatternMatch(true);
    super.configurePathMatch(configurer);
}

六、文件上传

创建一个upload.jsp在src/main/resource/views下:

<form action="upload" enctype="multipart/form-data" method="post">
    <input type="file" name="file"><br>
    <input type="submit" value="上传">
</form>

在控制器中用MultipartFile或MultipartFile[]接受单个或多个文件:

/**
 * 页面跳转
 * @author think
 */
registry.addViewController("/toUpload").setViewName("/upload");

/**
 * 文件上传
 * @author think
 */
@Controller
public class UploadController {
    @RequestMapping(value="/upload", method=RequestMethod.POST)
    public @ResponseBody String upload(MultipartFile file){
        try {
            FileUtils.writeByteArrayToFile(new File("F://" + file.getOriginalFilename()), file.getBytes());
            return "ok";
        } catch (Exception e) {
            return "wrong";
        }
    }
}

七、HttpMessageConverter

HttpMessageConverter是用来处理request和response数据的,Spring为我们内置了大量的HttpMessageConverter,下面是自定义HttpMessageConverter。

/**
 * 自定义MessageConverter
 * @author thinkHttp
 */
public class MyMessageConverter extends AbstractHttpMessageConverter<Obj>{
    //新建一个自定义的媒体类型
    public MyMessageConverter() {
        super(new MediaType("application", "x-wisely", Charset.forName("UTF-8")));
    }
    //处理请求数据:处理由"-"隔开的数据,并转为Obj对象
    @Override
    protected Obj readInternal(Class<? extends Obj> clazz, HttpInputMessage input)
            throws IOException, HttpMessageNotReadableException {
        String temp = StreamUtils.copyToString(input.getBody(), Charset.forName("UTF-8"));
        System.out.println(temp);
        String[] sts = temp.split("-");
        return new Obj(Long.parseLong(sts[0]), sts[1]);
    }
    //表明本类只处理Obj这个类
    @Override
    protected boolean supports(Class<?> clazz) {
        boolean b = Obj.class.isAssignableFrom(clazz);
        return b;
    }
    //处理如何输出数据到response中
    @Override
    protected void writeInternal(Obj obj, HttpOutputMessage output) throws IOException, HttpMessageNotWritableException {
        String out = "hello:" + obj.getId() + "-" + obj.getName();
        output.getBody().write(out.getBytes());
    }
}

/**
 * 页面跳转
 * @author think
 */
registry.addViewController("/beginConvert").setViewName("/converter");

自定义的HttpMessageConverter在SpringMVC进行注册有两种方式:

  • configureMessageConverters:重载会覆盖掉SpringMVC默认注册的多个HttpMessageConverter。
  • extendMessageConverters:仅添加一个自定义的HttpMessageConverter,不覆盖默认注册的HttpMessageConverter。

所以我们重写extendMessageConverters:

@Bean
public MyMessageConverter converter(){
    return new MyMessageConverter();
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(converter());
    super.extendMessageConverters(converters);
}

@Controller
public class ConverterController {
    @RequestMapping(value = "/convert", produces = {"application/x-wisely"})
    public @ResponseBody Obj converts(@RequestBody Obj obj){
        return obj;
    }
}

创建一个converter.jsp在src/main/resource/views下:

<body>
    <div id="resp"></div>
    <input type="button" onclick="req()" value="请求">
<script type="text/javascript">
    function req(){
        $.ajax({
            url : 'convert',
            data : '1-xiaoming',
            type : 'POST',
            contentType : 'application/x-wisely',
            success : function(data){
                alert(data);
                document.getElementById('resp').innerHTML = data;
            }
        });
    }
</script>
</body>

请求和响应数据的拦截机制在自定义的HttpMessageConverter的supports方法中:只有当控制器中@ResponseBody返回的对象类型与supports方法的对象类型相一致,才会继续readInternal(读取request的数据)和writeInternal(数据响应到response)。

八、服务端推送技术

推送方案:当客户端向服务端发送请求,服务端会抓住这个请求不放,等有数据更新才返回给客户端,当客户端接受到消息后,再向服务端发送请求,周而复始。这种方式好处是减少了服务器的请求数量,减少服务器的压力。

1、SSE

/**
 * 控制器
 * @author think
 */
@Controller
public class SSEController {
    //text/event-stream这种媒体类型是服务器端SSE的支持,每5秒向浏览器推送随机消息
    @RequestMapping(value = "/push", produces = "text/event-stream")
    public @ResponseBody String push(){
        Random random = new Random();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "data:Testing " + random.nextInt() + "\n\n";
    }
}

/**
 * 页面跳转
 * @author think
 */
registry.addViewController("/sse").setViewName("/sse");

创建一个sse.jsp在src/main/resource/views下:

<body>
    <div id="msgFromPush"></div>
    <a>ssss</a>
<script type="text/javascript">
    if (!!window.EventSource) {//!!是数据强转布尔类型,EventSource对象是新式的浏览器才有,EventSource是SSE的客户端
        var source = new EventSource('push');
        var s = '';
        source.addEventListener('message', function(e){//添加SSE客户端监听,获得服务端推送的消息
            s += e.data + '<br>';
            document.getElementById('msgFromPush').innerHTML = s;
        });
        source.addEventListener('open', function(e){
            //console.log('连接打开');
        }, false);
        source.addEventListener('error', function(e){
            if (e.readyState == EventSource.CLOSED) {
                //console.log('连接断开');
            } else {
                alert(e.readyState);
                //console.log(e.readyState);
            }
        }, false);
    } else {
        //console.log('您的浏览器不支持SSE');
    }
</script>
</body>

2、Servlet3.0+的异步方法处理

/**
 * 开启异步方法支持
 * @author think
 */
Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context));//注册springmvc的DispatcherServlet
servlet.setAsyncSupported(true);

/**
 * 开启定时任务支持
 * @author think
 */
@EnableScheduling//开启定时任务支持
public class MyMvcConfig extends WebMvcConfigurerAdapter{
}

/**
 * 页面跳转
 * @author think
 */
registry.addViewController("/async").setViewName("/async");

/**
 * 控制器
 * @author think
 */
//异步任务的实现是通过控制器从另外一个线程返回一个DeferredResult
@Controller
public class AsyncController {
    @Autowired
    PushService pushService;
    @RequestMapping("/defer")
    public @ResponseBody DeferredResult<String> deferCall(){
        return pushService.getAsyncUpdate();
    }
}

/**
 * 定时任务
 * @author think
 */
@Service
public class PushService {
    private DeferredResult<String> deferredResult;
    public DeferredResult<String> getAsyncUpdate(){
        deferredResult = new DeferredResult<>();
        return deferredResult;
    }
    @Scheduled(fixedDelay = 5000)
    public void refresh(){
        if (deferredResult != null) {
            deferredResult.setResult(new Long(System.currentTimeMillis()).toString());
        }
    }
}

在PushService中产生DeferredResult给控制器使用,通过@Scheduled注解的定时任务更新DeferredResult。

创建一个async.jsp在src/main/resource/views下:

<body>
<script type="text/javascript">
    deferred();
    function deferred(){
        $.get('defer', function(data){
            console.log(data);
            deferred();
        });
    }
</script>
</body>
相关标签: springmvc