本文章通过实战的方式掌握Spring的企业级开发功能,适合刚开始学习Spring框架的Java开发人员快速上手。
Spring的核心是提供了一个容器(container),通常称为Spring应用上下文(Spring application context),它们会创建和管理应用组件。这些组件也可以称为bean,会在Spring应用上下文中装配在一起,从而形成一个完整的应用程序。
将bean装配在一起的行为是通过一种基于依赖注入(dependency injection,DI)的模式实现的。此时,组件不会再去创建它所依赖的组件并管理它们的生命周期,使用依赖注入的应用依赖于单独的实体(容器)来创建和维护所有的组件,并将其注入到需要它们的bean中。通常,这是通过构造器参数和属性访问方法来实现的。
在历史上,一般通过两种配置方式为Spring应用上下文提供Bean
随着Spring Boot 2.x的引入,Spring自动配置的能力已经大大加强,Spring Boot能够基于类路径中的条目、环境变量和其他因素合理猜测需要配置的组件并将它们装配在一起。Java程序员尽可能多地使用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 https://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.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
可参见本人博客《Maven POM( Project Object Model,项目对象模型 )》及《一图说清maven常见要素》这两篇文章。
/**
* SpringBoot应用
*/
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
// 运行应用
SpringApplication.run(DemoApplication.class, args);
}
}
@SpringBootApplication是一个组合注解,它组合了3个其他的注解。
- @SpringBootConfiguration:将该类声明为配置类。尽管这个类目前还没有太多的配置,但是后续我们可以按需添加基于Java的Spring框架配置。这个注解实际上是@Configuration注解的特殊形式。
- @EnableAutoConfiguration:启用Spring Boot的自动配置。我们随后会介绍自动配置的更多功能。就现在来说,我们只需要知道这个注解会告诉SpringBoot自动配置它认为我们会用到的组件。
- @ComponentScan:启用组件扫描。这样我们能够通过@Component@Controller、@Service这样的注解声明其他类,Spring会自动发现它们并将它们注册为Spring应用上下文中的组件。
8. 测试类
// SpringBoot测试
@SpringBootTest
class DemoApplicationTests {
// 测试方法
@Test
void contextLoads() {
}
}
index()是一个简单的控制器方法。它带有@GetMapping注解,表明如果针对“/”发送HTTP GET请求,那么这个方法将会处理请求。该方法所做的只是返回String类型的index值,该控制器方法中还通过Spring自动注入IndexService服务组件,及调用服务组件方法。
/**
* 第一个SpringMVC程序--Controller层
*
* @author zhuhuix
* @date 2020-07-02
*/
@Controller
public class IndexController {
// Spring注入服务组件
@Autowired
private IndexService indexService;
@GetMapping("/")
public String index(Model model) {
String index = indexService.getIndex();
model.addAttribute("index", index);
// 返回视图 即index.html
return "index";
}
}
getIndex()是一个简单的服务方法。该方法所做的只是返回String类型的index值,该服务组件供控制层调用。
/**
* 第一个SpringMVC程序--Service层
* * @author zhuhuix
* @date 2020-07-02
*/
@Service
public class IndexService {
public String getIndex() {
return "hello world!!!";
}
}
– 使用Thymeleaf模板引擎
### application.properties
###ThymeLeaf配置
spring:
thymeleaf:
#模板的模式,支持 HTML, XML TEXT JAVASCRIPT
mode: HTML5
#编码 可不用配置
encoding: UTF-8
#内容类别,可不用配置
content-type: text/html
#开发配置为false,避免修改模板还要重启服务器
cache: false
#配置模板路径,默认是templates,可以不用配置
prefix: classpath:/templates
– 定义index.html视图层
<html xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8"/>
<title>Titletitle>
head>
<body>
<p th:text="${index}" />
body>
html>
•代码变更后应用会自动重启;
•当面向浏览器的资源(如模板、JavaScript、样式表)等发生变化时,会自动刷新浏览器
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
<scope>runtimescope>
dependency>
...
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<fork>truefork>
configuration>
plugin>
plugins>
build>
. 在上一小节中创建了第一个DEMO,本章将继续基于SpringMVC框架构建我们的web应用,该应用需要实现用户登记,具体实现步骤如下:
/**
* 基于SpringMVC框架开发web应用--用户类
*
* @author zhuhuix
* @date 2020-07-03
*/
public class User implements Serializable {
// 用户id
private Long id;
// 用户名
private String name;
// 邮箱
private String email;
User(){ }
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
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;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
/**
* 基于SpringMVC框架开发web应用--用户服务类
*
* @author zhuhuix
* @date 2020-07-03
*/
@Service
public class UserService {
public static ArrayList<User> users = new ArrayList<>();
// mock数据
public UserService() {
users.add(new User(1L, "Mike", "[email protected]"));
users.add(new User(2L, "Jack", "[email protected]"));
users.add(new User(3L, "Kate", "[email protected]"));
users.add(new User(4L, "Mary", "[email protected]"));
users.add(new User(5L, "Rose", "[email protected]"));
}
// 返回所有的用户
public List<User> listUsers() {
return users;
}
// 增加用户
public User saveUser(User user) {
user.setId(users.size() + 1L);
users.add(user);
return user;
}
}
在Spring MVC框架中,控制器是重要的参与者。它们的主要职责是处理HTTP请求传递给视图以便于渲染HTML(浏览器展现)。
注解 | 描述 |
---|---|
@RequestMapping | 通用的请求 |
@GetMapping | 处理HTTP GET请示 |
@PostMapping | 处理HTTP POST请示 |
@PutMapping | 处理HTTP PUT请示 |
@DeleteMapping | 处理HTTP DELETE请示 |
/**
* 基于SpringMVC框架开发web应用--用户控制器
*
* @author zhuhuix
* @date 2020-07-03
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 保存用户并返回到用户列表页面
@PostMapping
public ModelAndView saveUser(User user) {
userService.saveUser(user);
return new ModelAndView("redirect:/user");//重定向到list页面
}
// 获取创建用户表单页面
@GetMapping("/form")
public ModelAndView createForm(Model model) {
model.addAttribute("user",new User());
return new ModelAndView("register","userModel",model);
}
// 获取用户列表页面
@GetMapping
public ModelAndView list(Model model) {
model.addAttribute("userList", userService.listUsers());
return new ModelAndView("userlist", "userModel", model);
}
}
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultrag.net.nz/thymeleaf/layout"
>
<head>
<meta charset="UTF-8">
head>
<body>
<h3>用户列表h3>
<div>
<a th:href="@{/user/form}">创建用户a>
div>
<table border="1">
<thead>
<tr>
<td>IDtd>
<td>Emailtd>
<td>Nametd>
tr>
thead>
<tbody>
<tr th:if="${userModel.userList.size()} eq 0">
<td colspan="3">没有用户信息!td>
tr>
<tr th:each="user:${userModel.userList}">
<td th:text="${user.id}">td>
<td th:text="${user.email}">td>
<td th:text="${user.name}">td>
tr>
tbody>
table>
body>
html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultrag.net.nz/thymeleaf/layout"
>
<head>
<meta charset="UTF-8">
head>
<body>
<h3>登记用户h3>
<form action="/users" th:action="@{/user}" method="POST" th:object="${userModel.user}">
<input type="hidden" name="id" th:value="*{id}">
名称:<br>
<input type="text" name="name" th:value="*{name}">
<br>
邮箱:<br>
<input type="text" name="email" th:value="*{email}">
<input type="submit" value="提交" >
form>
body>
html>
虽然我们已经实现了用户列表与登记新用户,但视图层还存在漏洞,比如用户名称为空的时候不能保存,邮箱输入格式要符合规则,所以程序要对表单输入的内容进行校验。
>
>org.hibernate.validator >
>hibernate-validator >
>6.0.13.Final >
>
>
>javax.validation >
>validation-api >
>2.0.1.Final >
>
public class User implements Serializable {
// 用户id
@NotNull
private Long id;
// 用户名
@NotBlank(message = "用户名称不能为空")
private String name;
// 邮箱
@Pattern(message ="邮箱格式不符", regexp = "^[A-Za-z0-9\\u4e00-\\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")
private String email;
...
}
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 保存用户并返回到用户列表页面
@PostMapping
public ModelAndView saveUser(@Valid User user, Errors errors,Model model) {
if (errors.hasErrors()){
model.addAttribute("user",user);
if (errors.getFieldError("name")!=null) {
model.addAttribute("nameError", errors.getFieldError("name").getDefaultMessage());
}
if (errors.getFieldError("email")!=null) {
model.addAttribute("emailError", errors.getFieldError("email").getDefaultMessage());
}
return new ModelAndView("register","userModel",model);
}
userService.saveUser(user);
//重定向到list页面
return new ModelAndView("redirect:/user");
}
...
}
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
head>
<body>
<h3>登记用户h3>
<form action="/users" th:action="@{/user}" method="POST" th:object="${userModel.user}">
<input type="hidden" name="id" th:value="*{id}">
名称:<br>
<input type="text" name="name" th:value="*{name}" >
<br>
邮箱:<br>
<input type="text" name="email" th:value="*{email}">
<br>
<input type="submit" value="提交" >
<div style="color:red" th:text="${nameError}">div>
<div style="color:red" th:text="${emailError}">div>
form>
body>
html>
. 在上一小节中基于SpringMVC框架构建了我们的web应用,并在视图层运用模板引擎展示数据及校验表单输入,本章将使用JdbcTemplate及Spring Data实现数据持久化的操作。
MySQL 最流行的关系型数据库管理系统,MySQL所使用的 SQL 语言是用于访问数据库的最常用标准化语言,MySQL由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用在互联网领域中。
DROP DATABASE IF EXISTS user_info;
CREATE DATABASE user_info
DEFAULT CHARACTER SET utf8
DEFAULT COLLATE utf8_general_ci;
use user_info;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
<!--Mysql依赖包-->
mysql
mysql-connector-java
5.1.47
runtime
<!-- 数据库连接池:druid数据源驱动 -->
com.alibaba
druid-spring-boot-starter
1.1.10
#配置数据源
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://user_info?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
Spring对JDBC的支持要归功于JdbcTemplate类。JdbcTemplate提供了一种特殊的方式,通过这种方式,开发人员在对关系型数据库执行SQL操作的时候能够避免使用JDBC时常见的繁文缛节和样板式代码。
<!-- JDBC -->
org.springframework.boot
spring-boot-starter-jdbc
/**
* 基于SpringMVC框架开发web应用--用户服务类
*
* @author zhuhuix
* @date 2020-07-03
* @date 2020-07-04 增加通过jdbcTemplate处理数据
*/
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 返回所有的用户
public List<User> listUsers() {
return jdbcTemplate.query("select id,name,email from user;",
new Object[]{}, new BeanPropertyRowMapper<>(User.class));
}
// 增加用户
public int saveUser(User user) {
return jdbcTemplate.update("insert into user(name,email) values(?,?);"
, user.getName(), user.getEmail());
}
}
我们只修改了UserService用户服务类;控制器、视图模板都未做任何改变,接下来我们可以再次尝试运行一下。打开浏览器并访问http://localhost:8080/user。
输入用户信息并提交
查看数据库用户信息表
相对于普通的JDBC,Spring的JdbcTemplate能够极大地简化关系型数据库的使用。但是,你会发现使用JPA会更加简单。接下来我们会继续通过Spring Data框架让数据持久化变得更简单。
在上篇文章中,我们使用mysql数据库与JdbcTemplate简单实现了数据持久化操作,并对web程序进行了测试,本篇文章将继续通过Spring Data框架让数据持久化变得更简单。
Spring Data JDBC -数据访问组件对JDBC的支持。
Spring Data JPA:-基于关系型数据库进行JPA持久化。
Spring Data MongoDB - 持久化到Mongo文档数据库。
Spring Data Redis-持久化到Redis key-value内存数据库。
Spring Data REST:通过Spring Data数据访问组件导出为RESTful资源。
Spring Data Cassandra:持久化到Cassandra数据库。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
ALTER TABLE user ADD COLUMN create_time DATETIME;
ALTER TABLE user ADD COLUMN update_time DATETIME;
/**
* 基于SpringMVC框架开发web应用--用户类
*
* @author zhuhuix
* @date 2020-07-03
* @date 2020-07-07 添加JPA映射注解
*/
@Entity
public class User implements Serializable {
// 用户id
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 用户名
@NotBlank(message = "用户名称不能为空")
@Column(name="name")
private String name;
// 邮箱
@Column(name="email")
@Pattern(message ="邮箱格式不符", regexp = "^[A-Za-z0-9\\u4e00-\\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")
private String email;
// 创建时间
@Column(name = "create_time")
@CreationTimestamp
private Timestamp createTime;
// 更新时间
@Column(name = "update_time")
@UpdateTimestamp
private Timestamp updateTime;
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
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;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
/**
* 基于SpringMVC框架开发web应用--数据操作层
*
* @author zhuhuix
* @date 2020-07-07
*/
public interface UserRepository extends CrudRepository<User,Long> {
}
/**
* 基于SpringMVC框架开发web应用--用户服务类
*
* @author zhuhuix
* @date 2020-07-03
* @date 2020-07-04 增加通过jdbcTemplate处理数据
* @date 2020-07-07 将jdbcTemplate处理数据程序改为Spring Data JPA的处理方式
*/
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 返回所有的用户
public List<User> listUsers() {
return (List<User>) userRepository.findAll();
}
// 增加用户
public User saveUser(User user) {
return userRepository.save(user);
}
}
我们增加了数据层并修改了UserService服务层;控制器、视图模板都未做任何改变,接下来我们可以再次尝试运行一下。打开浏览器并访问http://localhost:8080/user。
测试结论:通过将JdbcTemplate替换成Spring Data JPA,同样实现了用户信息的查询与增加,而且通过JPA CrudRepository接口,我们没有书写一行SQL语句,也实现了数据的增加与查询。
public interface UserRepository extends CrudRepository<User,Long> {
// 自定义添加通过用户名称查找用户信息
List<User> findByName(String name);
}
按照Spring Data的规范的规定,查询方法以find | read | get开头(比如 find、findBy、read、readBy、get、getBy),涉及查询条件时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
直接在接口中定义查询方法,如果是符合规范的,可以不用写实现,即不用写SQL。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
...
// 根据名称查找用户
public List<User> searchUser(String name){
return userRepository.findByName(name);
}
}
/**
* 基于SpringMVC框架开发web应用--用户控制器
*
* @author zhuhuix
* @date 2020-07-03
* @date 2020-07-07 增加用户查找
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
...
// 查找输入页面
@GetMapping("/index")
public ModelAndView index(Model model) {
model.addAttribute("user", new User());
return new ModelAndView("index", "userModel", model);
}
// 查找提交并跳转用户列表
@PostMapping("/search")
public ModelAndView search(@ModelAttribute User user, Model model) {
model.addAttribute("userList", userService.searchUser(user.getName()));
return new ModelAndView("userlist", "userModel", model);
}
}
<html xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8"/>
<title>Titletitle>
head>
<body>
<h3>查找用户h3>
<form action="/users" th:action="@{/user/search}" method="POST" th:object="${userModel.user}">
名称:<br>
<input type="text" name="name" th:value="*{name}" >
<br>
<input type="submit" value="查询" >
form>
body>
html>
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的标准。
本文将通过Spring Security配置实现web应用的如下功能:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-security artifactId>
dependency>
通过将security starter添加到项目的构建文件中,我们得到了如下的安全特性:
我们试着启动一下应用,并访问用户列表页面,发现程序竟然跳转到了一个login登录页面,并要求输入用户和密码。
这是一个HTTP basic认证对话框,提示进行认证。要想通过这个认证,需要一个用户名和密码。用户名为user,而密码则是随机生成的,可以在日志监控界面上查到:
我们试着在登录界面上输入用户名user与图中的密码,进行认证登录:
程序顺利通过了密码认证,并进入到用户列表界面。但显然以上的方式是不符合需求的,我们需要的是:
-- ----------------------------
-- Table structure for admin
-- ----------------------------
DROP TABLE IF EXISTS `admin`;
CREATE TABLE `admin` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`user_password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
/**
* 基于SpringMVC框架开发web应用--管理员用户类,用于登录认证
*
* @author zhuhuix
* @date 2020-07-08
*/
@Entity
public class Admin implements UserDetails {
// 管理员id
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name="user_name")
private String userName;
@Column(name="user_password")
private String userPassword;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getPassword() {
return userPassword;
}
@Override
public String getUsername() {
return userName;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
}
/**
* 基于SpringMVC框架开发web应用--管理员数据操作层
*
* @author zhuhuix
* @date 2020-07-08
*/
public interface AdminRepository extends CrudRepository<Admin,Long> {
// 根据用户名查找管理员
Admin findByUserName(String username);
}
/**
* 基于SpringMVC框架开发web应用--管理员服务层
*
* @author zhuhuix
* @date 2020-07-08
*/
@Service
public class AdminService implements UserDetailsService {
private final Logger logger = LoggerFactory.getLogger(Logger.class);
@Autowired
private AdminRepository adminRepository;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Admin admin = adminRepository.findByUserName(s);
if (admin == null) {
logger.error("管理员" + s + "未找到");
throw new UsernameNotFoundException("User " + s + "not found");
}
return admin;
}
// 保存管理员
public Admin save(Admin admin){
return adminRepository.save(admin);
}
}
/**
* Spring Security配置类
*
* @author zhuhuix
* @date 2020-07-08
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AdminService adminService;
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(adminService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
以上通过建立管理员信息表,及通过JPA定义数据处理层,编写获取管理员信息的服务实现,最后配置Spring Security Web安全类,实现了自定义的登录验证方法,下面具体来测试一下:
web应用程序已经实现了自定义的用户登录验证。
/**
* 基于SpringMVC框架开发web应用--管理员注册控制器
*
* @author zhuhuix
* @date 2020-07-08
*/
@RestController
@RequestMapping("/admin")
public class AdminController {
@Autowired
private AdminService adminService;
// 管理注册页
@GetMapping
public ModelAndView registerForm(Model model) {
model.addAttribute("admin", new Admin());
return new ModelAndView("registration", "adminModel", model);
}
// 提交注册信息
@PostMapping("/register")
public ModelAndView save(Admin admin) {
BCryptPasswordEncoder bCryptPasswordEncoder =new BCryptPasswordEncoder();
admin.setUserPassword(bCryptPasswordEncoder.encode(admin.getPassword()));
if (adminService.save(admin) != null) {
return new ModelAndView("redirect:/login ");
} else {
return null;
}
}
}
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
head>
<body>
<h3>管理员注册h3>
<form action="/admin" th:action="@{/admin/register}" method="POST" th:object="${adminModel.admin}">
名称:<br>
<input type="text" name="userName" th:value="*{userName}" >
<br>
输入密码:<br>
<input type="password" name="userPassword" th:value="*{userPassword}">
<br>
<input type="submit" value="注册" >
form>
body>
html>
/**
* Spring Security配置类
*
* @author zhuhuix
* @date 2020-07-08
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AdminService adminService;
// 自定义用户验证
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(adminService)
.passwordEncoder(new BCryptPasswordEncoder());
}
// 保护web请求的安全性规则
@Override
protected void configure(HttpSecurity http) throws Exception{
http.authorizeRequests()
.antMatchers("/user/**").access("hasRole('ROLE_USER')")
.antMatchers("/admin/**").permitAll()
.and().formLogin().defaultSuccessUrl("/user")
.and().httpBasic();
}
}
Spring的环境抽象是各种配置属性的一站式服务。它抽取了原始的属性,这样需要这些属性的bean就可以从Spring本身中获取了。Spring环境会拉取多个属性源,包括:
- JVM系统属性;
- 操作系统环境变量;
- 命令行参数;
- 应用属性配置文件。
###数据源配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.url=jdbc:log4jdbc:mysql://localhost:3306/user_info?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
#thymelea模板配置
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
spring:
port:8080
显然具有层次关系的配置文件更易于理解与书写,接下来我们将使用application.yml取代application.properties完成各种属性配置。
###数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: root
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
url: jdbc:log4jdbc:mysql://localhost:3306/user_info?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
#thymelea模板配置
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mode: HTML5
encoding: UTF-8
content-type: text/html
cache: false
- 默认情况下,Spring Boot通过Logback配置日志,日志会以INFO级别写入到控制台中,我们希望重新配置显示日志的格式;
- 同时我们希望通过监控sql日志输出到控制台,并将输出的信息进行筛选打印;
<dependency>
<groupId>org.bgee.log4jdbc-log4j2groupId>
<artifactId>log4jdbc-log4j2-jdbc4.1artifactId>
<version>1.16version>
dependency>
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
log4jdbc.auto.load.popular.drivers=false
log4jdbc.drivers=com.mysql.cj.jdbc.Driver
<configuration scan="true" scanPeriod="30 seconds" debug="false">
<contextName>web democontextName>
<property name="log.charset" value="utf-8" />
<property name="log.pattern" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) - %gray(%msg%n)" />
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}pattern>
<charset>${log.charset}charset>
encoder>
appender>
<root level="info">
<appender-ref ref="console" />
root>
<logger name="jdbc.sqlonly" level="INFO" additivity="false">
<appender-ref ref="console" />
logger>
<logger name="jdbc.resultset" level="ERROR" additivity="false">
<appender-ref ref="console" />
logger>
<logger name="jdbc.resultsettable" level="OFF" additivity="false">
<appender-ref ref="console" />
logger>
<logger name="jdbc.connection" level="OFF" additivity="false">
<appender-ref ref="console" />
logger>
<logger name="jdbc.sqltiming" level="OFF" additivity="false">
<appender-ref ref="console" />
logger>
<logger name="jdbc.audit" level="OFF" additivity="false">
<appender-ref ref="console" />
logger>
configuration>
为了支持配置属性的注入,Spring Boot提供了@ConfigurationProperties注解。将它放到Spring 应用上下文的 bean之后,它就会为该bean中那些能够根据Spring环境注入值的属性赋值。
#管理员默认密码
admin:
password: 123456
/**
* 基于SpringMVC框架开发web应用--管理员用户默认密钥
*
* @author zhuhuix
* @date 2020-07-09
*/
@Component
@ConfigurationProperties(prefix = "admin")
public class DefaultPasswordProperties {
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
/**
* 基于SpringMVC框架开发web应用--管理员注册控制器
* * @author zhuhuix
* @date 2020-07-08
* @date 2020-07-09 注册密码为空时使用自定义的默认密码属性
*/
@RestController
@RequestMapping("/admin")
public class AdminController {
@Autowired
private AdminService adminService;
@Autowired
private DefaultPasswordProperties defaultPasswordProperties;
// 管理注册页
@GetMapping
public ModelAndView registerForm(Model model) {
model.addAttribute("admin", new Admin());
return new ModelAndView("registration", "adminModel", model);
}
// 提交注册信息
@PostMapping("/register")
public ModelAndView save(Admin admin) {
// 如果注册密码为空,则赋值默认密码
if (StringUtils.isEmpty(admin.getPassword())) {
admin.setUserPassword(defaultPasswordProperties.getPassword());
}
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
admin.setUserPassword(bCryptPasswordEncoder.encode(admin.getPassword()));
if (adminService.save(admin) != null) {
return new ModelAndView("redirect:/login ");
} else {
return null;
}
}
}
本节将进入到新的单元:Spring与应用的集成,今天先实现集成REST API服务。
微服务架构,前后端分离目前已成为互联网项目开发的业界标准,其核心思想就是前端(APP、小程序、H5页面等)通过调用后端的API接口,提交及返回JSON数据进行交互。
在前几篇文章中我们用了模板引擎开发了多页应用(MultiPage Application,MPA),我们将在原有基础上按以下步骤实现集成API服务:
创建用户管理的Restful Api(UserRestfulApi.java)的接口,实现增加、删除、修改、查找用户信息的API交互服务;
集成Swagger2,对上述API接口进行测试。
/**
* 基于SpringMVC框架开发web应用--用户restful api
* 增加、删除、修改、查找用户信息的API交互服务
*
* @author zhuhuix
* @date 2020-07-10
*/
@RestController
@RequestMapping("/user/api")
public class UserRestfulApi {
@Autowired
private UserService userService;
// 增加用户信息
@PostMapping
public ResponseEntity<User> addUser(User user) {
return ResponseEntity.ok(userService.saveUser(user));
}
// 根据id删除用户
@DeleteMapping("/{id}")
public ResponseEntity deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.ok(HttpStatus.OK);
}
// 根据id修改用户
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@RequestBody User user) {
return ResponseEntity.ok(userService.saveUser(user));
}
// 根据id查找用户
@GetMapping("/{id}")
public ResponseEntity<User> findUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.findUser(id));
}
}
/**
* 基于SpringMVC框架开发web应用--用户服务类
*
* @author zhuhuix
* @date 2020-07-03
* @date 2020-07-04 增加通过jdbcTemplate处理数据
* @date 2020-07-07 将jdbcTemplate处理数据程序改为Spring Data JPA的处理方式
* @date 2020-07-10 增加
*/
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 返回所有的用户
public List<User> listUsers() {
return (List<User>) userRepository.findAll();
}
// 保存用户
public User saveUser(User user) {
return userRepository.save(user);
}
// 删除用户
public void deleteUser(Long id){
userRepository.deleteById(id);
}
// 查找用户
public User findUser(Long id){
return userRepository.findById(id).get();
}
// 根据名称查找用户
public List<User> searchUser(String name){
return userRepository.findByName(name);
}
}
/**
* 基于SpringMVC框架开发web应用--数据操作层
*
* @author zhuhuix
* @date 2020-07-07
*/
public interface UserRepository extends CrudRepository<User,Long> {
// 自定义添加通过用户名称查找用户信息
List<User> findByName(String name);
}
Swagger2 作为一个规范和完整的框架,可以用于生成、描述、调用和可视化 RESTful 风格的 Web 服务:
1、 接口文档在线自动生成,文档随接口变动实时更新,节省维护成本
2、 支持在线接口测试,不依赖第三方工具
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
<exclusions>
<exclusion>
<groupId>io.swaggergroupId>
<artifactId>swagger-annotationsartifactId>
exclusion>
<exclusion>
<groupId>io.swaggergroupId>
<artifactId>swagger-modelsartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.swaggergroupId>
<artifactId>swagger-annotationsartifactId>
<version>1.5.21version>
dependency>
<dependency>
<groupId>io.swaggergroupId>
<artifactId>swagger-modelsartifactId>
<version>1.5.21version>
dependency>
/**
* Swagger2配置类
*
* @author zhuhuix
* @date 2020-07-10
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
@SuppressWarnings("all")
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.enable(true)
.apiInfo(apiInfo())
.select()
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Restful Api接口测试")
.version("1.0")
.build();
}
}
/**
* Spring Security配置类
*
* @author zhuhuix
* @date 2020-07-08
* @date 2020-07-10 禁用跨域请求的安全验证
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AdminService adminService;
// 自定义用户验证
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(adminService)
.passwordEncoder(new BCryptPasswordEncoder());
}
// 保护web请求的安全性规则
@Override
protected void configure(HttpSecurity http) throws Exception{
http.authorizeRequests()
.antMatchers("/user/**").access("hasRole('ROLE_USER')")
.antMatchers("/admin/**").permitAll()
.and().formLogin().defaultSuccessUrl("/user")
.and().httpBasic()
// 禁用 CSRF
.and().csrf().disable();
}
}
至此我们完成了Spring集成restful api服务,并通过集成Swagger2,简单直观地对http的各种请求进行了完整地测试,下面做个总结:
Spring框架提供了一种使用JavaMailSender接口发送电子邮件的简单抽象方法,而Spring Boot为其提供了自动配置以及启动程序模块。
public interface JavaMailSender extends MailSender {
MimeMessage createMimeMessage();
MimeMessage createMimeMessage(InputStream var1) throws MailException;
void send(MimeMessage var1) throws MailException;
void send(MimeMessage... var1) throws MailException;
void send(MimeMessagePreparator var1) throws MailException;
void send(MimeMessagePreparator... var1) throws MailException;
}
Spirng实现邮件发送功能,需要以下步骤:
1. 添加maven依赖
2. 添加Spring邮件配置
3. 创建邮件管理Bean并注入Spring应用上下文
4. 修改业务逻辑,调用邮件发送功能
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
dependencies>
### application.yml
spring:
#mail配置
mail:
host: smtp.163.com
username: [email protected]
password: 自行设置邮箱密码
default-encoding: UTF-8
/**
* 邮件发送Bean
*
* @author zhuhuix
* @date 2020-07-13
*/
@Service
@Component
public class MailManager {
private final org.slf4j.Logger logger = LoggerFactory.getLogger(Logger.class);
// 发件人
@Value("${spring.mail.username}")
private String from;
@Autowired
private JavaMailSender javaMailSender;
/**
* 普通文本邮件发送
*
* @param to 收件人
* @param subject 主题
* @param text 内容
*/
public void sendSimpleMail(String to, String subject, String text) {
SimpleMailMessage msg = new SimpleMailMessage();
msg.setFrom(this.from);
msg.setTo(to);
msg.setSubject(subject);
msg.setText(text);
try {
this.javaMailSender.send(msg);
logger.info(msg.toString());
} catch (MailException ex) {
logger.error(ex.getMessage());
}
}
/**
* HTML邮件发送
*
* @param to 收件人
* @param subject 主题
* @param text 内容
*/
public void sendHTMLMail(String to, String subject, String text) {
try {
MimeMessage msg = javaMailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(msg, true);
mimeMessageHelper.setFrom(this.from);
mimeMessageHelper.setTo(to);
mimeMessageHelper.setSubject(subject);
mimeMessageHelper.setText(text, true);
this.javaMailSender.send(msg);
logger.info("to=" + to + "," + "subject=" + subject + "," + "text=" + text);
} catch (MailException ex) {
logger.error(ex.getMessage());
} catch (MessagingException ex) {
logger.error(ex.getMessage());
}
}
/**
* 发送带有附件的邮件
* @param to 收件人
* @param subject 主题
* @param text 内容
* @param filePath 附件
*/
public void sendAttachmentMail(String to, String subject, String text, String filePath) {
try {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
mimeMessageHelper.setFrom(from);
mimeMessageHelper.setTo(to);
mimeMessageHelper.setSubject(subject);
mimeMessageHelper.setText(text, true);
FileSystemResource fileSystemResource = new FileSystemResource(new File(filePath));
String fileName = fileSystemResource.getFilename();
mimeMessageHelper.addAttachment(fileName, fileSystemResource);
javaMailSender.send(mimeMessage);
logger.info("to=" + to + "," + "subject=" + subject + "," + "text=" + text);
} catch (MailException ex) {
logger.error(ex.getMessage());
} catch (MessagingException ex) {
logger.error(ex.getMessage());
}
}
}
/**
* 基于SpringMVC框架开发web应用--用户服务类
*
* @author zhuhuix
* @date 2020-07-03
* @date 2020-07-04 增加通过jdbcTemplate处理数据
* @date 2020-07-07 将jdbcTemplate处理数据程序改为Spring Data JPA的处理方式
* @date 2020-07-10 增加删除deleteUser和查找findUser
* @date 2020-07-13 首次保存用户后通过邮件管理器发送通知邮件
*/
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private MailManager mailManager;
// 返回所有的用户
public List<User> listUsers() {
return (List<User>) userRepository.findAll();
}
// 保存用户
public User saveUser(User user) {
boolean firstRegister = false;
if ((user.getId() == null || user.getId().equals(0L))) {
firstRegister = true;
}
// 首次保存用户成功后发送通知邮件
if (userRepository.save(user) != null && firstRegister == true) {
sendMail(user);
}
return user;
}
// 发送邮件
private void sendMail(User user) {
// 发送文本邮件
String text = "您的邮箱信息已登记!";
mailManager.sendSimpleMail(user.getEmail(), "用户通知(文本邮件)", text);
// 发送HTML邮件
String content = "\n" +
"\n" +
" "
+ text + " \n" +
"\n" +
"";
mailManager.sendHTMLMail(user.getEmail(), "用户通知(HTML邮件)", content);
// 发送带有附件的邮件
String attachFilePath = "c:\\csdn-logo.png";
mailManager.sendAttachmentMail(user.getEmail(), "用户通知(带有附件的邮件)", content, attachFilePath);
}
// 删除用户
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
// 查找用户
public User findUser(Long id) {
return userRepository.findById(id).get();
}
// 根据名称查找用户
public List<User> searchUser(String name) {
return userRepository.findByName(name);
}
}
以上我们通过JavaMailSender接口实现了文本、超文本及带有附件的邮件的发送功能。
在书写这些程序时,采用了硬编码,可能会碰到如下问题:
RabbitMQ可以说是AMQP(Advanced Message Queue,高级消息队列协议)最杰出的实现。
概念 | 描述 |
---|---|
发送者 | 消息的生产者,也可以是一个向交换器发布消息的客户端应用程序 |
接收者 | 消息的消费者,也可以认为是向消息队列接收消息的服务端程序 |
Exchange(交换器) | 用来接收发送者发送的消息并将这些消息路由给服务器中的队列 |
Binding (绑定) | 用于消息队列和交换器之间的关联 |
队列 | 用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。 |
Binding key | 在绑定(Binding)Exchange与Queue的同时,一般会指定一个Binding key;Routing key结合Exchange可实现路由规则。 |
Routing key | 通过指定Routing key,结合Exchange和Routing key,可以决定消息流向哪里。 |
类型 | 描述 |
---|---|
Direct | 如果消息的routing key与队列的binding key相同,那么消息将会路由到该队列上。 |
Topic | 如果消息的routing key与队列binding key(可能会包含通配符)匹配,那么消息将会路由到一个或多个这样的队列上。 |
Fanout | 不管routing key和binding key是什么,消息都将会路由到所有绑定队列上。 |
Headers | 与Topic Exchange类似,只不过要基于消息的头信息进行路由,而不是routing key。 |
本文只对Direct模型进行展开处理,其他类型请读者自行研究。关于如何绑定队列到Exchange的更详细的描述,可以参考Alvaro Videla和Jason J.W. Williams编写的RabbitMQ in Action (RabbitMQ实战)。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
### application.yml
spring:
#RabbitMQ配置
rabbitmq:
host: 192.168.0.1
port: 5672
username: rabbitmq
password: rabbitmq
virtual-host: /
connection-timeout: 10000
listener:
simple:
acknowledge-mode: auto # 自动应答
auto-startup: true
default-requeue-rejected: false # 不重回队列
concurrency: 5
max-concurrency: 20
prefetch: 1 # 每次只处理一个信息
retry:
enabled: false
template:
exchange: web.demo
routing-key: user.key
/**
* rabbitmq 配置类
*
* @author zhuhuix
* @date 2020-07-14
*/
@Configuration(value = "rabbitMQConfig")
public class RabbitMQConfig {
// 获取exchange和routing-key定义
@Value("${spring.rabbitmq.template.exchange}")
private String exchange;
@Value("${spring.rabbitmq.template.routing-key}")
private String routingKey;
public String getExchange() {
return exchange;
}
public String getRoutingKey() {
return routingKey;
}
// 自定义消息转换器
@Bean
public MessageConverter messageConverter() {
return new SimpleMessageConverter() {
@Override
protected Message createMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
Message message = super.createMessage(object, messageProperties);
return message;
}
};
}
}
/**
* rabbitmq 接收器
*
* @author zhuhuix
* @date 2020-07-14
*/
@Component
public class RabbitMQReceiver {
private final org.slf4j.Logger logger = LoggerFactory.getLogger(Logger.class);
// 邮件集成请参考上篇文章《Spring全家桶的深入学习(八):Spring集成JavaMailSender实现邮件发送》
@Autowired
private MailManager mailManager;
@Autowired
private RabbitTemplate rabbitTemplate;
// 监听消息队列
@RabbitHandler
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange("#{rabbitMQConfig.getExchange()}"),
key = "#{rabbitMQConfig.getRoutingKey()}",
value = @Queue("user.queue")))
public void receiveMessage(Message message) {
try {
User user = (User) rabbitTemplate.getMessageConverter().fromMessage(message);
logger.info("接收到消息:[{}]", user.toString());
// 收到完整消息后,调用邮件发送程序,发送通知邮件
if (user != null) {
sendMail(user);
}
} catch (Exception ex) {
logger.error(ex.getMessage());
}
}
// 发送邮件
// 请参考上篇文章《Spring全家桶的深入学习(八):Spring集成JavaMailSender实现邮件发送》
private void sendMail(User user) {
// 发送文本邮件
String text = "您的邮箱信息已登记!";
mailManager.sendSimpleMail(user.getEmail(), "用户通知(文本邮件)", text);
// 发送HTML邮件
String content = "\n" +
"\n" +
" "
+ text + " \n" +
"\n" +
"";
mailManager.sendHTMLMail(user.getEmail(), "用户通知(HTML邮件)", content);
// 发送带有附件的邮件
String attachFilePath = "c:\\csdn-logo.png";
mailManager.sendAttachmentMail(user.getEmail(), "用户通知(带有附件的邮件)", content, attachFilePath);
}
}
/**
* 基于SpringMVC框架开发web应用--用户服务类
*
* @author zhuhuix
* @date 2020-07-03
* @date 2020-07-04 增加通过jdbcTemplate处理数据
* @date 2020-07-07 将jdbcTemplate处理数据程序改为Spring Data JPA的处理方式
* @date 2020-07-10 增加删除deleteUser和查找findUser
* @date 2020-07-13 首次保存用户后通过邮件管理器发送通知邮件
* @date 2020-07-14 将同步发送通知邮件的功能变更为通过消息队列异步发送
*/
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private RabbitTemplate rabbitTemplate;
// 返回所有的用户
public List<User> listUsers() {
return (List<User>) userRepository.findAll();
}
// 保存用户
public User saveUser(User user) {
boolean firstRegister = false;
if ((user.getId() == null || user.getId().equals(0L))) {
firstRegister = true;
}
// 首次保存用户成功后发送消息队列实现异步发送通知邮件
if (userRepository.save(user) != null && firstRegister == true) {
rabbitTemplate.convertAndSend(user);
}
return user;
}
// 删除用户
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
// 查找用户
public User findUser(Long id) {
return userRepository.findById(id).get();
}
// 根据名称查找用户
public List<User> searchUser(String name) {
return userRepository.findByName(name);
}
}
Spring Integration 正是企业级服务总线的实现:Spring Integration实现通用的集成模式,每个模式都实现为一个组件,消息会通过该组件在管道中传递数据。借助Spring配置,我们可以将这些组件组装成一个管道,数据可以通过这个管道来流动。
Spring Integration在系统内提供实现轻量级、事件驱动交互行为的框架
Spring Integration在系统间提供一种基于适配器的平台,以支持灵活的系统间交互
以上重要概念具体可参考 《Enterprise Integration Patterns》(企业集成模式)
/**
* Spring Integration网关入口
*
* @author zhuhuix
* @date 2020-07-15
*/
@Component
@MessagingGateway(defaultRequestChannel = "registerChannel")
public interface RegisterMessageGateway {
/**
* 注册通知流程
*
* @param user 用户信息
*/
void registerMessageFlow(User user);
}
/**
* Spring Integration过滤器
*
* @author zhuhuix
* @date 2020-07-15
*/
@Component
public class RegisterMessageFilter implements GenericSelector<User> {
@Override
public boolean accept(User user) {
if ((user == null || user.getId() == null || user.getId().equals(0L))) {
return false;
} else {
return true;
}
}
}
/**
* Spring Integration消息发送
*
* @author zhuhuix
* @date 2020-07-15
*/
@Component
public class RegisterRabbitMQSendHandler implements GenericHandler<User> {
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public Object handle(User user, MessageHeaders messageHeaders) {
rabbitTemplate.convertAndSend(user);
return null;
}
}
/**
* Spring Integration DSL配置
*
* @author zhuhuix
* @date 2020-07-15
*/
@Configuration
public class RegisterIntegrationConfig {
@Bean
public IntegrationFlow registerFlow(RegisterRabbitMQSendHandler registerRabbitMQSendHandler
, RegisterMessageFilter registerMessageFilter) {
return IntegrationFlows
// 从registerChannel消息通道获取消息
.from(MessageChannels.direct("registerChannel"))
// 过滤
.filter(registerMessageFilter)
// 发送邮件
.handle(registerRabbitMQSendHandler)
.get();
}
}
/**
* 基于SpringMVC框架开发web应用--用户服务类
*
* @author zhuhuix
* @date 2020-07-03
* @date 2020-07-04 增加通过jdbcTemplate处理数据
* @date 2020-07-07 将jdbcTemplate处理数据程序改为Spring Data JPA的处理方式
* @date 2020-07-10 增加删除deleteUser和查找findUser
* @date 2020-07-13 首次保存用户后通过邮件管理器发送通知邮件
* @date 2020-07-14 将同步发送通知邮件的功能变更为通过消息队列异步发送
* @date 2020-07-15 通过调用注册通知流程组件,屏蔽调用消息队列
*/
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private RegisterMessageGateway registerMessageGateway;
// 返回所有的用户
public List<User> listUsers() {
return (List<User>) userRepository.findAll();
}
// 保存用户
public User saveUser(User user) {
// 保存数据
userRepository.save(user);
// 调用注册通知流程
registerMessageGateway.registerMessageFlow(user);
return user;
}
// 删除用户
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
// 查找用户
public User findUser(Long id) {
return userRepository.findById(id).get();
}
// 根据名称查找用户
public List<User> searchUser(String name) {
return userRepository.findByName(name);
}
}
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO为数据库中的记录。
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.2version>
dependency>
#MyBatis配置
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml
/**
* mybatis数据层
*
* @author zhuhuix
* @date 2020-07-23
*/
@Repository
public interface UserMapper {
// 查找所有用户
List<User> findAll();
// 根据id查找用户
User findById(Long id);
// 新增用户
Long addUser(User user);
// 更新用户
void updateUser(User user);
// 删除用户
void deleteById(Long id);
// 自定义添加通过用户名称查找用户信息
List<User> findByName(String name);
}
<mapper namespace="com.example.demo.register.UserMapper">
<select id="findAll" resultType="com.example.demo.register.User" >
select * from user;
select>
<insert id="addUser" parameterType="com.example.demo.register.User" useGeneratedKeys="true" keyProperty="id">
insert into user (name,email) values (#{name},#{email});
insert>
<update id="updateUser" parameterType="com.example.demo.register.User">
update user set name=#{name},email=#{email} where id=#{id}
update>
<delete id="deleteById">
delete from user where id=#{id}
delete>
<select id="findById" resultType="com.example.demo.register.User" >
select * from user where id=#{id}
select>
<select id="findByName" resultType="com.example.demo.register.User">
select * from user where name=#{name}
select>
mapper>
/**
* 基于SpringMVC框架开发web应用--用户服务类
*
* @author zhuhuix
* @date 2020-07-03
* @date 2020-07-04 增加通过jdbcTemplate处理数据
* @date 2020-07-07 将jdbcTemplate处理数据程序改为Spring Data JPA的处理方式
* @date 2020-07-10 增加删除deleteUser和查找findUser
* @date 2020-07-13 首次保存用户后通过邮件管理器发送通知邮件
* @date 2020-07-14 将同步发送通知邮件的功能变更为通过消息队列异步发送
* @date 2020-07-15 通过调用注册通知流程,屏蔽调用消息队列
* @date 2020-07-23 将业务层UserService中的调用JPA的方法修改成新的UserMapper方法
*/
@Service
public class UserService {
@Autowired
//private UserRepository userRepository;
private UserMapper userMapper;
@Autowired
private RegisterMessageGateway registerMessageGateway;
// 返回所有的用户
public List<User> listUsers() {
return userMapper.findAll();
}
// 保存用户
public User saveUser(User user) {
// 保存数据
if (user.getId() != null && user.getId() != 0) {
userMapper.updateUser(user);
} else {
userMapper.addUser(user);
}
// 调用注册通知流程
registerMessageGateway.registerMessageFlow(user);
return user;
}
// 删除用户
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
// 查找用户
public User findUser(Long id) {
return userMapper.findById(id);
}
// 根据名称查找用户
public List<User> searchUser(String name) {
return userMapper.findByName(name);
}
}
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultrag.net.nz/thymeleaf/layout"
>
<head>
<meta charset="UTF-8">
head>
<body>
<h3>用户列表h3>
<div>
<a th:href="@{/user/form}">创建用户a>
div>
<table border="1">
<thead>
<tr>
<td>IDtd>
<td>Emailtd>
<td>Nametd>
tr>
thead>
<tbody>
<tr th:if="${userModel.userList.size()} eq 0">
<td colspan="3">没有用户信息!td>
tr>
<tr th:each="user:${userModel.userList}">
<td th:text="${user.id}">td>
<td th:text="${user.email}">td>
<td th:text="${user.name}">td>
<td><a th:href="@{/user/form(id=${user.id})}"> 修改 a>td>
<td><a th:href="@{/user/del(id=${user.id})}"> 删除 a>td>
tr>
tbody>
table>
body>
html>
/**
* 基于SpringMVC框架开发web应用--用户控制器
*
* @author zhuhuix
* @date 2020-07-03
* @date 2020-07-07 增加用户查找
* @date 2020-07-23 增加修改和删除功能
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 保存用户并返回到用户列表页面
@PostMapping
public ModelAndView saveUser(@Valid User user, Errors errors, Model model) {
if (errors.hasErrors()) {
model.addAttribute("user", user);
if (errors.getFieldError("name") != null) {
model.addAttribute("nameError", errors.getFieldError("name").getDefaultMessage());
}
if (errors.getFieldError("email") != null) {
model.addAttribute("emailError", errors.getFieldError("email").getDefaultMessage());
}
return new ModelAndView("register", "userModel", model);
}
userService.saveUser(user);
//重定向到list页面
return new ModelAndView("redirect:/user");
}
// 获取用户表单页面
@GetMapping("/form")
public ModelAndView createForm(Model model, @RequestParam(defaultValue = "0") Long id) {
if (id > 0) {
model.addAttribute("user", userService.findUser(id));
} else {
model.addAttribute("user", new User());
}
return new ModelAndView("register", "userModel", model);
}
// 获取用户列表页面
@GetMapping
public ModelAndView list(Model model) {
model.addAttribute("userList", userService.listUsers());
return new ModelAndView("userlist", "userModel", model);
}
// 查找输入页面
@GetMapping("/index")
public ModelAndView index(Model model) {
model.addAttribute("user", new User());
return new ModelAndView("index", "userModel", model);
}
// 查找提交并跳转用户列表
@PostMapping("/search")
public ModelAndView search(@ModelAttribute User user, Model model) {
model.addAttribute("userList", userService.searchUser(user.getName()));
return new ModelAndView("userlist", "userModel", model);
}
// 删除用户
@RequestMapping(path = "/del")
public ModelAndView del(@RequestParam(name = "id") Long id) {
userService.deleteUser(id);
return new ModelAndView("redirect:/user");
}
}
@SpringBootApplication
@MapperScan(basePackages = "com.example.demo.register")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
至此,我们通过集成MyBatis完整地实现了用户信息的增删改查,完整源代码已上传guthub SpringDemo。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodb-reactiveartifactId>
dependency>
spring:
data:
mongodb:
host: 127.0.0.1
port: 27017
database: user_info
/**
* 基于Spring WebFlux框架开发反应式API--对应mongodb文档的用户类
*
* @author zhuhuix
* @date 2020-07-28
*/
@Document(collection = "user")
public class UserDocument implements Serializable {
@Id
private Long id;
@Field
private String name;
@Field
private String email;
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;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public UserDocument() {
}
public UserDocument(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
@Override
public String toString() {
return "UserDocument{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
/**
* 基于mongodb的反应式Repository
*
* @author zhuhuix
* @date 2020-07-28
*/
public interface UserReactRepository extends ReactiveCrudRepository<UserDocument,Long> {
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webfluxartifactId>
dependency>
/**
* 基于SpringWebFlux用户控制器
*
* @author zhuhuix
* @date 2020-07-27
*/
@RestController
@RequestMapping("/user/react")
public class UserReactController {
// 自动注入数据处理组件
@Autowired
private UserReactRepository userReactRepository;
// 异步测试
@GetMapping("/test")
public Flux<UserDocument> test() {
List<UserDocument> list = new ArrayList<>();
for(int i=1;i<=100;i++){
list.add(new UserDocument(Integer.toUnsignedLong(i),"s"+i,"s"+i+"@163.com"));
}
return userReactRepository.saveAll(list);
}
// 异步获取用户列表
@GetMapping
public Flux<UserDocument> list() {
return userReactRepository.findAll();
}
// 异步根据名称获取单个用户
@GetMapping("/{id}")
public Mono<UserDocument> search(@PathVariable Long id) {
return userReactRepository.findById(id);
}
// 异步保存用户:新增或修改
@PostMapping
public Mono<UserDocument> save(@RequestBody UserDocument user) {
return userReactRepository.save(user);
}
// 异步删除用户
@DeleteMapping("/{id}")
public Mono<Void> delete(@PathVariable Long id) {
return userReactRepository.deleteById(id);
}
}