前言:
隨着spring 越來越強大,用spring4來搭建框架也是很快速,問題是你是對spring了解有多深入。如果你是新手,那么在搭建的過程中可以遇到各種各樣奇葩的問題。
SSM框架的搭建是作為我搭建APP開發框架的基礎。
我將會分以下幾點:
1) 配置文件如何配置
2) 如何java配置啟動servlet
3) 一些搭建過程中的坑
=============================================================================
配置文件如何配置
SSM框架的搭建配置方式有很多種,可具體分可以分為javaConfig配置,xml配置,或者xml文件與javaConf混合配置。其實用那種都行了,我個人搭建還是偏xml配置,不過現在的javaConfig也很成熟,也可以考慮,就是有一些坑,后面會講到的;混合配置,用的情況也是蠻多的,分享中會涉及一點。
接下來,我主要是以java配置類做的配置:
這是項目的結構 com.ouyang.teson目錄下直接放置配置類:
1)AppConfig.java
package com.ouyang.teson; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.context.annotation.*; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.mvc.Controller; /** * Created by ThinkPad on 2017/6/15. */ @Configuration @EnableAspectJAutoProxy @EnableTransactionManagement @ComponentScan(basePackages = {"com.ouyang.teson"}, excludeFilters={@ComponentScan.Filter(type=FilterType.ANNOTATION,value=EnableWebMvc.class)} ) @Import(AppDataConfig.class) //@PropertySource({"classpath:jdbc.properties"}) //@ImportSource("XXXX.xml") /*@Configuration,用於表示這個類是一個配置類,用於配置Spring的相關信息 @EnableAspectJAutoProxy,啟用切面自動代理,用於AOP @EnableTransactionManagement,啟用注解事務,即可以使用@Transactional注解來控制事務 @ComponentScan,組件掃描,在basePackages指定的目錄下掃描被@Controller、@Service、@Component等注解注冊的組件 @Import,引入指定的配置類,我們引入了Spring容器配置類和數據源事務配置類 @PropertySource,加載指定的配置文件,配置文件內容會加載入Environment中等待調用*/ public class AppConfig { @Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); // 跟@MapperScan(basePackages = { "com.ouyang.teson.dao" }) 等同 //如果通過web.xml 加載servlet的話,可能找不到映射對象 建議用注解 mapperScannerConfigurer.setBasePackage("com.ouyang.teson.dao"); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory"); return mapperScannerConfigurer; } }
2) AppDataConfig.java 是導入的配置
package com.ouyang.teson; import com.alibaba.druid.pool.DruidDataSource; import com.ouyang.teson.bean.TestBean; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.*; import org.springframework.core.env.Environment; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; import java.io.IOException; /** * Created by ThinkPad on 2017/6/15. */ @EnableAspectJAutoProxy @EnableTransactionManagement @PropertySource({"classpath:jdbc.properties"}) //@MapperScan(value={"/com/ouyang/teson/dao/*.xml"},basePackages = {"com.ouyang.teson.dao"}) public class AppDataConfig { @Autowired private Environment env; //設置阿里druid數據源 @Bean(name="dataSource") public DataSource getDataSource() { DruidDataSource druidDataSource = new DruidDataSource(); System.out.println("=============env============"+env.getProperty("jdbc.driverClassName")); druidDataSource.setDriverClassName(env.getProperty("jdbc.driverClassName")); druidDataSource.setUsername(env.getProperty("jdbc.username")); druidDataSource.setPassword(env.getProperty("jdbc.password")); druidDataSource.setUrl(env.getProperty("jdbc.url")); //連接超時時間 druidDataSource.setMaxWait(10000); //最大存活時間 //druidDataSource.setMaxActive(10000); // 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 druidDataSource.setTimeBetweenEvictionRunsMillis(60000); //連接池中的最小生存時間 druidDataSource.setMinEvictableIdleTimeMillis(300000); //這里建議配置為TRUE,防止取到的連接不可用 druidDataSource.setTestOnBorrow(true); druidDataSource.setTestOnReturn(false); //自動提交 druidDataSource.setDefaultAutoCommit(true); druidDataSource.setPoolPreparedStatements(true); druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(20); return druidDataSource; } // 配置SqlSessionFactory對象 public SqlSessionFactoryBean getSqSesionFactorys() throws IOException { SqlSessionFactoryBean sqlSessionFactoryBean =new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(getDataSource()); // sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml")); // 這里可以通過mybatis-config.xml 來設置 typeAliasPackage和mapper。 //這個setMapperLocations 必須把mapper文件放resources里面 不然獲取文件 //掃描mybatis配置文件的 PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml")); /* Resource[] mapperLocations = new Resource[] { new ClassPathResource("/com/ouyang/teson/mapper*//**//*.xml") }; sqlSessionFactoryBean.setMapperLocations(mapperLocations);*//* *//* Resource resource =new ClassPathResource("mybatis.xml"); sqlSessionFactoryBean.setConfigLocation(resource);*/ //設置映射的bean類 sqlSessionFactoryBean.setTypeAliasesPackage("com.ouyang.teson.bean"); return sqlSessionFactoryBean; } @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean = getSqSesionFactorys(); SqlSessionFactory sqlSessionFactory = factoryBean.getObject(); sqlSessionFactory.getConfiguration().setMapUnderscoreToCamelCase(true);// 開啟駝峰映射 sqlSessionFactory.getConfiguration().setCacheEnabled(true); //sqlSessionFactory.getConfiguration().setLazyLoadingEnabled(true); sqlSessionFactory.getConfiguration().setAggressiveLazyLoading(false); return sqlSessionFactory; } @Bean(name = "transactionManager") public DataSourceTransactionManager dataSourceTransactionManager() { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(this.getDataSource()); return dataSourceTransactionManager; } @Bean @Scope("prototype") public SqlSessionTemplate sqlSessionTemplate() throws Exception { SqlSessionTemplate template =new SqlSessionTemplate(sqlSessionFactory()); return template; } }
3)webConfig.java
package com.ouyang.teson; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; 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.WebMvcConfigurerAdapter; import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.servlet.view.InternalResourceViewResolver; /** * Created by ThinkPad on 2017/6/15. */ @Configuration @EnableWebMvc @ComponentScan(basePackages = {"com.ouyang.teson"},useDefaultFilters = true) public class WebConfig extends WebMvcConfigurerAdapter{ private final static Logger logger = LoggerFactory.getLogger(WebConfig.class); public ViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/views/jsp/function/"); viewResolver.setSuffix(".jsp"); return viewResolver; } //靜態文件 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { logger.info("addResourceHandlers"); registry.addResourceHandler("/static/**").addResourceLocations("/WEB-INF/static/"); } }
SpringMvc也設置了@ComponentScan,也會去掃相應的包,useDefaultFilters = true 會自動過濾 @Component
@Repository
, @Service
, or @Controller
一些重復的;
4) resources 配置目錄下 主要是兩個文件:1,jdbc.properties ;2,logback.xml
以下是logback.xml,還稍微比較亂,拿其他項目的配置,順便改了一下
<?xml version="1.0" encoding="UTF-8"?> <configuration> <property name="LOG_HOME" value="D:/logs2/" /> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{36}] - %msg%n </pattern> </encoder> </appender> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_HOME}/dts_rpc_main.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- rollover daily --> <fileNamePattern>${LOG_HOME}/logbak/dts_rpc_main-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <!-- or whenever the file size reaches 300MB --> <maxFileSize>1000MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <!-- 日志輸出格式 --> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n </pattern> </encoder> </appender> <logger name="com.ouyang.teson" level="INFO" additivity="false"> <appender-ref ref="FILE" /> </logger> <logger name="org.springframework" level="WARN" additivity="false"> <appender-ref ref="STDOUT" /> </logger> <logger name="org.springframework.boot" level="WARN" additivity="false"> <appender-ref ref="STDOUT" /> </logger> <logger name="org.mybatis" level="WARN" additivity="false"> <appender-ref ref="STDOUT" /> </logger> <logger name="org.apache" level="WARN" additivity="false"> <appender-ref ref="STDOUT" /> </logger> <logger name="com.ouyang" level="DEBUG" additivity="false"> <appender-ref ref="STDOUT" /> </logger> <logger name="com.alibaba" level="DEBUG" additivity="false"> <appender-ref ref="STDOUT" /> </logger> <root level="DEBUG"> <appender-ref ref="STDOUT" /> </root> </configuration>
5) pom.xml
導入的包,都是當下最新的包
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.ouyang</groupId> <artifactId>Teson</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>Teson Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <!-- spring版本號 --> <spring.version>4.3.9.RELEASE</spring.version> <!-- log4j日志文件管理包版本 --> <slf4j.version>1.6.6</slf4j.version> <log4j.version>1.2.12</log4j.version> <!-- mybatis版本號 --> <mybatis.version>3.4.4</mybatis.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- AspectJ Runtime --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.6</version> </dependency> <!-- AspectJ Weaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.6</version> </dependency> <!-- log 日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.7</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <!-- mysql驅動包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.29</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.31</version> </dependency> <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies> <build> <finalName>Teson</finalName> <!--<build> <finalName>app</finalName> </build>--> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!--- 為防止 沒有web.xml 情況下打包報錯 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.2</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project>
基本的配置就這樣,有基礎基本都能看得懂
接下來,要說說怎么啟動web服務器了
啟動web服務
1)方法1
在servlet3.0之后,啟動servlet,在spring里是可以直接通過java配置類進行加載servlet的。
springmvc 提供一個類AbstractAnnotationConfigDispatcherServletInitializer,該類創建了DispatcherServlet和ContextLoaderListener:getServletConfigClasses()返回的配置類定義了Spring MVC應用容器中的beans;getRootConfigClasses()返回的配置類定義了Spring應用根容器中的beans。
Spring MVC容器是根容器的子容器,子容器可以看到根容器中定義的beans,反之不行。
注意:通過AbstractAnnotationConfigDispatcherServletInitializer配置DispatcherServlet僅僅是傳統的web.xml文件方式的另一個可選項。盡管你也可以使用AbstractAnnotationConfigDispatcherServletInitializer的一個子類引入web.xml文件來配置,但這沒有必要。
可以參考文章:https://segmentfault.com/a/1190000004343063?_ea=575820
所以你只要自定一個類繼承AbstractAnnotationConfigDispatcherServletInitializer ,你可以刪掉web.xml了
webAppInitalzer.java
package com.ouyang.teson; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; /** * Created by ThinkPad on 2017/6/15. */ public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { AppConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[] { WebConfig.class }; } /** * identifies one or more paths that DispatcherServlet will be mapped to.<br> * In this case, it’s mapped to /, indicating that it will be the application’s default servlet.<br> * It will handle all requests coming into the application. */ @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
2) 方法2
你還是可以依然使用web.xml 來加載servlet,以下是web.xml的配置
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <context-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </context-param> <context-param> <param-name>contextClassLocation</param-name> <param-value>com.ouyang.teson.AppConfig</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>springServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </init-param> <init-param> <param-name>contextConfigLocations</param-name> <param-value>com.ouyang.teson.WebConfig</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <display-name>Archetype Created Web Application</display-name> </web-app>
一些坑的總結
1)@PropertiesSource 該注解主要是導入配置屬性的,可是如果該配置類里面,如果有@bean的方法加載失敗,則會直接報@PropertiesSource的屬性找不到,而是定位到具體的@bean配置出現問題。
查看源碼,確實很難debug代碼到具體的問題,層次太深了。
2)同一個配置類,@bean的方法,在處理@bean時,是把配置加載到List集合,然后統一實例化,如果有@bean的方法是通過ContentServlet去獲取bean的可能獲取不到而直接報錯。
ConfigurationClassParser.java 是直接加載配置類注解的主要邏輯處理,處理流程是先加載配置文件@PropertiesSource,然后處理 ComponentScans注解,加載文件,接着是處理@Import 和@importSource注解,調用processImports(configClass, sourceClass, getImports(sourceClass), true)進行處理,處理導入的配置類如果沒有異常,就會把bean加載到serveltcontenx里,在主配置類就可以注入使用,如果注入不了,說明導入的配置類有問題,接着處理配置中@Bean的方法,接着processInterfaces(configClass, sourceClass)處理繼承接口的默認方法。其實這個過程中,是有重復加載的,@bean的方法實例化的對象也是有重復的過程,具體怎么實現沒有跟蹤下去。 有時間可以自己去跟蹤一下代碼。
3) 還有一個坑,就是mybatis的配置,PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml")); 這里配置到com.ouyang.teson.mapper 不起作用,原因是maven打包的時候不會把classpath下的xml打包進去,不過在resources下的配置文件是可以被打包進去的,所有必須把xml文件放到resources下面,才能在命名空間,映射到具體的sql。
建議:
1,配置的時候建議一步步來,不要一下配置得太多,而且一邊配置,一邊測試,那是最好的,避免配置太多無法一下子定位問題。