Spring Boot 在企业级开发中的常用技术介绍。
全部章节传送门:
Spring Boot学习笔记(一):Spring Boot 入门基础
Spring Boot学习笔记(二):Spring Boot 运行原理
Spring Boot学习笔记(三):Spring Boot Web开发
Spring Boot学习笔记(四):Spring Boot 数据访问
Spring Boot学习笔记(五):Spring Boot 企业级开发
Spring Boot学习笔记(六):Spring Boot 应用监控
安全控制 Spring Security
Spring Security 是专门针对基于 Spring 的项目的安全框架,充分利用了依赖注入和 AOP 来实现安全的功能。
安全框架有2个重要的概念,即认证(Authentication)和授权(Authorization)。认证即认证用户可以访问当前系统;授权即确定用户在当前系统下所拥有的功能权限。
准备环境
在数据库中创建3张表,分别是用户表、角色表和用户角色关联表,并在表中插入几条数据。
create table t_sys_user(
id int primary key auto_increment,
username varchar(10),
password varchar(20)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into t_sys_user(id, username, password) values(1, 'wyk','wyk');
insert into t_sys_user(id, username, password) values(2, 'gjj','gjj');
create table t_sys_role(
id int primary key auto_increment,
name varchar(15)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into t_sys_role(id, name) values(1, 'ROLE_ADMIN');
insert into t_sys_role(id, name) values(2, 'ROLE_USER');
create table sys_user_roles(
sys_user_id int,
roles_id int,
foreign key(sys_user_id) references t_sys_user(id),
foreign key(roles_id) references t_sys_role(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into sys_user_roles(sys_user_id, roles_id) values(1, 1);
insert into sys_user_roles(sys_user_id, roles_id) values(2, 2);
新建 Spring Boot 项目
新建Spring Boot 的 Web项目,添加相关依赖。
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wyk</groupId>
<artifactId>securitydemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot Security</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- themeleaf针对spring security的扩展包-->
<dependency> <groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
在 application.properties 中添加相关配置。
# 数据库相关
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springstudy?serverTimezone=GMT%2B8
spring.datasource.username=spring
spring.datasource.password=spring
spring.datasource.dirverClassName=com.mysql.jdbc.Driver
# 根据实体类自动维护数据库表结构的功能
spring.jpa.hibernate.ddl-auto=none
# 设置hibernate操作的时候在控制台显示真实的SQL语句
spring.jpa.show-sql=true
# 安全日志级别
logging.level.org.springframework.security=INFO
# 关掉thymeleaf缓存
spring.thymeleaf.cache=false
创建用户和角色
根据数据表定义用户和角色类。
用户:
package com.wyk.securitydemo.domain;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Entity
@Table(name="t_sys_user")
public class SysUser implements UserDetails {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
//用户和角色的映射关系
@ManyToMany(cascade = {CascadeType.REFRESH}, fetch=FetchType.EAGER)
@JoinTable(name="sys_user_roles", joinColumns = {@JoinColumn(name="sys_user_id", referencedColumnName = "id")},
inverseJoinColumns={@JoinColumn(name="roles_id", referencedColumnName = "id")})
private List<SysRole> roles;
//重写,将用户角色作为权限
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
List<SysRole> roles = this.getRoles();
for(SysRole role : roles) {
auths.add(new SimpleGrantedAuthority(role.getName()));
}
return auths;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
public boolean isEnabled() {
return true;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
}
角色:
package com.wyk.securitydemo.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="t_sys_role")
public class SysRole {
@Id
@GeneratedValue
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
另外添加一个用来展示数据的类:
package com.wyk.securitydemo.domain;
public class Msg {
private String title;
private String content;
private String extraInfo;
public Msg(String title, String content, String extraInfo) {
this.title = title;
this.content = content;
this.extraInfo = extraInfo;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getExtraInfo() {
return extraInfo;
}
public void setExtraInfo(String extraInfo) {
this.extraInfo = extraInfo;
}
}
数据访问
本项目的数据访问很简单。
package com.wyk.securitydemo.dao;
import com.wyk.securitydemo.domain.SysUser;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SysUserRepository extends JpaRepository<SysUser, Long> {
//根据用户名查询用户
SysUser findByUsername(String username);
}
自定义 UserDetailService
UserDetailsService接口用于返回用户相关数据。它有loadUserByUsername()方法,根据username查询用户实体,可以实现该接口覆盖该方法,实现自定义获取用户过程。该接口实现类被DaoAuthenticationProvider 类使用,用于认证过程中载入用户信息。
package com.wyk.securitydemo.security;
import com.wyk.securitydemo.dao.SysUserRepository;
import com.wyk.securitydemo.domain.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class CustomUserService implements UserDetailsService {
@Autowired
SysUserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String s) {
SysUser user = userRepository.findByUsername(s);
if(user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
return user;
}
}
配置
Spring MVC 配置:
package com.wyk.securitydemo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 注册访问login转向login.html
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
/**
* 注册静态资源根目录是在static目录
* 估计是收spring security影响,不加此方法无法正确找到静态资源
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// TODO Auto-generated method stub
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
super.addResourceHandlers(registry);
}
}
Spring Security 配置:
package com.wyk.securitydemo.config;
import com.wyk.securitydemo.security.CustomPasswordEncoder;
import com.wyk.securitydemo.security.CustomUserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
UserDetailsService customUserService() {
return new CustomUserService();
}
//通过@Bean注入指定的PasswordEncoder
@Bean
public PasswordEncoder passwordEncoder(){
return new CustomPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//添加自定义user detail service 认证
auth.userDetailsService(customUserService());
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.anyRequest().authenticated() // 所有请求需要认证后访问
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.permitAll() // 定制登录行为,登录页面可任意访问
.and()
.logout().permitAll(); // 定义注销行为,注销页面可任意访问
}
@Override
public void configure(WebSecurity webSecurity) throws Exception {
//忽略静态资源
webSecurity.ignoring().antMatchers("/css/**","/js/**");
}
}
其中使用的加密算法是明文加密,即不加密,如下所示。
package com.wyk.securitydemo.security;
import org.springframework.security.crypto.password.PasswordEncoder;
public class CustomPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
}
前端页面
添加 bootstrap 静态资源,创建登陆页面:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta content="text/html;charset=UTF-8" />
<title>登录页面</title>
<link th:href="@{css/bootstrap.min.css}" rel="stylesheet" />
<style type="text/css">
body {
padding-top: 50px;
}
.starter-template {
padding: 40px 15px;
text-align: center;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Spring Security 演示</a>
</div>
<div>
<ul class="nav navbar-nav">
<li><a th:href="@{/}"> 首页 </a></li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="starter-template">
<p th:if="${param.logout}" class="bg-warning">已成功注销</p>
<p th:if="${param.error}" class="bg-danger">有错误,请重试</p>
<h2>使用帐号密码登录</h2>
<form name="form" th:action="@{/login}" action="/login" method="POST">
<div class="form-group">
<label for="username">帐号</label>
<input type="text" class="form-control" name="username" id="username"
value="" placeholder="账号" />
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" name="password" id="password"
value="" placeholder="密码" />
</div>
<input type="submit" id="login" value="Login" class="btn btn-primary" />
</form>
</div>
</div>
</body>
</html>
首页,需要注意,虽然maven引入的是thymeleaf-extras-springsecurity5,但是这里的命名空间要使用thymeleaf-extras-springsecurity4。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta content="text/html;charset=UTF-8" />
<title sec:authentication="name"></title>
<link th:href="@{css/bootstrap.min.css}" rel="stylesheet" />
<style type="text/css">
body {
padding-top: 50px
}
.starter-template {
padding: 40px 15px;
text-align: center;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Spring Security 演示</a>
</div>
<div>
<ul class="nav navbar-nav">
<li><a th:href="@{/}"> 首页 </a></li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="starter-template">
<h1 th:text="${msg.title}"></h1>
<p class="bg-primary" th:text="${msg.content}"></p>
<div sec:authorize="hasRole('ROLE_ADMIN')">
<p class="bg-info" th:text="${msg.extraInfo}"></p>
</div>
<div sec:authorize="hasRole('ROLE_USER')">
<p class="bg-info">无更多信息显示</p>
</div>
<form th:action="@{/logout}" method="post">
<input type="submit" class="btn btn-primary" value="注销" />
</form>
</div>
</div>
</body>
</html>
测试程序
运行程序,进入登陆界面。
使用普通用户登录。
注销后在使用管理员登录。
批处理 Spring Batch
批处理是一种处理模式,它涉及一系列自动复杂作业的执行而无需用户交互。批处理过程处理批量数据并运行很长时间。一些企业应用程序需要处理大量数据来执行操作,如
- 基于时间的事件,如周期性计算。
- 在大型数据集上重复处理的定期应用程序。
- 处理和验证交易方式中可用数据的应用程序。
Spring Batch 是一个轻量级框架,用于开发在企业应用程序中使用的批处理应用程序。
Spring Batch 结构
Spring Batch 框架架构如下所示。
每个作业Job(需要执行的任务)有1个或者多个作业步Step;每个Step对应一个ItemReader(读取数据的接口)、ItemProcessor(处理数据的接口)、ItemWriter(输出数据的接口); 通过JobLauncher(启动接口)可以启动Job,启动Job时需要从JobRepository(注册的容器)获取存在的JobExecution; 当前运行的Job及Step的结果及状态会保存在JobRepository中。
准备环境
以将一个csv文件中的数据使用JDBC批处理方式插入数据库为例进行讲解。
在MySQL中创建一个数据表。
create table t_person_info (
id int not null primary key,
name varchar(20),
age int,
nation varchar(20),
address varchar(20)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
alter table t_person_info modify id int auto_increment;
新建 Spring Boot 项目
创建 Spring Boot 项目并添加依赖。
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wyk</groupId>
<artifactId>batchdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>batchdemo</name>
<description>Demo project for Spring Boot Batch</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
<!--去除默认加载的hsqldb驱动 -->
<exclusions>
<exclusion>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 校验数据 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
在 application.properties 中添加配置。由于Spring Batch默认会创建数据表,这里要允许创建。
# 数据库相关
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springstudy?serverTimezone=GMT%2B8
spring.datasource.username=spring
spring.datasource.password=spring
spring.datasource.dirverClassName=com.mysql.jdbc.Driver
# 允许覆盖注册
spring.main.allow-bean-definition-overriding=true
# 在数据库里面创建默认的数据表
spring.batch.initialize-schema=ALWAYS
# 显示sql
spring.jpa.show-sql=true
在类路径 src/main/resources 下添加 people.csv 。
王某某,21,汉族,合肥
张某某,33,汉族,北京
李某某,22,藏族,拉萨
赵某某,13,壮族,上海
添加领域模型类
根据数据表添加领域模型类。
package com.wyk.batchdemo.domain;
import javax.validation.constraints.Size;
public class Person {
@Size(max=4, min=2)
private String name;
private int age;
private String nation;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getNation() {
return nation;
}
public void setNation(String nation) {
this.nation = nation;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
添加数据处理和校验
数据处理类:
package com.wyk.batchdemo.batch;
import com.wyk.batchdemo.domain.Person;
import org.springframework.batch.item.validator.ValidatingItemProcessor;
import org.springframework.batch.item.validator.ValidationException;
//数据处理
public class CsvItemProcessor extends ValidatingItemProcessor<Person> {
@Override
public Person process(Person item) throws ValidationException {
//调用自定义校验器
super.process(item);
//校验数据
if(item.getNation().equals("汉族")) {
item.setNation("01");
} else {
item.setNation("02");
}
return item;
}
}
数据校验类:
package com.wyk.batchdemo.batch;
import org.springframework.batch.item.validator.ValidationException;
import org.springframework.batch.item.validator.Validator;
import org.springframework.beans.factory.InitializingBean;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import java.util.Set;
public class CsvBeanValidator<T> implements Validator<T>, InitializingBean {
private javax.validation.Validator validator;
@Override
public void validate(T t) throws ValidationException {
Set<ConstraintViolation<T>> constraintViolations = validator.validate(t);//校验数据
if(constraintViolations.size() > 0) {
StringBuilder message = new StringBuilder();
for(ConstraintViolation<T> constraintViolation : constraintViolations) {
message.append(constraintViolation.getMessage() + "\n");
}
throw new ValidationException(message.toString());
}
}
@Override
public void afterPropertiesSet() throws Exception {
//校验器初始化
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.usingContext().getValidator();
}
}
Job 监听
package com.wyk.batchdemo.batch;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
public class CsvJobListener implements JobExecutionListener {
long startTime;
long endTime;
@Override
public void beforeJob(JobExecution jobExecution) {
startTime = System.currentTimeMillis();
System.out.print("任务开始处理");
}
@Override
public void afterJob(JobExecution jobExecution) {
endTime = System.currentTimeMillis();
System.out.print("任务处理结束");
System.out.println("耗时:" + (endTime - startTime) + "ms");
}
}
添加配置
package com.wyk.batchdemo.batch;
import com.wyk.batchdemo.domain.Person;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.batch.item.validator.Validator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
@EnableBatchProcessing
public class CsvBatchConfig {
@Bean
public ItemReader<Person> reader() throws Exception {
FlatFileItemReader<Person> reader = new FlatFileItemReader<Person>();
reader.setResource(new ClassPathResource("people.csv"));// 设置文件路径
reader.setLineMapper(new DefaultLineMapper<Person>() { // csv文件和领域模型进行映射
{
setLineTokenizer(new DelimitedLineTokenizer() {
{
setNames(new String[]{"name", "age", "nation", "address"});
}
});
setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>() {
{
setTargetType(Person.class);
}
});
}
});
return reader;
}
@Bean
public ItemProcessor<Person, Person> processor() {
CsvItemProcessor processor = new CsvItemProcessor();
processor.setValidator(csvBeanValidator()); //指定校验器
return processor;
}
@Bean
public ItemWriter<Person> writer(DataSource dataSource) {
JdbcBatchItemWriter<Person> writer = new JdbcBatchItemWriter<Person>();
writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<Person>());
String sql = "insert into t_person_info " + "(name,age,nation,address) "
+ "values(:name, :age, :nation, :address)";
writer.setSql(sql); // 设置需要批处理的SQL
writer.setDataSource(dataSource);
return writer;
}
@Bean
public JobRepository jobRepository(DataSource dataSource,
PlatformTransactionManager transactionManager)
throws Exception{
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("mysql");
return jobRepositoryFactoryBean.getObject();
}
@Bean
public SimpleJobLauncher jobLauncher(DataSource dataSource,
PlatformTransactionManager transactionManager)
throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository(dataSource, transactionManager));
return jobLauncher;
}
@Bean
public Job importJob(JobBuilderFactory jobs, Step s1) {
return jobs.get("importJob")
.incrementer(new RunIdIncrementer())
.flow(s1)
.end()
.listener(csvJobListener())
.build();
}
@Bean
public Step step1(StepBuilderFactory stepBuilderFactory, ItemReader<Person> reader,
ItemWriter<Person> writer, ItemProcessor<Person, Person> processor) {
return stepBuilderFactory
.get("step1")
.<Person, Person>chunk(65000) //批处理数据条数
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
@Bean
public CsvJobListener csvJobListener() {
return new CsvJobListener();
}
@Bean
public Validator<Person> csvBeanValidator() {
return new CsvBeanValidator<Person>();
}
}
添加玩配置后已经可以运行程序,执行批量,但如果我们想手动触发任务,还要添加一些设置。
手动触发任务
修改 CsvBatchConfig 类中的 reader 方法。
@Bean
@StepScope
public FlatFileItemReader<Person> reader(@Value("#{jobParameter['input.file.name']}") String pathToFile)
throws Exception {
FlatFileItemReader<Person> reader = new FlatFileItemReader<Person>();
reader.setResource(new ClassPathResource("people.csv"));// 设置文件路径
reader.setLineMapper(new DefaultLineMapper<Person>() { // csv文件和领域模型进行映射
{
setLineTokenizer(new DelimitedLineTokenizer() {
{
setNames(new String[]{"name", "age", "nation", "address"});
}
});
setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>() {
{
setTargetType(Person.class);
}
});
}
});
return reader;
}
然后添加控制器。
package com.wyk.batchdemo.controller;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@Autowired
JobLauncher jobLauncher;
@Autowired
Job importJob;
public JobParameters jobParameters;
@RequestMapping("/imp")
public String imp(String fileName) throws Exception{
String path = fileName + ".csv";
jobParameters = new JobParametersBuilder()
.addLong("time", System.currentTimeMillis())
.addString("input.file.name", path)
.toJobParameters();
jobLauncher.run(importJob, jobParameters);
return "ok";
}
}
运行程序,打开 http://localhost:8080/imp?fileName=people ,即可成功运行批量。
异步消息
异步消息主要用于系统间的通讯。所谓异步消息即消息发送者无需等待消息接收者的处理及返回,甚至无需关心消息是否发送成功。
其中,消息代理(message broker)用来接管发送后的消息。异步消息的2种形式的目的地(destination):队列(queue)用于点对点的通讯,主题(topic)用于发布/订阅式的消息通讯。
企业级消息代理
JMS(Java Message Service)即Java消息服务,而ActiveMQ是一个JMS消息代理的实现。
ActiveMQ 安装
在 Docker 之中安装 ActiveMQ, 首先打开 Docker,执行命令。
docker search activemq
然后安装其中的 ActiveMQ 镜像。
docker pull webcenter/activemq
运行 ActiveMQ 。
docker run -d --name myactivemq -p 61617:61616 -p 8162:8161 webcenter/activemq:latest
访问 http://localhost:8161 ,可以查看 ActiveMQ 的管理界面。
使用 ActiveMQ
新建 Spring Boot 项目,并添加 ActiveMQ 的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
在 applicaton.properties 中配置 ActiveMQ 消息代理的地址。
spring.activemq.broker-url=tcp://localhost:61616
定义消息,实现 MessageCreator 接口,并重写 createMessage 方法。
public class Msg implements MessageCreator {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("这是一个测试消息");
}
}
在运行类中定义消息发送以及目的地,需要实现CommandLineRunner接口。
@SpringBootApplication
public class ActivemqdemoApplication implements CommandLineRunner { // CommandLineRunner接口用于程序启动后执行的代码
@Autowired
JmsTemplate jmsTemplate;
public static void main(String[] args) {
SpringApplication.run(ActivemqdemoApplication.class, args);
}
public void run(String... args) throws Exception {
//向mt-destination目的地发送Msg的消息
jmsTemplate.send("my-destination", new Msg());
}
}
定义消息监听的类,通过 @JmsListener 注解监听消息。
@Component
public class Receiver {
@JmsListener(destination = "my-destination")
public void receiveMessage(String message) {
System.out.println("接收到: <" + message + ">");
}
}
运行程序,会自动向目的地发送消息,控制台会监听到消息。
在 ActiveMQ 的管理端也可以查看目的地的相关信息。