SSM 纯注解整合源码分析
1. 概述
在学习完 SSM 后,相信大家都对 SSM 整合中大量配置文件感到非常恼火,特别是在写一些配置文件时因为失误写错了一些字母导致程序一直不能正常运行,然后找 bug 找半天也找不到。在学校 SpringBoot 之前,我们就很有必要来了解通过注解的方式来整合我们的项目,这样能更好的理解 SpringBoot 在底层给我们的封装。同时也是对注解驱动开发的一个很好的复习。
2. Web容器配置类分析(替代 web.xml)
继承树:AbstractAnnotationConfigDispatcherServletInitializer 继承 AbstractDispatcherServletInitializer 继承 AbstractContextLoaderInitializer 继承 WebApplicationInitializer
在Servlet 3.0 规范过后,Spring 给我们提供了一个接口
翻译一下源码文档的注解:WebApplicationInitializer,通过这个接口的源码文档可以知道,通过这个接口我们能够初始化 Web 应用程序所需要的任何东西onStartup 方法是在程序开始时就会调用这个方法
在明白 Spring 给我们提供了这个接口之后,我们将目光转移到AbstractAnnotationConfigDispatcherServletInitializer这个类中
我们要关注的是两个 get 方法
通过注解我们可以得出,这个两个方法是获取 root application context 和 Servlet application context 这两个容器的,此时我们就不难想到 Spring容器 和 SpringMVC 容器不就刚好对应这个两个容器吗,并且这里是注解表示传入 @Configuration 或者 @Component 这两个注解修饰的 class文件 我们不难想到,这里就是让我们传入这两个容器的配置类,从而做到初始化Web 应用的目的,用这个类,就能够完全替代我们之前的 web.xml 文件。
所以我们编写一个自己的配置文件来继承这个类。
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.util.EnumSet;
public class WebAppInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//触发父类的onStartup
super.onStartup(servletContext);
//1.创建字符集过滤器对象
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
//2.设置使用的字符集
characterEncodingFilter.setEncoding("UTF-8");
//3.添加到容器(它不是ioc容器,而是ServletContainer)
FilterRegistration.Dynamic registration = servletContext.addFilter("characterEncodingFilter",characterEncodingFilter);
//4.添加映射
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST,DispatcherType.FORWARD,DispatcherType.INCLUDE),
false,"/*");
//解决跨域的过滤器
// FilterRegistration.Dynamic registration1 = servletContext.addFilter("crossOriginFilter",new CrossOriginFilter());
// registration1.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST,DispatcherType.FORWARD,DispatcherType.INCLUDE),
// false,"/*");
}
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
@Override
protected Class<?>[] **getServletConfigClasses**() {
return new Class[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
getServletMapping方法是AbstractAnnotationConfigDispatcherServletInitializer的父类AbstractDispatcherServletInitializer所提供的。代表 servlet mapping for the DispatcherServlet
在onStartup方法中我们就能做一些初始化操作,例如添加过滤器,设置跨域问题等等,这些都是之前我们在 web.xml 文件中做的事情。
getRootConfigClasses 方法 和 getServletConfigClasses方法 代表分别传入 Spring 和SpringMVC 的配置类。所以我们接下去来编写这两个配置类。
3. SpringMVC 配置类分析
我们来看 servlet 3.1 版本给我们的一个接口:WebMvcConfigurer
方法略。。。
/**
* Defines callback methods to customize the Java-based configuration for
* Spring MVC enabled via {@code @EnableWebMvc}.
*
* <p>{@code @EnableWebMvc}-annotated configuration classes may implement
* this interface to be called back and given a chance to customize the
* default configuration.
*
* @author Rossen Stoyanchev
* @author Keith Donald
* @author David Syer
* @since 3.1
*/
public interface WebMvcConfigurer {}
官方文档给出的解释:启动 SpringMVC 的基于Java的配置。并且 @EnableWebMvc 注解的配置类可以实现和这个接口来回调并有机会自定义默认配置。
很明显,我们只要按照官方的规范就能够以Java的方式来配置SpringMVC
我们再来分析这个接口中的方法
通过方法我们都能够知道,通过这些方法能够给我们的SpringMVC容器中添加哪些配置。例如addResourceHandlers 方法来配置静态资源。
createViewResolver 方法来配置视图解析器。
注意点:要在这个配置类中加上@EnableWebMvc来启动SpringMVC 的配置(前面的文档中也提到这一点)
通过上面的分析我们得到了我们的SpringMVC 配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.VersionResourceResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@ComponentScan(value = "com.lcx.controller")
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry){
registry.addResourceHandler("/js/**","/images/**","/css/**","/html/**")
.addResourceLocations("/js/","/images/","/css/","/html/")
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
/**
* 创建视图解析器
* @return
*/
@Bean
public ViewResolver createViewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/pages/");
viewResolver.setSuffix(".html");
return viewResolver;
}
}
4. Spring 配置类分析
相信大家在学习 Spring 的时候就知道,Spring的主要作用就是作为一个容器,将程序运行过程中所创建的容器都放入这个容器来进行统一管理,由这个容器来负责对象的赋值。即控制反转,依赖注入。我们可以通过**@Configuration**配置将这个类作为一个配置类
/*
* <p>Spring features such as asynchronous method execution, scheduled task execution,
* annotation driven transaction management, and even Spring MVC can be enabled and
* configured from {@code @Configuration} classes using their respective "{@code @Enable}"
* annotations. See
* */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {}
这里的文档也解释了使用 @Enable 可以用来配置注解驱动
于是我们能够得出 Spring 的配置类,在这个类中通过 @Bean 注解能够向 Spring容器 中添加对象。对这个类中的其他注解有疑问的可以去看我的另一篇文章:Spring 注解详解
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@PropertySource("classpath:jdbc.properties")
@ComponentScan(value = "com.lcx",excludeFilters =
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class))
@Import(JdbcConfig.class)
@MapperScan(basePackages = "com.lcx.dao")
@EnableAspectJAutoProxy
@EnableTransactionManagement
public class SpringConfig {
@Bean("sqlSessionFactory")
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
}
5. Mybatis 配置类
整合过 SSM 都知道,Mybatis 的整合主要就是将 Mybatis 的sqlSessionFactory 加入到容器中,还有就是将 相关的DataSource 整合进 Spring 容器中,当我们需要使用时,可以使用 依赖注入的方法进行自动注入,从而使用相关方法。
这里使用的是阿里的 Druid 数据源
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driverClass;
@Value("${jdbc.url}")
private String jdbcUrl;
@Value("${jdbc.userName}")
private String username;
@Value("${jdbc.psw}")
private String psw;
@Bean("dataSource")
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(jdbcUrl);
dataSource.setUsername(username);
dataSource.setPassword(psw);
return dataSource;
}
}
注意: 这里需要用到的 properties 文件在 Spring 配置类中导入了的,所以这样能够直接使用 @Value 注解来直接导入。
jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?charsetEncoding=utf-8
jdbc.userName=
jdbc.psw=
6. 事务支持配置文件
@Configuration
public class TransactionConfig {
@Bean("transactionManager")
public PlatformTransactionManager transactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
要创建一个 **DataSourceTransactionManager(数据源事务管理器)**才能使用事务。
7.编写其他代码(MVC三层)
这里其他业务代码就不给出来了,但是有一个注意点:就是DispatcherServlet 不能返回 Html 文件的问题
8. DispatcherServlet不能返回 html 问题
这里只给出解决方案,在 Tomcat 底层的配置文件中的 web.xml 中加上
<url-pattern>*.html</url-pattern>
效果如下:
想知道原因可以去看另一篇文章:我是如何一步步解决问题 让Spring MVC返回HTML类型的视图
他这篇文章最后面会有乱码问题,但是通过我这样事先配置了一个字符集过滤器,就不会出现这个问题,就能正常的返回 html 页面
9. 总结
在我刚学习SSM的时候也被各种配置文件折腾得很难受,但是不知道其中的原理,只能死记,但是这样总归是不行的,今天抽时间将之前学习的东西分享出来,从源码文档中来探究为什么要这样配置,并且使用注解的方式确实比使用配置文件的方式要好很多,并且知道了原理也能让我们从死记到理解,下次就不会为此而烦恼。并且为以后学习SpringBoot,让我们创建一个Java应用更加的方便。SpringBoot 的底层也使用了大量的注解来达到相关的效果。如果各位对SpringBoot 有兴趣也可以去阅读我的其他文章:SpringBoot核心源码分析