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个其他的注解。
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视图层
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8"/>
<title>Title</title>
</head>
<body>
<p th:text="${index}" />
</body>
</html>
•代码变更后应用会自动重启; •当面向浏览器的资源(如模板、JavaScript、样式表)等发生变化时,会自动刷新浏览器
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>runtime</scope>
</dependency>
...
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--fork : devtools生效必须设置成true -->
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
-- ctrl+alt+shift+/ :Registry 中第一项必须打勾
. 在上一小节中创建了第一个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", "mike@gmail.com"));
users.add(new User(2L, "Jack", "jack@gmail.com"));
users.add(new User(3L, "Kate", "kate@gmail.com"));
users.add(new User(4L, "Mary", "mary@gmail.com"));
users.add(new User(5L, "Rose", "rose@gmail.com"));
}
// 返回所有的用户
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);
}
}
<!DOCTYPE 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>
<div>
<a th:href="@{/user/form}">创建用户</a>
</div>
<table border="1">
<thead>
<tr>
<td>ID</td>
<td>Email</td>
<td>Name</td>
</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>
<!DOCTYPE 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>
该web应用一切运行正常。
虽然我们已经实现了用户列表与登记新用户,但视图层还存在漏洞,比如用户名称为空的时候不能保存,邮箱输入格式要符合规则,所以程序要对表单输入的内容进行校验。
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
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");
}
...
}
<!DOCTYPE html>
<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依赖包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
<!-- 数据库连接池:druid数据源驱动 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
#配置数据源
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 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
/**
* 基于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());
}
}
相对于普通的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数据库。
<!--pom.xml-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</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);
}
}
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);
}
}
<!-- index.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8"/>
<title>Title</title>
</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应用的如下功能:
<!-- Spring Security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<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;
}
}
}
<!DOCTYPE html>
<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环境会拉取多个属性源,包括:
###数据源配置
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
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
<version>1.16</version>
</dependency>
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
log4jdbc.auto.load.popular.drivers=false
log4jdbc.drivers=com.mysql.cj.jdbc.Driver
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds" debug="false">
<contextName>web demo</contextName>
<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>
<!--监控sql日志输出 -->
<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>
<!-- 如想看到表格数据,将OFF改为INFO -->
<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数据进行交互。
/**
* 基于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、 支持在线接口测试,不依赖第三方工具
<!-- RESTful APIs swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
<exclusions>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.21</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.5.21</version>
</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的各种请求进行了完整地测试,下面做个总结: