零配置web.xml实现SSM框架(非Spring Boot方式)

最近看了《Spring实战》这本书,书中提到Spring4.0以后推荐使用javaconfig配置方式,而公司的一个项目使用的是传统的XML配置方式,刚好可以拿来练练手。虽然现在Spring Boot越来越流行,极大的简化了配置过程,使大家不再是面向配置编程[笑哭脸],但还是想研究一下纯javaconfig方式配置,以了解点更深层的东西,现在通过一个demo项目演示如何零配置web.xml实现SSM框架。

目录

1. 配置方式对比图

2. Spring MVC应用上下文配置

3. 添加过滤器Filter、监听器Listener和其他Servlet

4. DispatcherServlet配置类

         配置拦截器

5. 根应用上下文配置类

6. MyBatis数据库配置

7. Redis缓存配置

8. 支持文件上传

9. CORS跨域请求配置

10. 定时任务配置

11. 完成业务代码

12. 测试结果

13. 总结


1. 配置方式对比图

配置对比图

该Demo项目我是使用maven构建的,Spring版本使用的是4.3.9,MySQL版本为8.0,开发工具使用的是IntelliJ IDEA,如何在idea中搭建maven+SpringMVC项目,网上教程很多,这里就不深入讨论了,下面的文章可以参考一下

《使用idea基于maven创建SpringMVC项目》

上面给出了这两种配置方式的对应文件图,先让大家有个整体的印象,后面我会逐一进行讲解,每一部分我都会贴出XML配置方式和Java配置方式的源码对比,以便大家更好的理解,那现在我们就开始吧^_^。

 

2. Spring MVC应用上下文配置

DispatcherServlet是Spring MVC的核心,在这里请求会第一次接触到框架,它来负责将请求路由到其他组件之中。

按照传统的方式,像DispatcherServlet这样的Servlet,以及根应用上下文会配置在web.xml文件中。但是,借助于Servlet 3规范,这种方式已经不是唯一方案了,可以通过扩展AbstractAnnotationConfigDispatcherServletInitializer类来实现。

XML配置方式(/src/main/webapp/WEB-INF/web.xml)

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee         
             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

  <display-name>springmvcdemo</display-name>

  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>

  <!--设置根上下文配置文件位置-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>

  <!--注册ContextLoaderListener-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!--注册DispatcherServlet-->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <!--设置DispatcherServlet上下文配置文件位置-->
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!--将DispatcherServlet映射到“/”-->
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

Java配置方式(/config/WebAppInitializer.java)

package com.mfun.javaconfigdemo.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * 指定根应用上下文配置类
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {RootConfig.class};
    }

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

    /**
     * 配置DispatcherServlet映射
     */
    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }
}

我们扩展AbstractAnnotationConfigDispatcherServletInitializer类,并复写了它的3个方法,该类在部署到Servlet 3.0容器中时,容器会自动发现它,并用来配置Servlet上下文。

  • getRootConfigClasses() 方法返回带有@Configuration注解的类,将会用来配置ContextLoaderListener创建的根应用上下文中的bean,通常是驱动应用后端的中间层和数据层组件,此处使用的RootConfig配置类后面会讲到。该方法对应于web.xml中的contextConfigLocation和ContextLoaderListener处配置。
  • getServletConfigClasses() 方法返回带有@Configuration注解的类,将会用来定义DispatcherServlet应用上下文中的bean,通常是Web组件的bean,此处使用的WebConfig配置类后面会讲到。该方法对应于web.xml中DispatcherServlet处配置。
  • getServletMappings() 方法将一个或多个路径映射到DispatcherServlet上。该方法对应于web.xml中servlet-mapping处配置。

重要提示:此处的java配置方式仅适用于支持Servlet3.0以上版本的Web容器,如Tomcat 7 或更高版本,如果你不能满足该条件,那么别无选择,只能使用web.xml了。

另外,你还需要在maven配置文件pom.xml中添加SpringMVC依赖包:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.9</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>

 

3. 添加过滤器Filter、监听器Listener和其他Servlet

如果你想通过Java配置方式注册过滤器filter、监听器Listener和其他Servlet的话,怎么办呢?别着急往下看:

XML配置方式(/src/main/webapp/WEB-INF/web.xml)

<!-- 字符集过滤器 -->
<filter>
 <filter-name>characterEncodingFilter</filter-name>
 <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
 <init-param>
   <param-name>encoding</param-name>
   <param-value>UTF-8</param-value>
 </init-param>
 <init-param>
   <param-name>forceEncoding</param-name>
   <param-value>true</param-value>
 </init-param>
</filter>
<filter-mapping>
 <filter-name>characterEncodingFilter</filter-name>
 <url-pattern>/*</url-pattern>
</filter-mapping>

<!--支持Restful请求-->
<filter>
 <filter-name>hiddenHttpMethodFilter</filter-name>
 <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
 <filter-name>hiddenHttpMethodFilter</filter-name>
 <url-pattern>/*</url-pattern>
</filter-mapping>

Java配置方式(/config/BaseInitializer.java)

package com.mfun.javaconfigdemo.config;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;

import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;

public class BaseInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) {
        // 字符集过滤器
        FilterRegistration.Dynamic encodingFilter = 
                servletContext.addFilter("charEncodingFilter", CharacterEncodingFilter.class);
        encodingFilter.setInitParameter("encoding", "UTF-8");
        encodingFilter.setInitParameter("forceEncoding", "true");
        // 添加Filter映射路径
        encodingFilter.addMappingForUrlPatterns(null, false, "/*");

        // 支持Restful请求
        FilterRegistration.Dynamic hiddenHttpMethodFilter = 
                servletContext.addFilter("hiddenHttpMethodFilter", HiddenHttpMethodFilter.class);
        hiddenHttpMethodFilter.addMappingForUrlPatterns(null, false, "/*");
    }
}

基于Java的初始化器允许我们定义任意数量的初始化器类。因此,如果我们想往Web容器中注册其他组件的话,只需要创建一个新的初始化器就可以了,最简单的方式就是实现Spring的WebApplicationInitializer接口。其实查看AbstractAnnotationConfigDispatcherServletInitializer类的源码会发现它也实现了该接口,所以能够被Web容器发现。

你可以复写onStartup(),在其中添加Filter、Listener或其他Servlet,如上所示,我们添加了CharacterEncodingFilter和HiddenHttpMethodFilter,也可以添加其他Servlet、Listener:

@Override
public void onStartup(ServletContext servletContext) {
  // 添加自定义Servlet
  ServletRegistration.Dynamic myServlet = 
          servletContext.addServlet("myServlet", MyServlet.class);
  myServlet.addMapping("/custom/**");

  // 添加自定义监听器
  servletContext.addListener(MyListener.class);
}

 

4. DispatcherServlet配置类

DispatcherServlet配置类(WebConfig.java)主要用于扫描注册Web组件bean和其他相关配置,以启用SpringMVC,Web组件bean主要为@Controller或@RestController注解的类。

XML配置方式(/src/main/resources/dispatcher-servlet.xml)

<?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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--启用SpringMVC相关注解支持-->
    <mvc:annotation-driven />

    <context:annotation-config />

    <!--使静态资源的请求直接由Web应用服务器默认的Servlet处理-->
    <mvc:default-servlet-handler />

    <!--仅扫描@Controller注解的类-->
    <context:component-scan
            base-package="com.mfun.springmvcdemo.controller"
            use-default-filters="false">
        <context:include-filter type="annotation"
                                expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

    <!--配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".html"></property>
    </bean>
</beans>

Java配置方式(/config/WebConfig.java)

package com.mfun.javaconfigdemo.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
// 仅扫描controller注解的类,不使用默认的过滤规则
@ComponentScan(basePackages = {"com.mfun.javaconfigdemo.controller"},
    useDefaultFilters = false,
    includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
    })
public class WebConfig extends WebMvcConfigurerAdapter {
    /**
     * 配置视图解析器
     */
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".html");
        return resolver;
    }

    /**
     * 使静态资源的请求直接使用默认servlet处理
     */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

我们通过扩展WebMvcConfigurerAdapter类,并添加@EnableWebMvc注解及复写相关方法,即可配置好Spring WebMvc。

  • @ComponentScan 设置为仅扫描com.mfun.javaconfigdemo.controller包下的@Controller注解的类,必须设置useDefaultFiltersfalse,来禁用默认过滤规则,因为默认过滤规则会扫描所有@Component注解的类,而不仅仅是includeFilters指定的。
  • viewResolver() 方法配置了一个视图解析器。
  • configureDefaultServletHandling() 复写该方法,通过DefaultServletHandlerConfigurer的enable()方法,使DispatcherServlet将静态资源的请求(如图片URL)自动转发到Web容器中的默认Servlet上,如果不这样设置,可能会由于DispatcherServlet中没有对应的映射方法而造成请求失败。

配置拦截器

通过复写该类的addInterceptors方法,可以添加拦截器,如登录拦截器,用于验证用户身份。

@Autowired
LoginInterceptor loginInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(loginInterceptor).addPathPatterns("/**");
}

 

5. 根应用上下文配置类

根应用上下文配置类(RootConfig.java)主要用于扫描注册除Web组件之外的其他bean,以及应用相关配置,该组件通常是驱动应用后端的中间层和数据层组件,如@Service、@Repository等注解的类。

XML配置方式(/src/main/resources/applicationContext.xml)

<?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: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/context
            http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 组件扫描 -->
    <context:component-scan base-package="com.mfun.springmvcdemo">
        <!-- 制定扫包规则 ,不扫描@Controller注解的JAVA类 -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!-- 引入属性文件 -->
    <bean id="propertyConfigurer"
          class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:application.properties</value>
                <value>classpath:application-${spring.profiles.active:dev}.properties</value>
                <value>classpath:log4j.properties</value>
            </list>
        </property>
    </bean>
</beans>

Java配置方式(/config/RootConfig.java)

package com.mfun.javaconfigdemo.config;

import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@PropertySource({
        "classpath:application.properties",
        "classpath:application-${spring.profiles.active:dev}.properties",
        "classpath:log4j.properties",
})
@ComponentScan(basePackages = {"com.mfun.javaconfigdemo"},
    excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {EnableWebMvc.class, Controller.class})
    })
public class RootConfig {
    /**
     * 加载属性文件,同时使@Value注解可用,解析属性占位符
     */
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}
  • @ComponentScan 设置为仅扫描com.mfun.javaconfigdemo包下@Component、@Repository、@Service等注解的类,不扫描@Controller和@EnableWebMvc注解的类(已在WebConfig.java中配置扫描)。
  • PropertySourcesPlaceholderConfigurer 用来加载系统属性、系统环境变量和指定的属性文件中的属性,同时使@Value注解可用,解析属性占位符。
  • @PropertySource 指定需要加载的属性文件,其中 ${spring.profiles.active:dev} 用来根据部署环境来加载不同的属性文件,具体用法可参考 https://blog.csdn.net/swordsnapliu/article/details/78540902

 

6. MyBatis数据库配置

Spring对常用持久化框架都提供了支持,如Hibernate、MyBatis、JDO等,并提供了一些附加服务。由于MyBatis相比Hibernate更加实用和灵活,变得越来越主流,下面我们看下如何配置它吧。

XML配置方式(/src/main/resources/jdbcConfig.xml)

该配置方式需要在/src/main/resources/applicationContext.xml文件里添加

 <import resource="classpath:jdbcConfig.xml" />

来加载JDBC配置文件

<?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:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 配置数据源连接池,此处使用c3p0 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 基础配置 -->
        <property name="jdbcUrl" value="${jdbc.url:}"></property>
        <property name="driverClass" value="${jdbc.driver:}"></property>
        <property name="user" value="${jdbc.username:}"></property>
        <property name="password" value="${jdbc.password:}"></property>
        <!-- 关键配置 -->
        <property name="checkoutTimeout" value="${c3p0.checkoutTimeout:0}" />
        <!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3-->
        <property name="initialPoolSize" value="${c3p0.pool.initSize:3}" />
        <!--连接池中保留的最小连接数-->
        <property name="minPoolSize" value="${c3p0.pool.minSize:3}" />
        <!--连接池中保留的最大连接数-->
        <property name="maxPoolSize" value="${c3p0.pool.maxSize:15}" />
        <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数-->
        <property name="acquireIncrement" value="${c3p0.acquireIncrement:3}" />
        <!--控制数据源内加载的PreparedStatements数量。如果maxStatements-->
        <!--与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0-->
        <property name="maxStatements" value="${c3p0.maxStatements:0}" />
        <!--定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0-->
        <property name="maxStatementsPerConnection" value="${c3p0.maxStatementsPerConnect:0}" />
        <!--最大空闲时间, 空闲时间内未使用则连接被丢弃。若为0则永不丢弃,单位:秒 -->
        <property name="maxIdleTime" value="${c3p0.maxIdleTime:0}" />
        <!--检查连接池中所有空闲连接的间隔时间,单位为秒-->
        <property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod:0}" />
        <!--如果设为 true 那么在归还连接时将校验连接的有效性。 Default: false-->
        <!--异步操作,应用端不需要等待测试结果,但会增加一定的数据库调用开销-->
        <property name="testConnectionOnCheckin" value="${c3p0.testConnectionOnCheckin:false}" />
    </bean>

    <!-- 配置Mybatis工厂 -->
    <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 自动扫描Dao -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.mfun.springmvcdemo.dao"></property>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"></property>
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--启用事务注解支持-->
    <tx:annotation-driven />
</beans>

Java配置方式(/config/JdbcConfig.java)

package com.mfun.javaconfigdemo.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

@Configuration
@EnableTransactionManagement // 启用事务注解
@MapperScan(basePackages={"com.mfun.javaconfigdemo.dao"}, // 自动扫描Mapper
        sqlSessionFactoryRef="sqlSessionFactoryBean")
public class JdbcConfig {
    @Value("${jdbc.driver:}")
    private String driver;

    @Value("${jdbc.url:}")
    private String dbUrl;

    @Value("${jdbc.username:}")
    private String username;

    @Value("${jdbc.password:}")
    private String password;

    @Value("${c3p0.checkoutTimeout:0}")
    private int checkoutTimeout;

    @Value("${c3p0.pool.initSize:3}")
    private int poolInitSize;

    @Value("${c3p0.pool.minSize:3}")
    private int poolMinSize;

    @Value("${c3p0.pool.maxSize:15}")
    private int poolMaxSize;

    @Value("${c3p0.acquireIncrement:3}")
    private int acquireIncrement;

    @Value("${c3p0.maxStatements:0}")
    private int maxStatements;

    @Value("${c3p0.maxStatementsPerConnect:0}")
    private int maxStatementsPerConnect;

    @Value("${c3p0.maxIdleTime:0}")
    private int maxIdleTime;

    @Value("${c3p0.idleConnectionTestPeriod:0}")
    private int idleConnectionTestPeriod;

    @Value("${c3p0.testConnectionOnCheckin:false}")
    private boolean testConnectionOnCheckin;

    /**
     * 配置数据源连接池,此处使用c3p0
     */
    @Bean
    public DataSource dataSource() {
        ComboPooledDataSource cpds = new ComboPooledDataSource();
        try {
            cpds.setJdbcUrl(dbUrl);
            cpds.setDriverClass(driver);
            cpds.setUser(username);
            cpds.setPassword(password);

            // 其他关键配置
            poolConfig(cpds);
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }

        return cpds;
    }

    private void poolConfig(ComboPooledDataSource cpds) {
        // 当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出
        // SQLException,如设为0则无限期等待。单位毫秒,默认为0
        cpds.setCheckoutTimeout(checkoutTimeout);

        // 初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3
        cpds.setInitialPoolSize(poolInitSize);

        // 连接池中保留的最小连接数
        cpds.setMinPoolSize(poolMinSize);

        // 连接池中保留的最大连接数
        cpds.setMaxPoolSize(poolMaxSize);

        // 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数
        cpds.setAcquireIncrement(acquireIncrement);

        // 控制数据源内加载的PreparedStatements数量。如果maxStatements
        // 与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0
        cpds.setMaxStatements(maxStatements);

        // 定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0
        cpds.setMaxStatementsPerConnection(maxStatementsPerConnect);

        // 最大空闲时间, 空闲时间内未使用则连接被丢弃。若为0则永不丢弃,单位:秒
        cpds.setMaxIdleTime(maxIdleTime);

        // 检查连接池中所有空闲连接的间隔时间,单位为秒
        cpds.setIdleConnectionTestPeriod(idleConnectionTestPeriod);

        // 如果设为 true 那么在归还连接时将校验连接的有效性。 Default: false
        // 异步操作,应用端不需要等待测试结果,但会增加一定的数据库调用开销
        cpds.setTestConnectionOnCheckin(testConnectionOnCheckin);
    }

    /**
     * 配置Mybatis工厂
     * @param dataSource
     */
    @Bean(name = "sqlSessionFactoryBean")
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        return sessionFactoryBean;
    }

    /**
     * 配置事务管理器
     * @param dataSource
     */
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}
  • dataSource() 方法注册一个数据源连接池bean,这里使用的是c3p0连接池,用它来连接MySQL数据库。
  • sqlSessionFactoryBean() 方法注册一个MyBatis工厂bean,可以从它获得SqlSession来操作数据库。这里我固定了bean ID,并在@MapperScan中使用。方法参数dataSource的值为 dataSource() 方法生成的数据源bean。
  • @MapperScan 自动扫描指定包下的所有Dao接口,然后包下面的所有接口在编译之后都会生成相应的实现类。
  • @EnableTransactionManagement 该注解开启声明式事务,使用事务时直接在访问数据库的Service方法上添加注解 @Transactional 便可。该注解作用对应于jdbcConfig.xml中的 <tx:annotation-driven /> 配置。
  • transactionManager() 方法配置事务管理器,方法参数dataSource的值为 dataSource() 方法生成的数据源bean。

另外,你还需要在maven配置文件pom.xml中添加数据库相关依赖包,mysql依赖包版本请根据你的使用的mysql版本指定,确保兼容:

 <!--数据库相关依赖包-->
 <!--c3p0数据库连接池-->
 <dependency>
   <groupId>c3p0</groupId>
   <artifactId>c3p0</artifactId>
   <version>0.9.1.2</version>
 </dependency>
 <dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>3.4.2</version>
 </dependency>
 <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>8.0.16</version> <!--mysql8.0以上版本相应的依赖包版本-->
 </dependency>
 <!-- spring/mybatis依赖包包 -->
 <dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis-spring</artifactId>
   <version>1.3.1</version>
 </dependency>
 <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-jdbc</artifactId>
   <version>4.3.9.RELEASE</version>
 </dependency>

 

7. Redis缓存配置

Redis常被用于高并发场景,它是一个高性能的key-value存储系统,和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。 Redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。

XML配置方式(/src/main/resources/cachingConfig.xml)

该配置方式需要在/src/main/resources/applicationContext.xml文件里添加

 <import resource="classpath:cachingConfig.xml" />

来加载Redis缓存配置文件

<?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:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/cache
            http://www.springframework.org/schema/cache/spring-cache.xsd">

    <!-- Redis连接池的设置 -->
    <bean id="redisPoolConfig"
          class="redis.clients.jedis.JedisPoolConfig"
          scope="prototype">
        <!-- redis最大实例数 -->
        <property name="maxTotal" value="${redis.pool.maxActive:8}" />
        <!-- 连接池中最多可空闲maxIdle个连接 ,表示即使没有数据库连接
        时依然可以保持maxIdle个空闲的连接,而不被清除,随时处于待命状态 -->
        <property name="maxIdle" value="${redis.pool.maxIdle:8}" />
        <!-- 最大等待时间:当没有可用连接时,连接池等待连接被归还的最大时间(以毫秒计数),超过时间则抛出异常 -->
        <property name="maxWaitMillis" value="${redis.pool.maxWait:-1}" />
        <!-- 是否在获取连接的时候检查有效性,如果检验失败,则从池中去除连接并尝试取出另一个 -->
        <property name="testOnBorrow" value="${redis.pool.testOnBorrow:false}" />
    </bean>

    <!--Redis连接工厂,此处配置的为单机模式,-->
    <!--如需要高可用,可使用哨兵模式Sentinel或集群模式Cluster-->
    <bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <constructor-arg ref="redisPoolConfig" />
        <property name="database" value="${redis.database:0}" />
        <property name="hostName" value="${redis.hostname:}" />
        <property name="port" value="${redis.port:6379}" />
        <property name="timeout" value="${redis.timeout:2000}" />
    </bean>

    <!--Redis模板-->
    <bean id="myRedisTemplate" class="com.mfun.springmvcdemo.util.MyRedisTemplate">
        <constructor-arg ref="redisConnectionFactory" />
    </bean>

    <!--启用缓存注解支持,如@Cacheable-->
    <cache:annotation-driven />

    <!--Redis缓存管理器,主要用于注解式缓存,如@Cacheable,ID必须为cacheManager,否则会报错-->
    <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg ref="myRedisTemplate" />
        <property name="defaultExpiration" value="${redis.defaultExpiration:0}" />
    </bean>
</beans>

Java配置方式(/config/CachingConfig.java)

package com.mfun.javaconfigdemo.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
@EnableCaching // 启用缓存注解支持,如@Cacheable
public class CachingConfig {
    @Value("${redis.hostname:}")
    private String host;

    @Value("${redis.port:6379}")
    private int port;

    @Value("${redis.database:0}")
    private int database;

    @Value("${redis.timeout:2000}")
    private int timeout;

    @Value("${redis.pool.maxActive:8}")
    private int poolMaxActive;

    @Value("${redis.pool.maxIdle:8}")
    private int poolMaxIdle;

    @Value("${redis.pool.maxWait:-1}")
    private long poolMaxWait;

    @Value("${redis.pool.testOnBorrow:false}")
    private boolean poolTestOnBorrow;

    @Value("${redis.defaultExpiration:0}")
    private long defaultExpiration;

    /**
     * Redis缓存管理器,主要用于注解式缓存,如@Cacheable
     */
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setDefaultExpiration(defaultExpiration);
        return cacheManager;
    }

    /**
     * Redis模板
     */
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory connectionFactory){
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        // 使用Jackson2JsonRedisSerializer替换默认的序列化规则
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        // 调用后初始化方法,没有它将抛出异常
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * Redis连接工厂,此处配置的为单机模式,
     * 如需要高可用,可使用哨兵模式Sentinel或集群模式Cluster
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        JedisConnectionFactory connectionFactory = new JedisConnectionFactory(redisPoolConfig());
        connectionFactory.setDatabase(database);
        connectionFactory.setHostName(host);
        connectionFactory.setPort(port);
        connectionFactory.setTimeout(timeout);

        // 调用后初始化方法,没有它将抛出异常
        connectionFactory.afterPropertiesSet();
        return connectionFactory;
    }

    /**
     * Redis连接池设置
     */
    private JedisPoolConfig redisPoolConfig() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        // redis最大实例数
        poolConfig.setMaxTotal(poolMaxActive);
        // 连接池中最多可空闲maxIdle个连接,
        // 表示即使没有数据库连接时依然可以保持maxIdle个空闲的连接,
        // 而不被清除,随时处于待命状态。
        poolConfig.setMaxIdle(poolMaxIdle);
        // 最大等待时间:当没有可用连接时,
        // 连接池等待连接被归还的最大时间(以毫秒计数),
        // 超过时间则抛出异常
        poolConfig.setMaxWaitMillis(poolMaxWait);
        // 是否在获取连接的时候检查有效性,如果检验失败,则从池中去除连接并尝试取出另一个
        poolConfig.setTestOnBorrow(poolTestOnBorrow);

        return poolConfig;
    }
}
  • @EnableCaching 开启注解驱动的缓存,它会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut)。根据所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或从缓存中移除某个值。该注解作用对应于cachingConfig.xml中的 <cache:annotation-driven />  配置。

@EnableCaching默认模式为AdviceMode.PROXY,,是基于Spring AOP,会造成类内部调用同类方法时,@Cacheable等缓存注解不生效,解决方法:你可以使用@EnableCaching(mode=AdviceMode.ASPECTJ),启用AdviceMode.ASPECTJ模式,同时需要在ClassPath中添加 spring-aspects.jar,注意此模式的注解只能作用于类上,接口上将不生效。
具体可看 https://www.cnblogs.com/cyhbyw/p/8615816.html

  • redisConnectionFactory() 方法配置Redis连接工厂,这里通过Jedis(Redis的Java客户端)连接Redis,为了解决线程安全和资源浪费问题,同时配置了JedisPool。
  • redisTemplate() 方法生成了一个Redis模板bean,通过这个bean我们可以对Redis进行操作。这里我配置的为单机模式, 如需要高可用,可使用哨兵模式Sentinel或集群模式Cluster,这里就不深入讨论了。键值的序列化方式视情况而定,我这里key按String格式序列化,value按Json格式序列化。方法参数connectionFactory的值为 redisConnectionFactory() 方法生成的连接工厂bean。
  • cacheManager() 方法生成一个RedisCacheManager bean,主要用于注解式缓存,它会与一个Redis服务器协作,并通过RedisTemplate将缓存条目存储到Redis中。需要注意的是bean的ID必须为cacheManager,否则使用缓存注解时会报错。方法参数redisTemplate的值为 redisTemplate() 方法生成的Redis模板bean。

另外,你还需要在maven配置文件pom.xml中添加数据库相关依赖包,spring-data-redis依赖包版本请根据你的使用的jedis版本指定,确保兼容:

 <!-- Redis缓存系统的依赖-->
 <!-- Redis客户端jedis依赖 -->
 <dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
   <version>2.9.0</version>
 </dependency>
 <dependency>
   <groupId>org.springframework.data</groupId>
   <artifactId>spring-data-redis</artifactId>
   <!--该版本与jedis2.9.0兼容,不可升高-->
   <version>1.8.1.RELEASE</version>
 </dependency>

 <!-- JSON依赖包 -->
 <dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.9.5</version>
 </dependency>

 

8. 支持文件上传

要支持文件上传,我们必须配置一个multipart解析器,通过它来告诉DispatcherServlet该如何读取multipart请求。Spring内置了2个MultipartResolver的实现供我们选择:

  • CommonsMultipartResolver: 使用Jakarta Commons FileUpload解析multipart请求;
  • StandardServletMultipartResolver: 依赖于Servlet3.0对multipart请求的支持(始于Spring 3.1)。

这里我们以CommonsMultipartResolver举例说明如何配置:

XML配置方式(在 /src/main/resources/dispatcher-servlet.xml 中添加如下代码)

 <!-- 支持上传文件 -->
 <bean id="multipartResolver"
       class="org.springframework.web.multipart.commons.CommonsMultipartResolver" >
     <!-- 100M -->
     <property name="maxUploadSize" value="104857600"></property>
     <property name="defaultEncoding" value="UTF-8"></property>
 </bean>

Java配置方式(在 /config/WebConfig.java 中添加如下代码)

 /**
  * 配置文件上传,bean的id必须为multipartResolver,否则会造成request转换错误
  */
 @Bean("multipartResolver")
 public MultipartResolver multipartResolver(){
     CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
     multipartResolver.setMaxUploadSize(104857600);
     multipartResolver.setDefaultEncoding("UTF-8");
     return multipartResolver;
 }

特别注意:bean的id必须为multipartResolver,否则会造成request转换错误。

 

9. CORS跨域请求配置

跨域指的是JavaScript通过XMLHttpRequest请求数据时,调用JavaScript的页面所在的域和被请求页面的域不一致。对于网站来说,浏览器出于安全考虑是不允许跨域。另外,对于域相同,但端口或协议不同时,浏览器也是禁止的。

一般解决跨域请求问题常用种方法有JSONP、CORS、Nginx反向代理等多种方式,现在较流行的方式是通过CORS解决跨域问题,我们来看看如何配置:

XML配置方式(在 /src/main/resources/dispatcher-servlet.xml 中添加如下代码)

 <!--CORS跨域请求支持-->
 <mvc:cors>
     <mvc:mapping path="/**"
                  allowed-headers="*"
                  allowed-methods="GET,POST,PUT,DELETE,OPTIONS,HEAD"
                  allowed-origins="${cors.allowedOrigins:}"
                  allow-credentials="true"
                  max-age="3600" />
 </mvc:cors>

Java配置方式(在 /config/WebConfig.java中添加如下代码)

 /**
  * CORS跨域请求支持
  */
 @Override
 public void addCorsMappings(CorsRegistry registry) {
     registry.addMapping("/**")
             .allowedHeaders("*") // 允许的header
             .allowedMethods("OPTIONS","HEAD","GET","POST","PUT","DELETE") // 允许的请求方法
             .allowedOrigins(allowedOrigins.split(",")) // 允许的请求域
             .allowCredentials(true)
             .maxAge(3600); // 预检测结果缓存时间
     super.addCorsMappings(registry);
 }

 

10. 定时任务配置

在项目日常开发过程中,经常需要定时任务来帮我们做一些工作,如清理日志,Spring内置了这一功能,配置也非常的简单,如下:

XML配置方式(在 /src/main/resources/applicationContext.xml 中添加如下代码)

<!--启用 @Scheduled 注解支持,实现定时器功能-->
<task:annotation-driven />

Java配置方式(在 /config/RootConfig.java中类级别添加如下注解)

@EnableScheduling
public class RootConfig {
}

配置后在需要定时执行的方法上添加@Scheduled注解即可生效,具体使用方法请自行搜索,网上教程很多。

 

11. 完成业务代码

我仅为了测试搭建的环境是否工作,所以这里的业务逻辑很简单,就是添加用户、修改和获取用户信息,而且没有做太多异常判断之类的工作,懒人说的就是我 [尬笑脸],具体代码如下:

Mysql数据库中创建 tb_user 表

CREATE TABLE 'tb_user'  (
  'id' bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  'name' varchar(30) NOT NULL DEFAULT '',
  'sex' char(2) NOT NULL DEFAULT '',
  'create_time' datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
  'modified_time' datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT,
  PRIMARY KEY ('id')
);

/model/User.java

package com.mfun.javaconfigdemo.model;

import com.fasterxml.jackson.annotation.JsonFormat;

import java.sql.Timestamp;

public class User {
    private Long id;
    private String name;//姓名
    private String sex;//性别
    private Timestamp createTime;//创建时间
    private Timestamp modifiedTime;//修改时间

    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;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    public Timestamp getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Timestamp createTime) {
        this.createTime = createTime;
    }

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    public Timestamp getModifiedTime() {
        return modifiedTime;
    }

    public void setModifiedTime(Timestamp modifiedTime) {
        this.modifiedTime = modifiedTime;
    }
}

/dao/UserDao.java

package com.mfun.javaconfigdemo.dao;

import com.mfun.javaconfigdemo.model.User;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;

@Repository
public interface UserDao {
    @Results(id = "UserDao", value = {
            @Result(column = "id", property = "id"),
            @Result(column = "name", property = "name"),
            @Result(column = "sex", property = "sex"),
            @Result(column = "create_time", property = "createTime"),
            @Result(column = "modified_time", property = "modifiedTime")
    })
    @Select("SELECT * FROM tb_user WHERE id=#{arg0}")
    User getUserById(Long id);

    @Insert("INSERT INTO tb_user(name, sex) VALUES(#{name}, #{sex})")
    @Options(useGeneratedKeys = true)
    int add(User userInfo);

    @Update("UPDATE tb_user SET name=#{arg1.name}, sex=#{arg1.sex}, modified_time=now() WHERE id=#{arg0}")
    int updateById(Long id, User user);
}

/service/UserService.java

package com.mfun.javaconfigdemo.service;

import com.mfun.javaconfigdemo.dao.UserDao;
import com.mfun.javaconfigdemo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {
    private UserDao userDao;

    @Autowired
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    // 调用方法前,先检索该缓存是否存在,如果存在,直接返回缓存中的值,
    // 如果不存在,调用方法后,将结果缓存起来
    @Cacheable(cacheNames="userCache", key="'user_' + #p0")
    public User getUserById(Long id) {
        return userDao.getUserById(id);
    }

    // 使用事务
    @Transactional(rollbackFor = Exception.class)
    // 调用方法后,将结果缓存起来
    @CachePut(cacheNames="userCache", key="'user_' + #p0.getId()")
    public User addUser(User user) throws Exception {
        int result = userDao.add(user);
        if(result <= 0) {
            throw new Exception("用户添加失败");
        }
        User newUser = getUserById(user.getId());
        return newUser;
    }

    // 使用事务
    @Transactional(rollbackFor = Exception.class)
    // 更新用户信息,同时更新缓存
    @CachePut(cacheNames="userCache", key="'user_' + #p0")
    public User updateUser(Long id, User user) throws Exception {
        int result = userDao.updateById(id, user);
        if(result <= 0) {
            throw new Exception("用户更新失败");
        }
        User newUser = getUserById(id);
        return newUser;
    }
}

/controller/UserController.java

package com.mfun.javaconfigdemo.controller;

import com.mfun.javaconfigdemo.model.User;
import com.mfun.javaconfigdemo.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {
    private UserService userService;

    private final static Logger logger = LoggerFactory.getLogger(UserController.class);

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public User getUser(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return user;
    }

    @RequestMapping(method = RequestMethod.POST)
    public User addUser(@RequestBody User user) {
        User newUser = null;
        try {
            newUser = userService.addUser(user);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return newUser;
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        User userUpdated = null;
        try {
            userUpdated = userService.updateUser(id, user);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return userUpdated;
    }
}

 

12. 测试结果

这里我使用idea自带的 Test RESTful Web Service 工具进行测试,Let's go!

  • 添加用户

测试参数:

name: 张三,sex:男

Response返回数据:

数据库记录:


  • 获取用户信息

测试参数:

ID为1的用户信息

Response返回数据:


  • 修改用户信息

测试参数:

修改ID为1的用户信息为 name: 李红,sex: 女

Response返回数据:

数据库记录:

13. 总结

本文提到了关于Spring的两种配置方式,推荐使用类型安全并且比XML更强大的JavaConfig,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。当然对于这两种配置方式并不是互斥关系,相反,他们能够相互融合,你可以根据具体情况灵活使用,从而更有效得完成环境搭建。

源码地址:https://github.com/jming115/javaconfigdemo

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值