SSM框架整合过程解析


添加Sring+SpringMVC相关依赖

因为使用的maven,web容器使用的tomcat,所以先在pom.xml中加上:

war

这样项目就会自动将依赖放进WEB-INF下面的lib中,否则会出现一系列NoClassFound等错误。
添加spring-webmvc依赖:


  org.springframework
  spring-webmvc
  ${spring.version}



  javax.servlet
  javax.servlet-api
  4.0.1

只添加一个spring-webmvc就够了
image.png
可以看到已经将相关依赖全部加载进去了。
这里有一个问题,@Contoller和@GetMapping等注解是在spring-web中,然而只有这两个注解,不去配置DispatcherServlet是不会处理请求的,也就是说如果只导入spring-web依赖,而没有spring-webmvc,即使有@Controller,也只是个普通的注解。

配置spring

在web.xml中配置


  org.springframework.web.context.ContextLoaderListener



  contextClass
  
    
    org.springframework.web.context.support.AnnotationConfigWebApplicationContext
  


  contextConfigLocation
  
  com.sk.day31.config.ApplicationConfig

ContextLoaderListener实现了ServletContextListener接口,ServletContextListener在文档中的解释是

该接口的实现可以接收关于它们所处的Web应用程序的Servlet上下文变化的通知。为了接收通知事件,必须在Web应用程序的部署描述符中配置该实现类。

ServletContextListener 是 Java EE 标准接口之一,类似 Jetty,Tomcat,JBoss 等 java 容器启动时便会触发该接口的 contextInitialized。

spring加载流程

  1. 在Web容器启动后,触发ContextLoaderListener,contextInitialized方法被调用,接着调用initWebApplicationContext方法,并将ServletContext当作参数传入
@Override
public void contextInitialized(ServletContextEvent event) {
    initWebApplicationContext(event.getServletContext());
}
  1. 进入initWebApplicationContext方法后,调用createWebApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        ...
		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
        ...
	}
  1. 进入createWebApplicationContext方法, 源码如下:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		Class<?> contextClass = determineContextClass(sc);
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

determineContextClass用来返回一个实现了WebApplicationContext接口的类:

protected Class<?> determineContextClass(ServletContext servletContext) {
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		if (contextClassName != null) {
			try {
				return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load custom context class [" + contextClassName + "]", ex);
			}
		}
		else {
			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
			try {
				return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load default context class [" + contextClassName + "]", ex);
			}
		}
	}

determineContextClass根据web.xml中定义的contextClass,来决定返回的实现类,如果没有配置,则返回默认的XmlWebApplicationContext,最后通过BeanUtils.instantiateClass(contextClass)创建对象。
那么就出现一个问题,XmlWebApplicationContext是什么?
首先要先讲一下BeanFactory,根据官方文档,BeanFactory接口提供了一个高级配置机制,能够管理任何类型的对象。简单来说就是Spring的容器,用来管理各种Bean。

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class MainApp {
   public static void main(String[] args) {
      XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("Beans.xml"));
      HelloWorld obj = (HelloWorld) factory.getBean("helloWorld");
      obj.getMessage();
   }
}

上述例子就是用了BeanFactory的其中一个实现类XmlBeanFactory来创建Bean。
而ApplicationContext是BeanFactory的扩展,提供了更多的功能,例如:

  • 用于访问应用程序组件的Bean工厂方法。从ListableBeanFactory继承而来。
  • 以通用方式加载文件资源的能力。从ResourceLoader接口继承而来。
  • 向注册的听众发布事件的能力。从ApplicationEventPublisher接口继承而来。
  • 解决消息的能力,支持国际化。继承自MessageSource接口。

ApplicationContext常用的实现类有ClassPathXmlApplicationContext,以及上面提到的WebApplicationContext,这些实现类相当于特化,比如ClassPathXmlApplicationContext就是从类路径中获取配置文件:

public class TestSpring {
    @Test
    public void test(){
        ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");         
        Hello hello=(Hello)context.getBean("helloWorld");
        hello.say();
    }
}

WebApplicationContext是专门为web应用准备的,它允许从相对于Web根目录的路径中装载资源配置文件完成初始化工作。而WebApplicationContext最常用的实现类也就是上面的XmlWebApplicationContext了,此实现类会默认从WEB-INF的目录下读取applicationContext.xml来创建容器,当然这个路径也可以自定义。

  1. 再回到前面,determineContextClass返回了XmlWebApplicationContext类,并实例化,将其强转为ConfigurableWebApplicationContext,并执行configureAndRefreshWebApplicationContext
if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
                    ...
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
  1. 进入configureAndRefreshWebApplicationContext方法,通过ServletContext获取web.xml中的contextConfigLocation值,设置到XmlWebApplicationContext中。
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}
    ...
    wac.refresh();
  1. 执行refresh方法
public void refresh() throws BeansException, IllegalStateException {
    Object var1 = this.startupShutdownMonitor;
    synchronized(this.startupShutdownMonitor) {
       //容器预先准备,记录容器启动时间和标记
      prepareRefresh();
      //创建bean工厂,里面实现了BeanDefinition的装载
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      //配置bean工厂的上下文信息,如类装载器等
      prepareBeanFactory(beanFactory);
      try {
        //在BeanDefinition被装载后,提供一个修改BeanFactory的入口
        postProcessBeanFactory(beanFactory);
        //在bean初始化之前,提供对BeanDefinition修改入口,PropertyPlaceholderConfigurer在这里被调用
        invokeBeanFactoryPostProcessors(beanFactory);
        //注册各种BeanPostProcessors,用于在bean被初始化时进行拦截,进行额外初始化操作
        registerBeanPostProcessors(beanFactory);
        //初始化MessageSource
        initMessageSource();
        //初始化上下文事件广播
        initApplicationEventMulticaster();
        //模板方法
        onRefresh();
        //注册监听器
        registerListeners();
        //初始化所有未初始化的非懒加载的单例Bean
        finishBeanFactoryInitialization(beanFactory);
        //发布事件通知
        finishRefresh();
        } catch (BeansException var5) {
            if(this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var5);
            }
            this.destroyBeans();
            this.cancelRefresh(var5);
            throw var5;
        }
    }
}

完成bean的加载。

JavaConfig方式配置ApplicationConfig

@Configuration
@ComponentScan(basePackages = "com.sk.day31", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)})
public class ApplicationConfig {
    
}

效果类似


  -->

配置spring-webmvc

在web.xml中配置:


  dispatcherServlet
  org.springframework.web.servlet.DispatcherServlet
  
    contextClass
    
      
      org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    
  
  
    contextConfigLocation
    com.sk.day31.config.SpringMvcConfig
  
  1


  dispatcherServlet
  /

@Configuration
@ComponentScan(basePackages = "com.sk.day31.controller")
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("classpath:/static/")
                .setCacheControl(CacheControl.maxAge(Duration.ofDays(365)));
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

@EnableWebMvc效果等于mvc:annotation-driven/。
configureDefaultServletHandling方法作用等于mvc:default-servlet-handler/

配置mybatis

添加依赖


  org.springframework
  spring-jdbc
  ${spring.version}


  mysql
  mysql-connector-java
  8.0.28


  com.alibaba
  druid
  1.1.22


  org.mybatis
  mybatis-spring
  2.0.7


  org.mybatis
  mybatis
  3.5.9

spring-jdbc必须添加,不然会报错java.lang.NoClassDefFoundError: org/springframework/dao/support/DaoSupport。

配置类

@Configuration
@PropertySource("classpath:druid.properties")
@MapperScan(basePackages = "com.sk.day31.mapper")
public class MybatisConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DruidDataSource dataSource(){
        DruidDataSource dataSource=new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setTypeAliasesPackage("com.sk.day31.entity");
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        factoryBean.setDataSource(dataSource());
        return factoryBean.getObject();
    }
}

到此就算是整合完毕了