手把手搭建 零配置文件的spring项目(java Configuration 代替xml编写配置文件,并脱离web.xml启动web项目)
前言:
要知道,在自我学习的过程中都有一个过渡的阶段,作为刚刚大三的学生,深有体会,书本和课程大纲教授的是基于spring2.5和tomcat3.0以下版本的ssm项目结构,相当于是最基础的一代版本,然而在目前最前沿的技术springboot面前,是跨越了几十个版本的,在掌握了最基础的ssm结构的时候,就想要去解除springboot的东西,内心就会有一些疑问,比如:他是如何做到零配置,怎么做到零web.xml启动web项目,怎么做到默认优于配置的,这篇文章希望带给你有所帮助。
再次之前我也是找了各大论坛,各大社区,去解答疑惑,最后还是落眼在spring.io官网,我发现官网提供的就是我想要的那种效果,
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html
官网里给了一个比较直观的方式去初始化咱们的spring:
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
通过一个类去实现WebApplicationInitializer接口,并且重写onStartup方法来启动咱们的spring
可以看到源码里的注释,第一个是加载springweb应用的配置(就是咱们的ioc容器和mvc的配置),第二个是初始化并注册一个DispatcherServlet通过new的方式。
熟悉springweb应用加载顺序的同学肯定很容易就知道了这里代替了咱们传统smm中的哪一部分:
1、加载springweb应用的配置(mvc和ioc)
<!--通过dispatcherservlet加载mvc的配置-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<!--通过监听器初始化ioc容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:beans.xml</param-value>
</context-param>
2、初始化并注册一个DispatcherServlet
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
官网给出的那一大串代码其实就是代替了以上两段web.xml内的配置文件,那官网是如何做到这么简短的呢?这里就需要提到一个名词java Configuration。
java Configuration:
口头翻译即为java配置,其实就是用java代码去代替xml,使spring纯java加载。
这里其实会有一个误区,不知道刚刚学完ssm的小伙伴会不会有同样的问题,java Configuration 和 注解,会不会想到是同一个东西,刚刚说到了Configuration其实就是用java代码代替xml,注解确实也是起到了同样的作用,但是注解和Configuration 是两个东西,这里我举个例子:
//这是用注解的方式去
@Component
class A{
public A MethodName(){
return new A();
}
}
//这是Configuration
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
可以看到两者是不同的两个东西,在spring官网也是有同样的区分别:
Basic Concepts: @Bean
and @Configuration
AnnotationConfigApplicationContext
所以不要进入这个误区了,好了,最基础的介绍到此结束,咱们开始正式进入零配置的解读。
正文解读:
咱们先回顾一下基于xml配置的ssm结构
1、spring.xml 用于扫描包和整合mybatis,包含了数据源,sqlSessionFactoryBean等等
2、springmvc.xml 用于mvc配置,拦截器,静态资源映射,视图解析器等等
3、web.xml 用于加载以上两个配置文件
OK回顾完毕,然后我们看看,如何用javaconfiguration的方式吧上述全都给代替掉,
在前言中,我也是说道了spring.io官网中给出了web.xml代替的方式即实现WebApplicationInitializer接口,重写onStartup方法,咱们先把源码搬过来,一条一条的读:
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// web.xml 中的 listener 初始化
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
// listener中的加载spring.xml的配置文件内容
ac.register(MyApp.class);
// 用于初始化spring配置文件 web项目可用可不用
ac.refresh();
//创建servlet容器
DispatcherServlet dispatcherServlet = new DispatcherServlet(ac);
// 创建前端控制器去名字为dispatcherServlet <servlet>标签
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
// <随服务器启动加载这个servlet
registration.setLoadOnStartup(1);
// <servlet-mapping>里面的url-patten
registration.addMapping("/*");
}
ok,从上文中,咱们可以看到,在onStartup方法中,传入参数是ServletContext,内部的AnnotationConfigWebApplicationContext其实就是初始化ioc容器加载spring的配置文件,然后通过 ac.register(MyApp.class),将MyApp这个类,注册并加载,细心的同学就会发现,其实这个MyApp.class才是最重要的那个配置文件,即基于Configuration的配置文件,这个文件我先丢个源码,这个里头就是涵盖了我们所有的spring,springmvc的配置内容,包括数据源等等
@Configuration//表名是配置类
@EnableWebMvc//标识可以写mvc的配置
@ComponentScan("xyz.zjhwork")//扫描包的路径 相当于<context:component-scan base-package="cn.yunge">
public class MvcConf implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 字符转换 包括解决中文乱码
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
fastJsonHttpMessageConverter.setSupportedMediaTypes(MediaType.parseMediaTypes("text/html;charset=utf-8"));
converters.add(fastJsonHttpMessageConverter);
}
......
上述文件就是我的MyApp的类,这个名字当然可以随便取,显示申明当前的类是配置类,因为这里我们需要把mvc和spring 的配置丢在一起,所以这里我们还要标明这个类可以写mvc的配置,因此这类里头,我们可以写所以的mvc和spirng的配置,只是要学习一些将xml转换为javaconfiguration的写法。后面我会把这个类放一个基础完整的源码。
ok到这里我们再回顾一下,我们用javaconfiguration代替了那些原来的配置:
1、spring.xml ---》MyApp类
√
2、springmvc.xml ---》 MyApp类
√
3、web.xml ----》onStartup方法
√
好了,到这里相当于我们基于配置方面的东西,已经实现了,但是肯定会有想要知道原因的小伙伴问,凭什么他这里实现一个WebApplicationInitializer
接口就能加载配置,原来的tomcat呢,springboot是不需要配置tomcat 的啊,人是直接启动,根本不需要再启动外置服务器,好,下面我就讲讲,以上说出的两个疑问。
WebApplicationInitializer接口:
在官方文档解读中:
In a Servlet 3.0+ environment, you have the option of configuring the Servlet container programmatically as an alternative or in combination with a web.xml
file. The following example registers a DispatcherServlet
:
这里说到,在servlet3.0+
的环境中:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
你可以通过configuring的方式去代替web.xml
配置,并注册一个dispatcherservlet
,所以,如果你的servlet版本在3.0+以上,只要你实现了WebApplicationInitializer这个接口就可以注册一个dispatcherservlet
,正因为servlet3.0的规范规定,无论你使用的服务器是tomcat还是jetty,在服务器启动的时候都会先执行onStartup这个方法,由此去加载用于编写spring和mvc的配置内容,接下来就是咱们的内置tomcat服务器
内置Tomcat:
首先引入maven坐标
<!--核心包-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.33</version>
</dependency>
<!--加上这个不会报错误和异常-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>8.5.33</version>
</dependency>
注意这里的tomcat版本一定要选择好,基于servlet3.0的tomcat是8.0+版本的,所以低于8.0版本的tomcat都是不支持servlet3.0规范的。
导入内嵌tomcat包之后,我们就可以用一个main函数去启动tomcat,这样,我们就不需要外部去开启tomcat然后再部署项目了:
public class ApplicationStarter {
public static void run(){
//实例化tomcat
Tomcat tomcat = new Tomcat();
//设置端口号为9999
tomcat.setPort(9999);
// 标识tomcat启动为webapp
try {
tomcat.addWebapp("/1","D://test/");
// tomcat启动
tomcat.start();
// tomcat监听用户接入
tomcat.getServer().await();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//启动tomcat
run();
}
}
这样的一个类,相当于就是咱们的spring项目的入口启动器,启动web项目就基于这样一个类,这里比较重要的一个地方就是tomcat.addWebapp
将当前的服务器启动标识为web项目,不然不会去加载咱们的onStartup
方法,
到此,咱们的讲解就结束了,其他需要重视的就是在咱们把xml配置文件改写为configurtion的格式的时候注意写法,下面我就把最基础的配置,即MyApp配置类的内容发出来(一步一步敲出来的),拿去用的时候需要把里头的包名和开发自定义的部分的东西修改掉。
package xyz.zjhwork.springApplicationStarter.mvcConf;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import xyz.zjhwork.interceptor.LoginInterceptor;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
@Configuration
@EnableWebMvc
@ComponentScan("xyz.zjhwork")
public class MvcConf implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 字符转换 包括解决中文乱码
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
fastJsonHttpMessageConverter.setSupportedMediaTypes(MediaType.parseMediaTypes("text/html;charset=utf-8"));
converters.add(fastJsonHttpMessageConverter);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截器注册
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/newException").addPathPatterns("/")
.addPathPatterns("/userStatus").addPathPatterns("/userExit").addPathPatterns("/newException").addPathPatterns("/myListException").addPathPatterns("/userInfo")
.addPathPatterns("/isFavByUsernameAndExceptionId").addPathPatterns("/findFavByUsername").addPathPatterns("/deleteFavFromFavByUsernameAndExceptionId").addPathPatterns("/addFavByUsernameAndExceptionId")
.addPathPatterns("/isAproByUsernameAndExceptionId").addPathPatterns("/addAproByUsernameAndExceptionId").addPathPatterns("/insertComment").addPathPatterns("/findHistoryByUsername").addPathPatterns("/userInfoUpdate")
;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css/");
registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/");
registry.addResourceHandler("/img/**").addResourceLocations("classpath:/static/img/");
registry.addResourceHandler("/theme/**").addResourceLocations("classpath:/static/theme/");
// 静态资源存放
registry.addResourceHandler("/*.html").addResourceLocations("classpath:/static/");
}
/**
* mybatisConf
*
* @return
*/
@Bean("pooledDataSource")
public DataSource dataSource() {
//加载db.properties 读取数据库基本信息
Properties pop = new Properties();
try {
pop.load(this.getClass().getClassLoader().getResourceAsStream("db.properties"));
} catch (IOException e) {
e.printStackTrace();
}
PooledDataSource dataSource = new PooledDataSource();
try {
dataSource.setDriver(pop.getProperty("jdbc.driver"));
dataSource.setUsername(pop.getProperty("jdbc.username"));
dataSource.setPassword(pop.getProperty("jdbc.password"));
dataSource.setUrl(pop.getProperty("jdbc.url"));
dataSource.setDefaultAutoCommit(true);
dataSource.setPoolMaximumActiveConnections(20);
dataSource.setPoolMaximumIdleConnections(0);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
@Bean("sqlSessionFactoryBean")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws IOException {
SqlSessionFactory factory;
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//加载mapper.xml
ResourcePatternResolver resolver = new ClassPathXmlApplicationContext();
bean.setMapperLocations(resolver.getResources("classpath*:/daoMappers/*.xml"));
try {
factory = bean.getObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
return factory;
}
@Bean("mapperScannerConfigurer")
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("xyz.zjhwork.dao");
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean");
return mapperScannerConfigurer;
}
}
结束:
本文结束,不知道小伙伴看到全java代码的spring 有没有兴奋的感觉呢!有问题或者需要帮助的同学联系邮箱zjhChester@gmail.com