SpringMVC学习(二)——快速搭建SpringMVC开发环境(注解方式)

文章目录

  • 说明
  • 1、工程搭建
  • 2、注解配置
    • 2.1、context:annotation-config说明
    • 2.2、context:component-scan配置说明
    • 2.3、mvc:annotation-driven配置说明
  • 3、简单代码实现
  • 4、文件上传下载实现
    • 4.1 文件上传jar包依赖
    • 4.2 在Spring中添加Bean配置
    • 4.3 文件上传实现
    • 4.4 文件下载实现
  • 5、拦截器配置
    • 5.1 拦截器代码实现
    • 5.2 拦截器配置实现
    • 5.3 排除拦截器配置实现
  • 6、统一异常配置
  • 7、日志集成

说明

本文主要是通过注解方式搭建SpringMVC架构,现在通过注解方式搭建框架更加常见以及便捷,文中如果有不妥之处望各位能够批评指正,大家共同进步。
码字不易,转载请注明出处。需要源码请自行下载:https://gitee.com/leo825/spring-framework-learning-example.git

1、工程搭建

可以参考《SpringMVC学习(一)——快速搭建SpringMVC开发环境(非注解方式)》搭建一个非注解型的SpringMVC,注解方式就是在非注解方式的基础之上做的优化。本文也是在这个工程的基础之上做的修改。

2、注解配置

在开启注解的时候遇到几个注解配置:



先说明一下这几个注解的定义,通过他们的定义可以理解他们的用途

2.1、context:annotation-config说明

1、如果想使用@Autowired注解,就必须在Spring容器中声明AutowiredAnnotationBeanPostProcessor的Bean
2、如果想使用@Resource、@PostConstruct、@PreDestroy等注解,就必须在Spring中声明CommonAnnotationBeanPostProcessor的Bean
3、如果想使用@PersistenceContext注解,就必须在Spring中声明PersistenceAnnotationBeanPostProcessor的Bean
4、如果想使用 @Required的注解,就必须在Spring中声明RequiredAnnotationBeanPostProcessor的Bean

AutowiredAnnotationBeanPostProcessor
RequiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor

通过上面的描述我们知道,使用配置就是向Spring容器中注册上面几个Bean,并实现@Autowired、@Resource、@PostConstruct、@PreDestroy、@PersistenceContext、@Required等注解的功能。

2.2、context:component-scan配置说明

下面的是官网上对于这个标签的描述:

Scans the classpath for annotated components that will be auto-registered as
	Spring beans. By default, the Spring-provided @Component, @Repository,
	@Service, and @Controller stereotypes will be detected.

	Note: This tag implies the effects of the 'annotation-config' tag, activating @Required,
	@Autowired, @PostConstruct, @PreDestroy, @Resource, @PersistenceContext and @PersistenceUnit
	annotations in the component classes, which is usually desired for autodetected components
	(without external configuration). Turn off the 'annotation-config' attribute to deactivate
	this default behavior, for example in order to use custom BeanPostProcessor definitions
	for handling those annotations.

大致意思就是说包含要做的事情,同时额外支持@Component、@Repository、@Service、@Controller注解.并且、扫描base-package并且在applicationcontext中注册扫描的beans。因此,如果配置了就不需要再配置

2.3、mvc:annotation-driven配置说明

根据mvc开头能看出来,这个配置是是针对SpringMVC的,配置会向Spring容器中自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 两个bean,是@Controller分发请求所必须的。并且这个配置提供了数据绑定支持,@NumberFormatannotation支持,@DateTimeFormat支持,@Valid支持,读写XML的支持(JAXB),读写JSON的支持(Jackson非常主要)。

注意:在笔者测试过程中发现配置不是必须的,查了原因这个注解驱动主要是配置RequestMappingHandlerMapping和RequestMappingHandlerAdapter。但是springmvc容器启动是会加载默认的DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter。所以不配置也是不影响项目的启动运行的。但是看源码这个两个默认的实现方法是过时的,因此虽然不影响项目,但是我们一般还是会配置使用新的代替的方法。

这个标签的具体实现类是:

org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser

通过阅读类的注释,可以找到这个类主要向Spring容器中注册了以下Bean实例

RequestMappingHandlerMapping
BeanNameUrlHandlerMapping
RequestMappingHandlerAdapter
HttpRequestHandlerAdapter
SimpleControllerHandlerAdapter
ExceptionHandlerExceptionResolver
ResponseStatusExceptionResolver
DefaultHandlerExceptionResolver

这些Bean的作用依次为:
1、前面两个RequestMappingHandlerMappingBeanNameUrlHandlerMapping是HandlerMapping接口的实现类,用来处理请求映射的。

  • 第一个是处理@RequestMapping注解的。
  • 第二个会将Controller类的名称映射为请求的url。

2、中间三个RequestMappingHandlerAdapterHttpRequestHandlerAdapterSimpleControllerHandlerAdapter是用来处理请求的。就是调用Controller的那个方法来处理请求。

  • 第一个是处理@Controller注解的处理器,支持自定义方法参数和返回值。
  • 第二个处理继承HttpRequestHandler的处理器。
  • 第三个处理继承自Controller接口的处理器。

3、后面三个是用来处理异常的解析器(自行查看)。
4、除以上之外,此配置还提供以下支持:

  • 支持使用ConversionService实例对表单参数进行类型转换;
  • 支持使用@NumberFormatannotation、@DateTimeFormat注解完成数据类型的格式化;
  • 支持使用@Valid注解对Java bean实例进行JSR 303验证;
  • 支持使用@RequestBody和@ResponseBody注解

3、简单代码实现

首先在myspringmvc-servlet.xml中添加上面介绍的注解配置:

    <!-- 配置SpringMVC支持注解,需要放在context:component-scan前面,否则可能会报404-->
    <mvc:annotation-driven></mvc:annotation-driven>
    <!-- 定义项目扫描包的路径,并支持注解例如:@Component@Repository@Service@Controller-->
    <context:component-scan base-package="com.leo"></context:component-scan>

然后开始编写一个Controller,并且添加@Controller注解

@Controller
public class HelloController{

    /**
     * 返回一个ModeAndView
     * @return
     * @throws ServletException
     * @throws IOException
     */
    @RequestMapping(value = "/hello",method = RequestMethod.GET)
    public ModelAndView hello() throws ServletException, IOException {
        System.out.println("使用配置实现 hello controller 跳转到 success");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("data", "恭喜您,测试成功了");
        modelAndView.setViewName("success");//跳转到/WEB-INF/views/success.jsp
        return modelAndView;
    }

    /**
     * 返回逻辑视图“success”
     * @return
     * @throws ServletException
     * @throws IOException
     */
    @RequestMapping(value = "/hello2",method = RequestMethod.GET)
    public String hello2() throws ServletException, IOException {
        System.out.println("访问了 hello2");
        return "success";
    }

    /**
     * 返回一个字符串
     * @return
     * @throws ServletException
     * @throws IOException
     */
    @RequestMapping(value = "/hello3",method = RequestMethod.GET)
    @ResponseBody
    public String hello3() throws ServletException, IOException {
        System.out.println("访问了 hello3");
        String hello = "你好 SpringMVC";
        return hello;
    }
}

使用 @Autowired注入UserInfoService

@Controller
public class GetUserInfoController {
    @Autowired
    UserInfoService userInfoService;
    
    @RequestMapping("/getUserInfoList1")
    public ModelAndView getUserInfoList1(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        System.out.println("GetUserInfoController获取用户信息");
        ModelAndView modelAndView = new ModelAndView();
        //访问数据库
        List userInfoList = userInfoService.getUserInfoList();
        System.out.println(userInfoList);
        modelAndView.addObject("data", userInfoList);
        modelAndView.setViewName("success");//跳转到/WEB-INF/views/success.jsp
        return modelAndView;
    }
}

如果想使用依赖注入的UserInfoService,首先通过@Service注入到Sping中

@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Override
    public void insertUserInfo(UserInfo userInfo) {
        jdbcTemplate.execute("INSERT INTO USER_INFO(NAME,GENDER,AGE,REMARKS) VALUES('" + userInfo.getName() + "','" + userInfo.getGender() + "','" + userInfo.getAge() + "','" + userInfo.getRemarks() + "')");
    }

    @Override
    public void deleteUserInfo(Integer id) {
        DBUtil.getJdbcTemple().execute("DELETE FROM USER_INFO WHERE ID = " + id);
    }

    @Override
    public void updateUserInfo(Integer id, UserInfo newUserInfo) {
        jdbcTemplate.update("UPDATE USER_INFO SET NAME=?, GENDER=?, AGE=? ,REMARKS=? WHERE ID=?", new Object[]{
                newUserInfo.getName(),
                newUserInfo.getGender(),
                newUserInfo.getAge(),
                newUserInfo.getRemarks(),
                id
        });
    }

    @Override
    public List<UserInfo> getUserInfoList() {
        List<UserInfo> userInfos = new ArrayList<>();
        List<Map<String, Object>> results = jdbcTemplate.queryForList("SELECT * FROM USER_INFO");
        for (Map obj : results) {
            UserInfo userInfo = new UserInfo();
            userInfo.setId((Integer) obj.get("ID"));
            userInfo.setName((String) obj.get("NAME"));
            userInfo.setGender("0".equals((String) obj.get("GENDER")) ? "女" : "男");
            userInfo.setAge((String) obj.get("AGE"));
            userInfo.setRemarks((String) obj.get("REMARKS"));
            userInfos.add(userInfo);
        }
        return userInfos;
    }
}

4、文件上传下载实现

4.1 文件上传jar包依赖

首先,如果没有添加jar包依赖的在pom.xml中添加依赖

        <!-- SpirngMVC支持文件上传的工具包 -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.3</version>
        </dependency>

4.2 在Spring中添加Bean配置

其次,需要在myspringmvc-servlet.xml配置文件中添加Bean支持

    
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        
        <property name="defaultEncoding" value="utf-8">property>
        
        <property name="maxUploadSize" value="1048576">property>
    bean>

4.3 文件上传实现

通过以上的配置值就可以使用MutipartFile直接接收文件了

    /**
     * 文件上传
     *
     * @param multipartFil
     * @param request
     * @return
     * @throws IOException
     */
    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public String upload(@RequestParam(value = "file") MultipartFile multipartFil, HttpServletRequest request) throws IOException {
        if (!multipartFil.isEmpty()) {
            //文件标签名
            System.out.println(multipartFil.getName());
            //上传文件名称
            System.out.println(multipartFil.getOriginalFilename());
            //文件大小
            System.out.println(multipartFil.getSize());
            //上传文件的类型
            System.out.println(multipartFil.getContentType());

            String fileDirStr = request.getServletContext().getRealPath("/uploadFile");
            String filename = multipartFil.getOriginalFilename();
            System.out.println(fileDirStr);
            File fileDir = new File(fileDirStr);
            if (!fileDir.exists()) {
                fileDir.mkdirs();
            }
            File file1 = new File(fileDir, filename);
            multipartFil.transferTo(file1);
        }
        return "success";
    }

前端的测试代码如下:

<form action="${pageContext.request.contextPath}/upload"
      enctype="multipart/form-data"
      method="post">
    <input type="file" name="file" value="请选择需要上传的文件" /><br>
    <input type="submit" value="提交">
form>

4.4 文件下载实现

文件下载的方式也有很多,这里提供一下三种测试方法:

    /**
     * 下载文件:
     * 通过Spring提供的这种对象,这种下载具有局限性,文件太大会导致内存溢出异常
     *
     * @param request
     * @param filename
     * @return
     * @throws IOException
     */
    @RequestMapping(value = "/download", method = RequestMethod.GET)
    public ResponseEntity<byte[]> download(HttpServletRequest request, @RequestParam String filename) throws IOException {
        String fileDirStr = request.getServletContext().getRealPath("/uploadFile");
        File file = new File(fileDirStr, filename);
        byte[] body = FileUtils.readFileToByteArray(file);
        //设置下载名称,否则乱码
        String downloadFilename = new String(file.getName().getBytes("utf-8"), "iso-8859-1");
        HttpHeaders httpHeaders = new HttpHeaders();
        //设置文件类型
        httpHeaders.add("Content-Disposition", "attchement;filename=" + downloadFilename);

        ResponseEntity responseEntity = new ResponseEntity(body, httpHeaders, HttpStatus.OK);
        return responseEntity;
    }

    /**
     * 使用字节流直接输出,实现类似上面
     *
     * @param request
     * @param response
     * @param filename
     */
    @RequestMapping(value = "/download2", method = RequestMethod.GET)
    public void download2(HttpServletRequest request, HttpServletResponse response, @RequestParam String filename) throws IOException {
        //获取主机文件
        String fileDirStr = request.getServletContext().getRealPath("/uploadFile");
        File file = new File(fileDirStr, filename);
        byte[] outByte = FileUtils.readFileToByteArray(file);
        //设置下载名称,否则乱码
        String downloadFilename = new String(file.getName().getBytes("utf-8"), "iso-8859-1");
        response.setHeader("Content-Disposition", "attchement;filename=" + downloadFilename);
        response.getOutputStream().write(outByte);
        response.getOutputStream().close();
    }

    /**
     * 普通的输出输出流下载
     *
     * @param request
     * @param response
     * @param filename
     * @throws IOException
     */
    @RequestMapping(value = "/download3", method = RequestMethod.GET)
    public void download3(HttpServletRequest request, HttpServletResponse response, @RequestParam String filename) throws IOException {
        //获取主机文件
        String fileDirStr = request.getServletContext().getRealPath("/uploadFile");
        File file = new File(fileDirStr, filename);
        InputStream inputStream = new FileInputStream(file);

        //设置下载名称,否则乱码
        String downloadFilename = new String(file.getName().getBytes("utf-8"), "iso-8859-1");
        response.setHeader("Content-Disposition", "attchement;filename=" + downloadFilename);
        OutputStream os = response.getOutputStream();
        byte[] bf = new byte[2048];
        int len;
        while ((len = inputStream.read(bf)) > 0) {
            os.write(bf, 0, len);
        }
        os.close();
        inputStream.close();
    }

5、拦截器配置

5.1 拦截器代码实现

首先,编写两个拦截器继承自HandlerInterceptorAdapter
第一个拦截器:

public class HandlerInterceptor1 extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("HandlerInterceptor1 preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("HandlerInterceptor1 postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("HandlerInterceptor1 afterCompletion");
    }
}

第二个拦截器:

public class HandlerInterceptor2 extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("HandlerInterceptor2 preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("HandlerInterceptor2 postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("HandlerInterceptor2 afterCompletion");
    }
}

5.2 拦截器配置实现

编写完上面的拦截器代码之后,需要将Bean实例注入到Spring中

    
    <mvc:interceptors>
        <bean class="com.leo.interceptor.HandlerInterceptor1">bean>
        <bean class="com.leo.interceptor.HandlerInterceptor2">bean>
    mvc:interceptors>

访问:http://localhost:8080/springmvc/hello
执行情况如下日志所示:

HandlerInterceptor1 preHandle
HandlerInterceptor2 preHandle
HelloController.handlerAllException
HandlerInterceptor2 afterCompletion
HandlerInterceptor1 afterCompletion
HandlerInterceptor1 preHandle
HandlerInterceptor2 preHandle
使用配置实现 hello controller 跳转到 success
HandlerInterceptor2 postHandle
HandlerInterceptor1 postHandle
HandlerInterceptor2 afterCompletion
HandlerInterceptor1 afterCompletion

5.3 排除拦截器配置实现

需要在myspringmvc-servlet.xml配置文件中添加如下配置信息:

    
    <mvc:interceptors>
        <mvc:interceptor>
            
            <mvc:mapping path="/springmvc/*"/>
            
            <mvc:exclude-mapping path="/springmvc/hello">mvc:exclude-mapping>
            <bean class="com.leo.interceptor.HandlerInterceptor1">bean>
        mvc:interceptor>
        <mvc:interceptor>
        	
            <mvc:mapping path="/**"/>
            <bean class="com.leo.interceptor.HandlerInterceptor2">bean>
        mvc:interceptor>
    mvc:interceptors>

配置后,再访问:http://localhost:8080/springmvc/hello
控制台输出的日志为如下:

HandlerInterceptor2 preHandle
使用配置实现 hello controller 跳转到 success
HandlerInterceptor2 postHandle
HandlerInterceptor2 afterCompletion

想了解过滤器和拦截器区别的可以参考:《SpringMVC中过滤器和拦截器的区别》

6、统一异常配置

@ControllerAdvice 注解修饰可以处理所有的Controller的异常

@ControllerAdvice
public class HandlerException {
    @ExceptionHandler
    public ModelAndView handlerAllException(Exception e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("exceptionMsg", e.getMessage());
        mv.setViewName("error");
        System.out.println("HelloController.handlerAllException");
        return mv;
    }
    @ExceptionHandler(value = {ArithmeticException.class})
    public ModelAndView handlerArithmeticException(Exception e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("exceptionMsg", e.getMessage());
        mv.setViewName("error");
        System.out.println("HelloController.handlerArithmeticException");
        return mv;
    }
}

当然也可以通过在myspringmvc-servlet.xml配置文件中添加Bean支持实现

    
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        
        <property name="exceptionAttribute" value="ex">property>
        <property name="exceptionMappings">
            <props>
                <prop key="java.lang.ArrayIndexOutOfBoundsException">errorprop>
            props>
        property>
    bean>

7、日志集成

本章节介绍的是集成log4j2日志框架,首先pom中配置依赖jar

        
        <dependency>
            <groupId>org.apache.logging.log4jgroupId>
            <artifactId>log4j-apiartifactId>
            <version>2.11.2version>
        dependency>
        <dependency>
            <groupId>org.apache.logging.log4jgroupId>
            <artifactId>log4j-coreartifactId>
            <version>2.11.2version>
        dependency>

然后在相应的文件目录下编写log4j.xml模板文件,笔者的在resources下



<Configuration status="ERROR" monitorInterval="30">
    <Properties>
        
        <Property name="LOG_HOME">/root/bubble/logsProperty>
        <property name="ERROR_LOG_FILE_NAME">/root/bubble/logsproperty>
    Properties>
    <Appenders>
        
        <Console name="Console" target="SYSTEM_OUT">
            
            <ThresholdFilter level="TRACE" onMatch="ACCEPT" onMismatch="DENY" />
            
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36}:%L [%t] - %msg%n" />
        Console>
        
        <RollingFile name="RollingFileInfo" fileName="${LOG_HOME}/search.log" filePattern="${LOG_HOME}/$${date:yyyy-MM}/search-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36}:%L [%t] - %msg%n" />
            
            <Policies>
                
                
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
                
                <SizeBasedTriggeringPolicy size="100 MB" />
            Policies>
            
            <DefaultRolloverStrategy max="100" />
        RollingFile>
        <RollingFile name="RollingFileError"
                     fileName="${ERROR_LOG_FILE_NAME}/search-error.log"
                     filePattern="${ERROR_LOG_FILE_NAME}/$${date:yyyy-MM}/search-error-%d{yyyy-MM-dd}.log">
            <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY" />
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36}:%L [%t] - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
            Policies>
            <DefaultRolloverStrategy max="100" />
        RollingFile>
    Appenders>
    
    <Loggers>
        
        <logger name="org.springframework" level="INFO">logger>
        
        
        <root level="all">
            <appender-ref ref="Console" />
            <appender-ref ref="RollingFileInfo" />
            <appender-ref ref="RollingFileError" />
        root>
    Loggers>
Configuration>

然后在web.xml文件中,将日志文件配置到上下文中

    
    <context-param>
        <param-name>log4jConfigurationparam-name>
        <param-value>classpath:log4j2.xmlparam-value>
    context-param>

完成以上配置就已经集成了log4j2框架。开始测试:

@Controller
@RequestMapping("/log")
public class LoggerRecordController {
    //获取日志
    private static final Logger LOGGER = LogManager.getLogger();

    @Autowired
    UserInfoService userInfoService;

    @RequestMapping("/testLog1")
    @ResponseBody
    public ModelAndView testLog1(@RequestParam("username") String name) {
        long startTime = System.currentTimeMillis();
        ModelAndView modelAndView = new ModelAndView();
        //访问数据库
        List userInfoList = userInfoService.getUserInfoList();
        LOGGER.info(userInfoList);
        modelAndView.addObject("data", userInfoList);
        modelAndView.setViewName("success");//跳转到/WEB-INF/views/success.jsp
        LOGGER.info("获取name=" + name + "接口耗时:" + (System.currentTimeMillis() - startTime) + "ms");
        return modelAndView;

    }
}

测试结果,如下所示:

2020-02-09 20:15:59.608 INFO  com.leo.controller.LoggerRecordController:38 [http-apr-8080-exec-7] - [UserInfo{id=3, name='晓玲', gender='女', age='22', remarks='工程师'}, UserInfo{id=4, name='晓玲', gender='女', age='24', remarks='工程师'}]
2020-02-09 20:15:59.631 INFO  com.leo.controller.LoggerRecordController:41 [http-apr-8080-exec-7] - 获取name=leo825接口耗时:39ms

你可能感兴趣的:(SpringMVC学习专栏,spring)