Spring-_-Bear 的 CSDN 博客导航

一、引言

在学习全注解方式整合 Spring、Spring MVC 和 MyBatis 框架之前,一定要先掌握 XML 配置文件方式整合 SSM,可参考 SSM 框架整合(基于 XML)

相比于 XML 的配置方式,使用全注解的方式进行 SSM 整合就需要自定义类来替代 Spring、Spring MVC、MyBatis 的核心配置文件以及 web.xml,那么这些自定义的配置类 Tomcat 容器如何知道它们是用于配置框架的呢?解决思路得从 Tomcat 加载 Web 核心配置文件 web.xml 的机制入手分析。

在 Servlet3.0 环境中,Tomcat 会自动在类路径中查找 javax.servlet.ServletContainerInitializer 接口的实现类,如果找到该接口的实现类就用该类来配置 Servlet 容器。

在 Spring 中提供了 ServletContainerInitializer 接口的实现类,名为 org.springframework.web.SpringServletContainerInitializer,SpringServletContainerInitializer 反过来又会查找 org.springframework.web.WebApplicationInitializer 接口的实现类,并将配置任务交给 WebApplicationInitializer 的实现类来完成。

Spring3.2 及以上版本提供了 WebApplicationInitializer 接口的基础实现类 org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer(没错,类名就是这么长),当我们自定义的配置类继承 AbstractAnnotationConfigDispatcherServletInitializer 并将其部署到 Servlet3.0 容器的时候,容器会自动发现它并该类来配置 Servlet 上下文容器。

在这里插入图片描述

经过以上 Tomcat 加载 web.xml 的机制分析,当使用全注解方式整合 SSM 框架时,有以下几个核心步骤:

  1. 自定义类继承 AbstractAnnotationConfigDispatcherServletInitializer 以替代 web.xml,并在该类中指定 Spring 和 Spring MVC 的配置类,配置 DispatcherServlet 的拦截路径以及编码过滤器等。
  2. 自定义 Spring 的配置类以替代 Spring 的核心配置文件 applicationContext.xml 的功能,仅需通过在该类上使用 @Configuration 注解将该自定义的类标识为配置类,容器便会自动加载其中的配置。
  3. 同理,可以通过新建其它的自定义类来配置 Spring MVC 和 MyBatis。

二、web.xml 的替代配置类

package cn.edu.whut.springbear.uims.config;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import javax.servlet.Filter;

/**
 * web.xml 的替代配置类
 *
 * @author Spring-_-Bear
 * @datetime 2022-06-30 14:35 Thursday
 */
public class WebInitializerConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * 指定 Spring 的配置类
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfiguration.class};
    }

    /**
     * 指定 Spring MVC 的配置类
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfiguration.class};
    }

    /**
     * 指定 DispatchServlet 的拦截路径
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * 自定义过滤器
     */
    @Override
    protected Filter[] getServletFilters() {
        // 编码过滤器解决中文乱码
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter("UTF-8", true);
        // Http 请求方法过滤器以支持 RESTful 风格编程
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
    }

}

三、Spring 的替代配置类

  1. @Import 注解可引入其它的配置类。
  2. @PropertySource 注解可引入 *.properties 数据配置文件。
  3. @ComponentScan 注解配置组件包扫描规则。应注意 controller 包应交由 Spring MVC 的核心配置类扫描,不可在 Spring 的配置类中重复扫描,否则报错。
package cn.edu.whut.springbear.uims.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;

import javax.sql.DataSource;


/**
 * Spring 的配置类以替代 applicationContext.xml
 *
 * @author Spring-_-Bear
 * @datetime 2022-06-30 14:36 Thursday
 */
@Configuration
@Import(MyBatisConfiguration.class)
@PropertySource("classpath:jdbc.properties")
@ComponentScan({"cn.edu.whut.springbear.uims.interceptor", "cn.edu.whut.springbear.uims.mapper", "cn.edu.whut.springbear.uims.service"})
public class SpringConfiguration {

    /**
     * 读取 jdbc.properties 中的配置信息
     */
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.className}")
    private String className;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    /**
     * 配置数据源
     */
    @Bean
    public DataSource getDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(className);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }

}

四、Spring MVC 的替代配置类

  1. @EnableWebMVC 注解用开启注解驱动以支持 Spring MVC 的一些高级功能。
  2. @ComponentScan 配置组件扫描规则,应只扫描 controller 包,其余包交由 Spring 配置类扫描。
package cn.edu.whut.springbear.uims.config;

import cn.edu.whut.springbear.uims.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;

import javax.servlet.ServletContext;
import java.util.List;
import java.util.Properties;


/**
 * Spring MVC 的替代配置类以替代 spring-mvc-config.xml
 *
 * @author Spring-_-Bear
 * @datetime 2022-06-30 14:36 Thursday
 */
@Configuration
@EnableWebMvc
@ComponentScan("cn.edu.whut.springbear.uims.controller")
public class SpringMvcConfiguration implements WebMvcConfigurer {

    /**
     * 配置默认 Servlet 处理静态资源
     */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    /**
     * 以下三个方法均用于配置 Thymeleaf 视图解析器
     * ITemplateResolver -> SpringTemplateEngine -> ViewResolver
     */
    @Bean
    public ITemplateResolver templateResolver() {
        WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        assert webApplicationContext != null;
        ServletContext servletContext = webApplicationContext.getServletContext();
        assert servletContext != null;
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setCharacterEncoding("UTF-8");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        return templateResolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        return templateEngine;
    }

    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }

    /**
     * 视图控制器(不需要处理逻辑,仅实现页面跳转时使用)
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/success").setViewName("success");
    }

    /**
     * 文件上传解析器:需引入 commons-fileupload 依赖
     */
    @Bean
    public CommonsMultipartResolver multipartResolver() {
        CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
        // 允许上传的最大文件大小 10M
        commonsMultipartResolver.setMaxUploadSize(10485760L);
        // 文件统一编码为 uft-8
        commonsMultipartResolver.setDefaultEncoding("utf-8");
        return commonsMultipartResolver;
    }

    /**
     * 拦截器:需要自定义类实现 HandlerInterceptor 接口
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录拦截器:拦截除主页、登录、静态资源外的所有请求
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/", "/login", "/static/**");
    }

    /**
     * 配置异常解析器
     */
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
        Properties prop = new Properties();
        // 当遇到自定义的 InterceptorException 异常时,跳转视图名称为 index 的视图页面,也即 index.html
        prop.setProperty("cn.edu.whut.springbear.uims.exception.InterceptorException", "index");
        exceptionResolver.setExceptionMappings(prop);
        // 为异常信息设置一个属性名 msg,默认在 request 域中进行共享
        exceptionResolver.setExceptionAttribute("msg");
        resolvers.add(exceptionResolver);
    }

}

五、MyBatis 的替代配置类

package cn.edu.whut.springbear.uims.config;

import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;


/**
 * MyBatis 的替代配置类以替代 mybatis-config.xml
 *
 * @author Spring-_-Bear
 * @datetime 2022-06-30 15:07 Thursday
 */
@Configuration
public class MyBatisConfiguration {

    /**
     * SQL 会话工厂
     */
    @Bean("sqlSessionFactory")
    public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource) throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 设置 SqlSessionFactoryBean 的数据源
        sqlSessionFactoryBean.setDataSource(dataSource);
        // 以包为单位设置 POJO 类别名(默认为类名)
        sqlSessionFactoryBean.setTypeAliasesPackage("cn.edu.whut.springbear.uims.pojo");
        // 开启自动驼峰命名转换(Java 成员变量与数据库表字段之间的自动转换)
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        sqlSessionFactoryBean.setConfiguration(configuration);
        // 指定 *Mapper.xml 的文件路径
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        // 设置分页插件
        sqlSessionFactoryBean.setPlugins(new Interceptor[]{getPageInterceptor()});
        return sqlSessionFactoryBean;
    }

    /**
     * 指定 Mapper 接口所在的包
     */
    @Bean
    public MapperScannerConfigurer getMapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("cn.edu.whut.springbear.uims.mapper");
        return mapperScannerConfigurer;
    }

    /**
     * 设置分页插件:需引入 com.github.pagehelper 依赖
     */
    @Bean
    public PageInterceptor getPageInterceptor() {
        PageInterceptor pageInterceptor = new com.github.pagehelper.PageInterceptor();
        Properties properties = new Properties();
        properties.setProperty("value", "true");
        pageInterceptor.setProperties(properties);
        return pageInterceptor;
    }

}

六、结语

SSM 框架整合(基于 XML) 的基础上,通过将一系列的 XML 配置文件转换为配置类,使用配置类的方式使得代码更加优雅简洁、可读性高,笔者优先推荐使用全注解方式进行 SSM 框架的整合。原配置文件与配置类的对应关系如下:

  1. web.xml => WebInitializerConfiguration
  2. applicationContext.xml => SpringConfiguration
  3. spring-mvc-config.xml =>SpringMvcConfiguration
  4. mybatis-config.xml => MyBatisConfiguration

如下图所示,与 XML 配置文件方式相比,工程结构更加清晰明了,代码可维护性更高。

在这里插入图片描述

需要注意的是,在 IDEA 的工程结构设置(快捷键:Ctrl + Alt + S)中,同样必须指定 web 工程的上下文路径为 src\main\webapp,如下图所示:

在这里插入图片描述

至此,成功通过全注解方式整合 Spring、Spring MVC 和 MyBatis 框架,功能与 XML 文件方式并无异处,但代码整体层次更加优雅美观。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐