如何一步步消灭spring,mybatis以及springmvc的xml,这篇文章会慢慢告诉你,同时也会给出保持xml的方法,只需要消灭你想消灭的部分就行了,顺便学springboot内嵌tomcat。
xml用着是这真的烦,顺便最近看了些springboot源码,然而有些项目还是ssm的,不好换springboot。因此就就尝试把旧的ssm项目改造,这篇文章注重于应用,会尽量告诉你这东西是干嘛的。而不会讲源码,如果有兴趣,等下次有机会再拉出来讲吧,让我们开始吧。
目录
maven依赖
消灭Spring.xml
写一个扫描包的Component
读取这个特殊的Component
消灭mybatis.xml
扫描mapper接口
搞定mapper.xml文件
配置数据库连接池
所有mybatis配置
消灭*Mapper.xml
消灭SpringMvc.xml
消灭web.xml
内嵌Tomcat
读取Properties文件
4.0.0
com.ssm
ssm-test
1.0-SNAPSHOT
javax.servlet
javax.servlet-api
3.1.0
provided
org.springframework
spring-context-support
5.2.8.RELEASE
org.springframework
spring-aop
5.2.8.RELEASE
org.slf4j
slf4j-nop
1.7.2
org.springframework
spring-jdbc
5.2.7.RELEASE
com.zaxxer
HikariCP
3.4.5
org.springframework
spring-aspects
5.2.2.RELEASE
mysql
mysql-connector-java
8.0.20
org.mybatis
mybatis-spring
2.0.4
org.mybatis
mybatis
3.5.4
org.springframework
spring-webmvc
5.2.8.RELEASE
org.apache.tomcat.embed
tomcat-embed-core
9.0.37
org.apache.tomcat.embed
tomcat-embed-el
9.0.33
org.apache.tomcat.embed
tomcat-embed-jasper
9.0.20
UTF-8
1.8
1.8
src/main/java
**/*.properties
**/*.xml
false
src/main/resources
org.apache.maven.plugins
maven-compiler-plugin
3.8.0
1.8
utf8
首先,注册bean,这个bean的特殊之处在于,它会扫描其他组件,spring提供了@ComponentScan注解进行扫描。这里由于后面需要配置springmvc,因此需要排除所有带有@Controller的注解
@Configuration
// 排除Controller注解的类
@ComponentScan(basePackages = "com.ssm.*",
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,
classes = Controller.class)})
public class SpringConfig{}
那么问题来了,不用xml要怎么加载这个特殊的bean呢。答案是spring不止有xml的ClassPathXmlApplicationContext还有AnnotationConfigApplicationContext的上下文容器。
public class MyApplicationContext {
public static void main(String[] args) {
// 此构造方法会自动调用refresh方法启动容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
}
}
你可能发现了,如果此方法写到main方法里,那不就需要在此方法上才能运行容器么?那tomcat是怎么运行的?想知道原因,请看后面。此类只是做个过渡,实际上后面会用其他方法加载,而不是在main函数运行,不然就不能部署在tomcat上了
由于mybatis的配置牵扯的东西较多,下面分个小节阐述
首先消灭mybatis的包的mapper接口的扫描,使用@MapperScan,这个不用我多说吧,springboot也经常用。在刚才的spring的类上加上,顺便开启spring的事务。
@Configuration
@MapperScan(basePackages = "com.ssm.dao")
@ComponentScan(basePackages = "com.ssm.*",
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,
classes = Controller.class)})
@EnableTransactionManagement
public class SpringConfig {}
如果是mybatis.xml要读取*mapper.xml文件,mybatis提供了以下四种方法
首先,mapper.xml留着好还是不留好仁者见仁,就算是springboot,也是很多有些mapper.xml的。不过后面也会提出使用注解代替mapper.xml的方法。
另外,关于上面的xml代码。上面三种方式都还行,一个个写,但是官方并没有提供基于包的扫描方法。只提供了一个参数为Resource数组的扫描方法。因此,本人在这里使用了spring提供的方法了。补充下知识,ResourceLoader是一个顶级接口,所有的ApplicationContext都实现了这个接口,用于读取多种不同类型的Resoutce,xml自然也能读取。之后将此数组传给SqlSessionFactoryBean就行了。这个类后面会解释,到时给出完整代码
/* 读取所有mapper文件 */
ResourceLoader resourceLoader = new DefaultResourceLoader();
ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
String scanPath = "classpath:xml\\*.xml";
Resource[] resources = resolver.getResources(scanPath);
这里用Hikari为例子,这里先写死,后面再使用properties
@Bean
DataSource dataSource(){
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setUsername("root");
hikariDataSource.setPassword("MySQL/80/root:pwd");
hikariDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
hikariDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC&&characterEncoding=UTF-8");
return hikariDataSource;
}
mybatis需要实际需要配置的地方挺多的,因此上面导入了spring-mybatis包,此包提供一个你估计听说过的类,SqlSessionFactoryBean,几乎所有的mybatis配置都可以交给这个类配置。
这里给不熟spring的人提示一下,FactoryBean接口实际上注入的是Factory,而不是FactoryBean对象。也就是最后返回的是SqlSessionFactory,使用SqlSessionFactoryBean是因为配置太多,mybatis会默认帮我们配置一些属性。几乎所有在mybatis中配置的属性都可以在其中配置。配置项挺多的,这里配置几个以举例
@Bean
SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
/* 设置数据库连接池*/
sqlSessionFactoryBean.setDataSource(dataSource);
// // 读取mybatis.xml的配置信息, 此类不能跟setMapperLocations一起使用
// ClassPathResource resource = new ClassPathResource("mybatis.xml");
// sqlSessionFactoryBean.setConfigLocation(resource);
/* 配置默认数据库提供者*/
VendorDatabaseIdProvider provider = new VendorDatabaseIdProvider();
Properties properties = new Properties();
properties.setProperty("MySQL", "mysql");
properties.setProperty("Oracle", "oracle");
provider.setProperties(properties);
sqlSessionFactoryBean.setDatabaseIdProvider(provider);
/* 其他设置*/
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setCacheEnabled(true);
configuration.setDefaultStatementTimeout(5);
// 驼峰命名法
configuration.setMapUnderscoreToCamelCase(true);
sqlSessionFactoryBean.setConfiguration(configuration);
/* 读取所有mapper文件 */
ResourceLoader resourceLoader = new DefaultResourceLoader();
ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
String scanPath = "classpath:xml\\*.xml";
Resource[] resources = resolver.getResources(scanPath);
sqlSessionFactoryBean.setMapperLocations(resources);
return sqlSessionFactoryBean;
}
到此为止,我们消灭了mybatis.xml
mybatis其实提供了注解来注入mapper接口,可以不使用mapper.xml。不过遇到复杂sql,还是建议用xml,注解写起来更麻烦
@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);
@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);
@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);
这个配置要消灭很简单,跟spring差不多,就多了个@EnableMvc的注解以及多一个WebMvcConfigurer接口,这个接口可以做大部分的SpringMvc配置。比如注册拦截器等。
@Configuration
@ComponentScan(basePackages = "com.ssm.controller",
includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,
classes = Controller.class)
})
@EnableWebMvc
public class SpringMVCConfig implements WebMvcConfigurer{
// 注册视图解析器
@Bean
InternalResourceViewResolver InternalResourceViewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/classes/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
要消灭这个,需要用到servlet3.0的标准。具体我就不介绍了,需要扯一些源码。
package com.ssm;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.xml.ws.Dispatch;
import java.util.EnumSet;
// 此接口可以代替web.xnl
public class WebConfig implements WebApplicationInitializer {
public void onStartup(javax.servlet.ServletContext servletContext) throws ServletException {
System.out.println("准备加载web应用");
// 创建一个web容器
AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
webContext.register(SpringMVCConfig.class);
webContext.register(SpringConfig.class);
webContext.setServletContext(servletContext);
// 设置dispatchServlet
DispatcherServlet dispatcherServlet = new DispatcherServlet(webContext);
ServletRegistration.Dynamic addServlet = servletContext.addServlet("dispatchServlet", dispatcherServlet);
addServlet.addMapping("/");
//设置servlet的启动优先级
addServlet.setLoadOnStartup(1);
// 设置请求的监听器
servletContext.addListener(new RequestContextListener());
// 设置字符编码过滤器
FilterRegistration.Dynamic filter = servletContext.addFilter("charsetFilter", new CharacterEncodingFilter());
EnumSet dispatcherTypeEnumSet = EnumSet.allOf(DispatcherType.class);
dispatcherTypeEnumSet.add(DispatcherType.REQUEST);
dispatcherTypeEnumSet.add(DispatcherType.FORWARD);
// 设置过滤请求的范围
filter.addMappingForUrlPatterns(dispatcherTypeEnumSet, true,"/");
webContext.refresh();
}
}
上面的选项中,基本上跟web.xml一一对应,配置dispatchServlet,配置监听器,配置字符集过滤器等等。另外可以看到上面的代码中,我们把spring的bean配置跟springmvc的bean配置都读取进去了。且只有一个容器,当然你也可以创建两个容器,然后把spring容器设为springmvc容器得到父容器。
不过,上面的代码还是比较原始,SpringMvc提供了一个更好的类给我们使用,那就是AbstractAnnotationConfigDispatcherServletInitializer
// 此类有几乎能配置用户用的所有东西,用 各种get配置
public class AppWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//加载model
@Override
protected Class>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
//官方建议在此方法中加载View,Controller
@Override
protected Class>[] getServletConfigClasses() {
return new Class[]{SpringMVCConfig.class};
}
//定义请求映射
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
// 重写父类的此方法进行注册过滤器
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
// @Override
// public void onStartup(ServletContext servletContext) throws ServletException {
// FilterRegistration.Dynamic filter = servletContext.addFilter("charsetFilter", new CharacterEncodingFilter());
// EnumSet dispatcherTypeEnumSet = EnumSet.allOf(DispatcherType.class);
// dispatcherTypeEnumSet.add(DispatcherType.REQUEST);
// dispatcherTypeEnumSet.add(DispatcherType.FORWARD);
// // 设置过滤请求的范围
// filter.addMappingForUrlPatterns(dispatcherTypeEnumSet, true,"/");
// super.onStartup(servletContext);
// }
}
想放在spring容器加载的类就放在RootConfig中。到此为止,消灭了web.xml。可以直接部署在tomcat上运行。如果没成功,可能是servlet版本不对,我也因为版本不对玩了两个小时。。。后来解决方法是新建一个项目,把类都拷过去,没话说。
内嵌tomcat其实没那么牛逼,记得前面导入的几个jar包么,里面就有tomcat对象。。。不过要吐槽的是,我也因为tomcat的版本玩了一下午,求求你别再用版本问题搞我了
public class Start {
public static void run() {
Tomcat tomcat = new Tomcat();
tomcat.addWebapp("/",
"D:\\Users\\Downloads\\forum-master\\src\\main\\webapp");
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(8080);
tomcat.setConnector(connector);
try {
tomcat.start();
} catch (LifecycleException e) {
e.printStackTrace();
}
tomcat.getServer().await();
}
}
都知道springboot有注解可以直接读取properties文件,然而spring没有提供,只能自己注入咯。这里需要用到BeanFactoryPostProcessor。这东西是spring最牛逼的接口之一,时间原因不多讲了。后面取值用@Value注解就可以了
@Component
public class PropertiesProcesor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
// 这里是读取类路径中的配置文件
cfg.setLocation(new ClassPathResource("my.properties"));
cfg.postProcessBeanFactory(configurableListableBeanFactory);
}
}
或者,如果你不用注入,也可以在容器运行前这样写
public class MyApplicationContext {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new ClassPathResource("df.properties"));
cfg.postProcessBeanFactory(factory);
context.addBeanFactoryPostProcessor(cfg);
context.register(ApplicationConfig.class);
context.refresh();
}
ssm零配置到此为止。有机会再分享各个部分的原理。