[JavaWeb][Spring][使用注解配置Spring]
文章目录
前言
Spring是JavaWeb开发人员最熟悉的框架了
在Spring 3.0 之前 Spring只能使用xml配置
因为过于繁杂和冗赘
以至于被称之为xml地狱
Spring官方也注意到了这个问题
于是提供了以Java和注解的方式配置Spring启动
Spring+Spring MVC+Spring Data JPA
首先 引入依赖 这个项目采用Gradle构建
依赖项
apply plugin: 'java'
apply plugin: 'idea'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
//Spring依赖 包括核心包context 事务包tx 服务包webmvc 数据对象包jpa
compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2'
compile group: 'org.springframework', name: 'spring-context', version: '4.3.18.RELEASE'
compile group: 'org.springframework', name: 'spring-tx', version: '4.3.18.RELEASE'
compile group: 'org.springframework', name: 'spring-webmvc', version: '4.3.18.RELEASE'
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '1.11.14.RELEASE'
//aspectj切面支持包
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.1'
//apache 文件上传包
compile group: 'commons-fileupload', name: 'commons-fileupload', version: '1.3.3'
// hibernate核心包
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.3.3.Final'
//日志依赖 包括slf4j,log4j2核心包 jcl-slf4j log4j2-slf4j桥接包
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'
compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25'
compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.0'
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.0'
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.0'
compile group: 'org.apache.logging.log4j', name: 'log4j-web', version: '2.11.0'
//jackson依赖 处理spring的json转换
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.5'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.5'
//javax依赖 包括servlet xml jstl spring启动web应用和处理jsp页面依赖这些包
compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0'
compile group: 'javax.servlet', name: 'jstl', version: '1.2'
compile group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1'
//druid数据库连接池
compile group: 'com.alibaba', name: 'druid', version: '1.1.10'
//mysql驱动
runtime group: 'mysql', name: 'mysql-connector-java', version: '8.0.11'
//junit测试依赖
testCompile group: 'junit', name: 'junit', version: '4.12'
}
Spring核心库 不需要像之前那样一个个引入
官方提供了 一个一次引入全部核心库的依赖
compile group: 'org.springframework', name: 'spring-context', version: '4.3.18.RELEASE'
目录结构
java/xyx/my/core/config 目录下用来存放配置类
整合
使用Java配置 那自然要建立配置类
首先是Spring容器自身的启动配置类
Spring 核心
RootConfig.java
package xyz.my.core.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/*
* RootConfig配置 * Spring容器的配置文件 * 等于配置applicationContext.xml
* * 需要Spring管理的Bean
* 在这里注册 */
@Configuration
@EnableAspectJAutoProxy // 相当于 xml 中的 <aop:aspectj-autoproxy/>
@EnableTransactionManagement // 开启注解式事务管理
@ComponentScan(basePackages ={"xyz.my"}, excludeFilters =
{ @ComponentScan.Filter(type = FilterType.ANNOTATION, value = { Controller.class,RestController.class,ControllerAdvice.class}) })
//设置扫描Spring注解的范围 此处不扫描WebMVC的注解 mvc注解交由WebAppConfig配置类处理
public class RootConfig {
}
使用Java配置Spring 主要的工具是注解
@Configuration 标明这是一个Spring配置类 这个标签相当于xml配置的
< context:component-scan/ >
框架在启动的时候会自动扫描 标注有@Configuration注解的类 将其配置数据初始化
@EnableAspectJAutoProxy 启动Spring的切面织入支持 相当于xml中的
< aop:aspectj-autoproxy/>
@EnableTransactionManagement 启动Spring注解式事务支持 类似xml中的
< tx:annotation-driven/>
@ComponentScan 配置启动时Spring要扫描并自动注入的类 以包路径为扫描路径
这里配置了basePackages ={"xyz.my"}
以全部包路径为扫描路径 包含了所有代码 但是通过配置了
@ComponentScan.Filter
扫描过滤器 排除了@Controller
注解标注的类
因为这些类是HTTP请求映射处理类 应由SpringMVC管理 后面会使用SpringMVC的配置类WebAppConfig处理
如果有需要注册在Spring容器应用上下文中的组件
可以使用@Bean注解在RootConfig 中配置
当然也可以使用@Component
注解在单独的配置类中注册 两种方式都可以
接下来是SpringMVC的配置类
SpringMVC的配置相对来说比较复杂
因为SpringMVC处理的资源范围要多得多
SpringMVC官方文档支持 SpringMVC包含有以下几个方面的配置
- 类型转换
- 合法性校验
- 拦截器
- 请求内容体
- 消息转换
- 视图控制器
- 视图解析器
- 静态资源
- 默认Servlet
- 路径匹配
SpringMVC
WebAppConfig.java
package xyz.my.core.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
@EnableWebMvc //等效于<mvc:annotation-driven />
@ComponentScan(basePackages ={"xyz.my.action","xyz.my.actionHandler"},
includeFilters = @ComponentScan.Filter
(type = FilterType.ANNOTATION,
value = {Controller.class,RestController.class
,ControllerAdvice.class}))
public class WebAppConfig extends WebMvcConfigurerAdapter {
/**
* 设置由 web容器处理静态资源
* 相当于 xml中的<mvc:default-servlet-handler/>
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
{ configurer.enable();
}
/**
* 描述 : <注册静态资源处理器>.
*<p><使用方法说明></p>
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("addResourceHandlers");
registry.addResourceHandler("/assets/**").addResourceLocations("/assets/");
registry.addResourceHandler("/libs/**").addResourceLocations("/libs/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/images/**").addResourceLocations("/images/");
registry.addResourceHandler("/template/**").addResourceLocations("/template/");
registry.addResourceHandler("/favicon/**").addResourceLocations("/favicon/");
}
/**
* 描述 : <注册响应拦截器>.
*<p> <使用方法说明> </p> *
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("addInterceptors");
}
/**
* 描述 : <注册JSP视图处理器>.
*<p> <使用方法说明> </p> * @return
*/
@Bean
public ViewResolver JSPviewResolver() {
log.info("JSPViewResolver");
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/jsp/");
viewResolver.setSuffix(".jsp");
viewResolver.setViewClass(JstlView.class);
viewResolver.setOrder(1);
return viewResolver;
}
/**
* 描述 : <注册JSON内容数据转换器>
*<p> <使用方法说明> </p>
* @return
*/
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter rma=new RequestMappingHandlerAdapter();
MappingJackson2HttpMessageConverter Jackson2Converter = new MappingJackson2HttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
supportedMediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE));
supportedMediaTypes.add(MediaType.valueOf(MediaType.APPLICATION_JSON_UTF8_VALUE));
supportedMediaTypes.add(MediaType.valueOf(MediaType.TEXT_PLAIN_VALUE));
Jackson2Converter.setDefaultCharset(Charset.forName("utf-8"));
Jackson2Converter.setSupportedMediaTypes(supportedMediaTypes);
List<HttpMessageConverter<?>> lstConverter=new ArrayList<>();
lstConverter.add(Jackson2Converter);
rma.setMessageConverters(lstConverter);
return rma;
}
/**
* 描述 : <注册上传限制器>.
*<p> <使用方法说明> </p>
* @return
*/
@Bean(name = "multipartResolver")
public CommonsMultipartResolver commonsMultipartResolver(ServletContext servletContext) {
log.info("multipartResolver");
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
commonsMultipartResolver.setServletContext(servletContext);
commonsMultipartResolver.setMaxUploadSize(100 * 1024 * 1024);//上传文件 byte 最大100M
return commonsMultipartResolver;
}
}
WebAppConfig中 @EnableWebMvc
是启用SpringMVC 等效于< mvc:annotation-driven />
它会从 WebMvcConfigurationSupport
导入 Spring MVC 的配置,会在处理请求时加入注解的支持(比如 @RequestMapping
,@ExceptionHandler
等注解
此处也配置了包扫描 扫描路径是action请求分发包的内容和请求异常处理包actionHandler
配置需要扫描的类是Controller.class,ControllerAdvice.class
相比RootConfig,WebAppConfig.java的配置内容就很多了 当然这也是因为HTTP请求处理的复杂性
第一个 启用根Web容器处理静态资源
/**
* 设置由 web容器处理静态资源
* 相当于 xml中的<mvc:default-servlet-handler/>
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
{
configurer.enable();
}
这个配置是为了 将静态资源的处理由SpringMVC的前端控制前DispatchServlet交给默认的Servlet
即Web应用容器(如果使用的是tomcat就是tomcat容器的servlet)处理
因为默认情况下DispatchServlet是会拦截所有的请求路径 即拦截/**
此时就无法对静态资源进行访问 所以必须把静态资源的处理转交给默认的Servlet
使DispatchServlet不再拦截对静态资源的访问 同时也降低了Spring需要处理的请求数量
第二个 注册要处理的静态资源路径
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("addResourceHandlers");
registry.addResourceHandler("/assets/**").addResourceLocations("/assets/");
registry.addResourceHandler("/libs/**").addResourceLocations("/libs/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/images/**").addResourceLocations("/images/");
registry.addResourceHandler("/template/**").addResourceLocations("/template/");
registry.addResourceHandler("/favicon/**").addResourceLocations("/favicon/");
}
前面说了 静态资源由默认的根Servlet来处理
那么哪些是静态资源? 这就需要在这里注册和定义静态资源的目录
使用ResourceHandlerRegistry 注册静态资源到默认Servlet处理
addResourceHandler()
方法定义访问web应用时的路径
addResourceLocations()
方法则定义资源存储的路径 一般是classpath:/js/ 也可以简写成 /js/
或者指定定义为绝对存储路径 此时 写为 **file:D:/data/img_files/**的形式
第三个 注册拦截器
/**
* 描述 : <注册响应拦截器>.
*<p> <使用方法说明> </p> * @return
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("addInterceptors");
registry.addInterceptor(localeChangeInterceptor);
}
拦截器是SpringMVC提供的强大的请求处理机制
但如果不需要 你也可以不配置。 Spring也提供了一些拦截器
比如国际化信息拦截器LocaleChangeInterceptor
动态主题拦截器ThemeChangeInterceptor
可以这样配置
registry.addInterceptor(new LocaleChangeInterceptor());
registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**");
第三个 注册视图处理器
/**
* 描述 : <注册JSP视图处理器>.
*<p> <使用方法说明> </p>
* @return
*/
@Bean
public ViewResolver JSPviewResolver() {
log.info("JSPViewResolver");
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/jsp/");
viewResolver.setSuffix(".jsp");
viewResolver.setViewClass(JstlView.class);
viewResolver.setOrder(1);
return viewResolver;
}
注册一个JSP视图管理器 这样才能使用JSP页面
也可使用其他模板引擎 如Thymeleaf freemarker等
当然如果前后端分离 可以不注册视图处理器
直接使用REST传递JSON数据
第四个 注册请求内容适配器
@Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter rma=new RequestMappingHandlerAdapter();
MappingJackson2HttpMessageConverter Jackson2Converter = new MappingJackson2HttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
supportedMediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE));
supportedMediaTypes.add(MediaType.valueOf(MediaType.APPLICATION_JSON_UTF8_VALUE));
supportedMediaTypes.add(MediaType.valueOf(MediaType.TEXT_PLAIN_VALUE));
Jackson2Converter.setDefaultCharset(Charset.forName("utf-8"));
Jackson2Converter.setSupportedMediaTypes(supportedMediaTypes);
List<HttpMessageConverter<?>> lstConverter=new ArrayList<>();
lstConverter.add(Jackson2Converter);
rma.setMessageConverters(lstConverter);
return rma;
}
现在前后端之间多使用Json交互 所以此处注册了MappingJackson2HttpMessageConverter
这一json请求转换器 并且配置了此转换器支持的媒体类型 包含text/html text/plain application/json
编码字符集为utf8 并将此转换器注册到请求内容适配器RequestMappingHandlerAdapter中
最后一个 CommonsMultipartResolver 很容易 理解
就是配置可上传文件大小的
ServerInitializer
ServerInitializer用来初始化整个web应用
相当于web.xml
必须配置此初始化类 否则Spring应用无法启动
@Slf4j
public class ServerInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
implements WebApplicationInitializer {
/**
* 配置DispatcherServlet 匹配的路径 web过滤路径
*
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 引入其他模块的根配置文件
* 此处引入了核心容器的配置文件RootConfig
* 数据对象框架的配置文件JPAConfig
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
log.info("启动容器配置文件,包括RootConfig,JPAConfig");
return new Class<?>[]
{RootConfig.class, JPAConfig.class};
}
/**
* 引入Spring-MVC模块的根配置文件
*
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
log.info("启动Web容器配置文件,包括WebAppConfig");
return new Class<?>[]
{WebAppConfig.class};
}
/**
* 定义过滤器
* 此处加载了编码字符集过滤器
* @return
*/
@Override
protected Filter[] getServletFilters() {
log.info("加入过滤器 包含字符集编码过滤器");
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
return new Filter[]{characterEncodingFilter};
}
}
Spring Data JPA
SpringDataJPA是一个强大的ORM框架 属于SpringData项目的一部分
但SDJ是一个门面框架 底层需要其他实现了JPA标准的ORM框架支持
包括Hibernate,EclipseLink,OpenJPA等
此处我们使用Hibernate
引入依赖
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.3.3.Final'
JPA 配置类
JPAConfig.class
@Configuration
@EnableJpaRepositories(basePackages="xyz.my.dao") @PropertySource(value="classpath:datasource.properties")
@Slf4j
public class JPAConfig {
@Autowired
private Environment environment;
/**
* 通过数据库连接池配置数据源
* 具体配置选项可以查看
* druid连接池的指南
*/
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(environment.getProperty("db.url"));
dataSource.setUsername(environment.getProperty("db.username"));
dataSource.setPassword(environment.getProperty("db.password"));
dataSource.setDriverClassName(environment.getProperty("db.driverClass"));
dataSource.setTimeBetweenEvictionRunsMillis(
Integer.valueOf(environment.getProperty("db.timeBetweenEvictionRunsMillis")));
dataSource.setPoolPreparedStatements(true);
dataSource.setInitialSize(Integer.valueOf(environment.getProperty("db.initialSize")));
dataSource.setMinIdle(Integer.valueOf(environment.getProperty("db.minIdle")));
dataSource.setMaxActive(Integer.valueOf(environment.getProperty("db.maxActive")));
dataSource.setValidationQuery(environment.getProperty("db.validationQuery"));
dataSource.setMaxWait(Long.valueOf(environment.getProperty("db.maxWait")));
dataSource.setTestWhileIdle(Boolean.getBoolean(
environment.getProperty("db.testWhileIdle")));
try {
dataSource.init();
} catch (SQLException e) {
log.error("数据库连接池启动异常 {}", e);
throw new RuntimeException(e);
}
return dataSource;
}
/**
* 启用JPA事务管理器
*/
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
/**
* 启用JPA数据适配器
* 并指明数据库类型为MySQL
* 自动生成表为false 这个属性在后面JPA属性里配置
*/
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setDatabase(Database.MYSQL);
jpaVendorAdapter.setGenerateDdl(true);
jpaVendorAdapter.setShowSql(true);
return jpaVendorAdapter;
}
/**
* 配置JPA持久化工厂属性
* 设置扫描的数据模型包xyz.my.model
* 配置JPA属性 hibernate.format_sql 格式化SQL语句为true
* 配置JPA属性 hibernate.hbm2ddl.auto 自动生成表为validate(校验模式)
*/
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean lemfb = new LocalContainerEntityManagerFactoryBean();
lemfb.setDataSource(dataSource());
lemfb.setJpaVendorAdapter(jpaVendorAdapter());
lemfb.setPackagesToScan("xyz.my.model");
Properties jpaProperties = new Properties();
jpaProperties.setProperty("hibernate.format_sql", "true");
jpaProperties.setProperty("hibernate.hbm2ddl.auto", "validate");
lemfb.setJpaProperties(jpaProperties);
return lemfb;
}
}
@EnableJpaRepositories(basePackages=“xyz.my.dao”)
启用SpringDataJPA的注解 并指定扫描的dao层接口包为xyz.my.dao
@PropertySource(value=“classpath:datasource.properties”)
引入属性配置文件datasource.properties
将数据库配置写在配置文件中 便于后续修改
datasource.properties内容
db.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
db.username=admin
db.password=admin
db.driverClass=com.mysql.jdbc.Driver
db.initialSize=1
db.maxActive=8
db.minIdle=1
db.testWhileIdle=true
db.testOnBorrow=false
db.timeBetweenEvictionRunsMillis=60000
db.maxWait=60000
db.validationQuery=SELECT 1
这里的配置基本采用Druid的推荐配置 根据本机条件微调 比如maxActive一般等于或稍大于服务器CPU逻辑核心数
url的配置 为db.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
和普通的MySQL配置大不相同 是因为使用了MySQL8.0
MySQL8.0 做了大量的更新 所以必须在连接字符串中显示指定是否启用安全连接 服务时区
启用切面事务管理
@Configuration
@Slf4j
public class TxAdviceAspectJ {
private static final int TX_METHOD_TIMEOUT = 30;
private static final String AOP_POINTCUT_EXPRESSION = "execution(* xyz.my..service.*.*(..))";
@Autowired
private PlatformTransactionManager transactionManager;
@Bean
public TransactionInterceptor txAdvice() {
//按名称匹配事务属性
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
/*事务属性规则器 只读事务,不做更新操作*/
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
//设置事务传播行为 PROPAGATION_NOT_SUPPORTED不支持事务
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED );
/*当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务*/
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));//设置事务回滚行为 遇异常回滚
//设置事务传播行为 REQUIRED需要事务:有事务时按事务执行 无事务时创建一个新事务
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
requiredTx.setTimeout(TX_METHOD_TIMEOUT);//设置事务超时时间
Map<String, TransactionAttribute> txMap = new HashMap<>(10);
/*添加事务自动代理 前缀名匹配*/
/*增删改 匹配变更事务*/
txMap.put("add*", requiredTx);
txMap.put("save*", requiredTx);
txMap.put("insert*", requiredTx);
txMap.put("edit*", requiredTx);
txMap.put("update*", requiredTx);
txMap.put("delete*", requiredTx);
/*查询 匹配只读事务*/
txMap.put("get*", readOnlyTx);
txMap.put("query*", readOnlyTx);
txMap.put("find*", readOnlyTx);
source.setNameMap( txMap );
/*以配置内容 创建事务管理拦截器 注入应用上下文*/
TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
return txAdvice;
}
@Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
log.debug("事务行为处理器切点{}",pointcut.getPointcutExpression());
return new DefaultPointcutAdvisor(pointcut, txAdvice());
} }
以上的配置是使用切面环绕的方式进行事务管理
网上一般的注解式事务管理都是使用**@Transcational**标记实现
但这种方法太麻烦 难道每个持久化方法都要标记吗?
反而不如xml配置方便了
所以在以上采用Java配置了切面环绕的事务管理
public TransactionInterceptor txAdvice() 切面拦截器等同于xml配置(xml配置为简化代码)
<tx:advice id="txadvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" propagation="READONLY" rollback-for="Exception" />
<tx:method name="query*" propagation="READONLY" rollback-for="Exception" />
<tx:method name="find*" propagation="READONLY" rollback-for="Exception" />
<tx:method name="add*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="save*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="del*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="edit*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="list*" propagation="REQUIRED" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
public Advisor txAdviceAdvisor() 注入切点 等同于xml配置
<aop:config>
<aop:pointcut id="serviceMethod" expression="execution(*xyz.my..service.*.*(..))"/>
<aop:advisor pointcut-ref="serviceMethod" advice-ref="txadvice"/>
</aop:config>
日志配置
项目中日志部分显然是必不可少的
在此使用Slf4J+Log4j2
log4j2引入了全新的异步队列日志 比logback要快很多倍
Spring自身使用的是commons-logging 即JCL
我们使用SLF4j作为门面 用它的应用接口编程
底层使用Log4j2输出日志
综合以上 我们需要两大桥接包
jcl-over-slf4j
将jcl的输出桥接到slf4j
log4j-slf4j-imp
将slf4j的日志信息由log4j输出
另外由于是web项目 还需要log4j-web
依赖
依赖项在本文最初有写
在resource目录下 建立log4j的配置文件
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="WARN" monitorInterval="30">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="log4j2:[%d{HH:mm:ss:SSS}] [%p]-%l-%m%n"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="log" fileName="log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS}
%-5level %class{36} %L %M %msg%xEx%n"/>
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
<RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring一些无用的DEBUG信息-->
<logger name="org.springframework" level="info">
</logger>
<!--root 真正引入需要打印的日志内容-->
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="log"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>
到这里 一个基本的Spring Web应用已经配置完毕
业务内容
从持久化层 开始建立
新建一个模型对象 Student
import lombok.Builder;
import lombok.Data;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.sql.Timestamp;
@Entity
@DynamicUpdate
@DynamicInsert
@Table(name = "t_student")
@Data
@Builder
public class Student {
@Id
@Column(name = "id", nullable = false, length = 32)
@GeneratedValue(generator="UserGenerator")
@GenericGenerator(name="UserGenerator", strategy="uuid")
private String id;
private String name;
private Character sex;
private Integer age;
private String grade;
private String aliasname;
private Timestamp intime;
private Timestamp modtime;
}
模型对象 Student 同时也是持久化对象
直接映射到对应的数据库表
@Entity 表示这是一个持久化对象
@Table(name = “t_ student”) 表示映射到对应的数据库表 t _sudent
@DynamicUpdate @DynamicInsert 表示动态插入
即只插入 变更有变化的成员属性
@Id 表示此成员属性为数据库表的主键
@Column(name = “id”, nullable = false, length = 32)
表示此属性对应的数据库表列名为id 不可为空 长度32位
下面两个注解为生成器注解
@GeneratedValue(generator=“UserGenerator”) 生成器名为UserGenerator
@GenericGenerator(name=“UserGenerator”, strategy=“uuid”)
使用UserGenerator 生成UUID类型的数据
生成器主要用于自动生成主键
这是Hibernate的功能 具体查看Hibernate文档
有了持久化对象 可以建立持久化层了
@Repository
public interface StudentDao extends JpaRepository<Student,String> {
List<Student> findByName(String name)throws Exception;
}
持久化层的类很简单 添加@Repository 注解并继承JpaRepository接口即可
不用定义持久化对象的增删改查方法 也不用写其内容
因为SpringDataJPA已经替你生成好了
服务层
@Service
public class FirstService {
@Autowired
private StudentDao studentDao;
public Result getStudent(String id)throws Exception {
Student student=studentDao.findOne(id);
return Result.init().setData("student",student);
}
public Result saveStudent(Student student)throws Exception {
studentDao.save(student);
return Result.init();
}
public Result updateStudent(Student student)throws Exception {
Student newStudent=studentDao.findOne(student.getId());
studentDao.save(newStudent);
return Result.init();
}
public Result deleteStudent(String id)throws Exception {
Student student=studentDao.findOne(id);
studentDao.delete(student);
return Result.init();
}
}
路由层
@RestController
@Slf4j
@RequestMapping("first")
public class FirstAction {
@Autowired
private FirstService firstService;
@GetMapping("student/{id}")
public Result GetStudent(@PathVariable("id")String id)throws Exception{
log.debug("访问get接口 id={}",id);
return Result.init();
}
@PutMapping("student")
public Result addStudent(@RequestBody Student student)throws Exception{
log.debug("访问put接口 Student={}",student);
return Result.init();
}
@PostMapping("student")
public Result updateStudent(@RequestBody Student student)throws Exception{
log.debug("访问post接口 Student={}",student);
return Result.init();
}
@DeleteMapping("student/{id}")
public Result deleteStudent(@PathVariable("id")String id)throws Exception{
log.debug("访问delete接口 id={}",id);
return Result.init();
}
@PutMapping("add")
public Result increaseStudent()throws Exception{
log.debug("访问increaseStudent接口");
return firstService.addStudent();
}
}
至此一个可用的web应用已经全部搭建完毕
Spring+Spring MVC+MyBatis
依赖项
apply plugin: 'java'
apply plugin: 'idea'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
//Spring依赖 包括核心包context 事务包tx 服务包webmvc 数据对象包jpa
compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2'
compile group: 'org.springframework', name: 'spring-context', version: '4.3.18.RELEASE'
compile group: 'org.springframework', name: 'spring-tx', version: '4.3.18.RELEASE'
compile group: 'org.springframework', name: 'spring-webmvc', version: '4.3.18.RELEASE'
compile group: 'org.springframework', name: 'spring-jdbc', version: '4.3.18.RELEASE'
//MyBatis依赖
compile group: 'org.mybatis', name: 'mybatis', version: '3.4.6'
compile group: 'org.mybatis', name: 'mybatis-spring', version: '1.3.2'
//aspectj切面支持包
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.1'
//apache 文件上传包
compile group: 'commons-fileupload', name: 'commons-fileupload', version: '1.3.3'
//日志依赖 包括slf4j,log4j2核心包 jcl-slf4j log4j2-slf4j桥接包
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'
compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25'
compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.0'
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.0'
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.0'
compile group: 'org.apache.logging.log4j', name: 'log4j-web', version: '2.11.0'
//jackson依赖 处理spring的json转换
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.5'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.5'
//javax依赖 包括servlet xml jstl spring启动web应用和处理jsp页面依赖这些包
compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0'
compile group: 'javax.servlet', name: 'jstl', version: '1.2'
compile group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1'
//druid数据库连接池
compile group: 'com.alibaba', name: 'druid', version: '1.1.10'
//mysql驱动
runtime group: 'mysql', name: 'mysql-connector-java', version: '8.0.11'
//junit测试依赖
testCompile group: 'junit', name: 'junit', version: '4.12'
}
整合
Spring SpringMVC的配置和上篇文章的是一样的
只是在整合数据映射框架的配置不一样的
只有Mybatis的配置不一样
MyBatis
要在Spring中使用MyBatis,需要在Spring的配置文件中定义一些类。下面先了解一下这些核心类。
- SqlSessionFactoryBean
Sql会话的工厂 由此类创建数据库访问会话SqlSession
在MyBatis中,SqlSessionFactory可以使用SqISessionFactoryBuilder来创建,而在MyBatis-Spring中,使用SqISessinFacotryBean来代替,可以直接在Spring通过配置文件的形式配置。
-
MapperFactoryBean
数据对象映射的工厂 由此类创建数据对象到数据查询结果集的映射
MapperFactoryBean实现了Spring的FactoryBean接口,通过mapperlnterface属性注入接口,将映射接口变成可以注射的Spring Bean。 -
SqlSessionTemplate
数据访问会话管理
SqlSessinTemplate类负责管理MyBatis的SqlSession,调用MyBatis的SQL映射语句,轻松实现数据库的访问。SqlSessionTernplate是线程安全的。当调用SQL方法时,包括通过从映射器getMapper()方法返回的映射器的方法,SqlSessionTemplate会保证使用的SqlSession是和当前Spring事务相关联的。此外,它管理会话的生命周期,包括必要的关闭、提交和回滚操作。
MyBatisConfig.class
@Configuration
@PropertySource(value="classpath:datasource.properties")
@MapperScan(value="xyz.my.dao")
@Slf4j
public class MyBatisConfig {
@Autowired
private Environment environment;
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(environment.getProperty("db.url"));
dataSource.setUsername(environment.getProperty("db.username"));
dataSource.setPassword(environment.getProperty("db.password"));
dataSource.setDriverClassName(environment.getProperty("db.driverClass"));
dataSource.setTimeBetweenEvictionRunsMillis(Integer.valueOf(environment.getProperty("db.timeBetweenEvictionRunsMillis")));
dataSource.setPoolPreparedStatements(true);
dataSource.setInitialSize(Integer.valueOf(environment.getProperty("db.initialSize")));
dataSource.setMinIdle(Integer.valueOf(environment.getProperty("db.minIdle")));
dataSource.setMaxActive(Integer.valueOf(environment.getProperty("db.maxActive")));
dataSource.setValidationQuery(environment.getProperty("db.validationQuery"));
dataSource.setMaxWait(Long.valueOf(environment.getProperty("db.maxWait")));
dataSource.setTestWhileIdle(Boolean.getBoolean(environment.getProperty("db.testWhileIdle")));
try {
dataSource.init();
} catch (SQLException e) {
log.error("数据库连接池启动异常 {}", e);
throw new RuntimeException(e);
}
return dataSource;
}
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public SqlSessionFactory sqlSessionFactory(ApplicationContext applicationContext)
throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setTypeAliasesPackage("xyz.my.model");
Resource[] mapperLocations = applicationContext.getResources("classpath:mapper/**/*.xml");
sessionFactory.setMapperLocations(mapperLocations);
return sessionFactory.getObject();
}
@Bean
public org.apache.ibatis.session.Configuration MyBatisConfig
(org.apache.ibatis.session.Configuration configuration) {
//不启用强制 参数命名
configuration.setUseActualParamName(false);
//不启用JDBC主键自增策略
configuration.setUseGeneratedKeys(false);
//关闭驼峰命名转换:Table{create_time} -> Entity{createTime}
configuration.setMapUnderscoreToCamelCase(false);
return configuration;
}
}
MyBatis的配置比JPA简单一些
首先是数据源Bean 和JPA的配置没有什么区别
然后配置数据源事务管理器DataSourceTransactionManager
第二个配置数据库会话工厂
@Bean
public SqlSessionFactory sqlSessionFactory(ApplicationContext applicationContext)
throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource());//设置数据源
sessionFactory.setTypeAliasesPackage("xyz.my.model");//设置模型类扫描包目录
Resource[] mapperLocations = applicationContext.getResources("classpath:mapper/**/*.xml");
//设置对象映射文件扫描目录
sessionFactory.setMapperLocations(mapperLocations);
return sessionFactory.getObject();
}
第三个 配置MyBatis属性
@Bean
public org.apache.ibatis.session.Configuration MyBatisConfig
(org.apache.ibatis.session.Configuration configuration) {
//不启用强制 参数命名
configuration.setUseActualParamName(false);
//不启用JDBC主键自增策略
configuration.setUseGeneratedKeys(false);
//关闭驼峰命名转换:Table{create_time} -> Entity{createTime}
configuration.setMapUnderscoreToCamelCase(false);
return configuration;
}
dao层
@Repository public interface StudentDao {
Student get(String id)throws Exception;
Student add(Student student)throws Exception;
Student update(Student student)throws Exception;
Student delete(String id)throws Exception;
}
mapper-xml层
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="xyz.my.dao.StudentDao" >
<sql id="column"> id,class,name,sex,age,grade,aliasname,intime,modtime </sql>
<sql id="key_where"> where id=#{id} </sql>
<!-- 查询 -->
<select id="get" resultType="xyz.my.model.Student" parameterType="java.lang.String">
select
<include refid="column"/>
from t_student
<include refid="key_where"/>
</select>
<select id="find" resultType="xyz.my.model.Student"
parameterType="xyz.my.model.Student">
select
<include refid="column"/>
from t_student
<where>
<if test="id != null and id != ''">and id=#{id}</if>
<if test="class != null and class != ''">and class=#{class}</if>
<if test="name != null and name != ''">and name=#{name}</if>
<if test="sex != null and sex != ''">and sex=#{sex}</if>
<if test="age != null and age != ''">and age=#{age}</if>
<if test="grade != null and grade != ''">and grade=#{grade}</if>
<if test="aliasname != null and aliasname != ''">and aliasname=#{aliasname}</if>
<if test="intime != null">and intime=#{intime}</if>
<if test="modtime != null">and modtime=#{modtime}</if>
</where>
order by intime
</select>
<select id="findAll" resultType="xyz.my.model.Student"
parameterType="xyz.my.model.Student">
select
<include refid="column"/>
from t_student
</select>
<!-- 插入 -->
<insert id="insert" parameterType="xyz.my.model.Student">
insert into t_student (
<trim suffixOverrides=",">
<if test="id != null and id != ''">id,</if>
<if test="class != null and class != ''">class,</if>
<if test="name != null and name != ''">name,</if>
<if test="sex != null and sex != ''">sex,</if>
<if test="age != null and age != ''">age,</if>
<if test="grade != null and grade != ''">grade,</if>
<if test="aliasname != null and aliasname != ''">aliasname,</if>
<if test="intime != null">intime,</if>
<if test="modtime != null">modtime</if>
</trim>
) values (
<trim suffixOverrides=",">
<if test="id != null and id != ''">#{id},</if>
<if test="class != null and class != ''">#{class},</if>
<if test="name != null and name != ''">#{name},</if>
<if test="sex != null and sex != ''">#{sex},</if>
<if test="age != null and age != ''">#{age},</if>
<if test="grade != null and grade != ''">#{age},</if>
<if test="aliasname != null and aliasname != ''">#{aliasname},</if>
<if test="intime != null">{intime},</if>
<if test="modtime != null">#{modtime}</if>
</trim>
)
</insert>
<!-- 更新 -->
<update id="update" parameterType="xyz.my.model.Student">
update t_student
<trim prefix=" set " suffixOverrides=",">
<if test="id != null and id != ''">id=#{id},</if>
<if test="class != null and class != ''">class=#{class},</if>
<if test="name != null and name != ''">name=#{name},</if>
<if test="sex != null and sex != ''">sex=#{sex},</if>
<if test="age != null and age != ''">age=#{age},</if>
<if test="grade != null and grade != ''">and grade=#{grade}</if>
<if test="aliasname != null and aliasname != ''">and aliasname=#{aliasname}</if>
<if test="intime != null">and intime=#{intime}</if>
<if test="modtime != null">and modtime=#{modtime}</if>
</trim>
<include refid="key_where"/>
</update>
<!-- 删除 -->
<delete id="delete" parameterType="xyz.my.model.Student">
delete from t_student
<include refid="key_where"/>
</delete>
</mapper>