SpringBoot 开发 Web 系统,快速入门指南!

开发 前端
在传统的 Java web 工程中,通常会采用 JSP 来编写页面并进行数据展示。而在 Spring Boot 框架中,推荐使用 Thymeleaf 模板引擎来开发 Web 页面。

01、背景介绍

在之前的文章中,我们简单的介绍了 SpringBoot 项目的创建过程,了解了 Spring Boot 开箱即用的特性,本篇文章接着上篇的内容继续介绍 Spring Boot 用于 web 工程开发时的其它特性。

废话不多说了,上代码!

02、应用实践

当将 SpringBoot 框架用于传统的 web 项目开发时,通常分为以下三个过程来实现。

  • 第一步:连接数据库,实现对表进行 CRUD 操作
  • 第二步:引入模板引擎来开发页面
  • 第三步:使用一些常见的 web 特性来满足其它的功能开发

最后源码目录结构如下!

springboot-hello
├── src
│   └── main
│       ├── java
│          ├── com
│             ├── example
│                ├── springboot
│                   ├── Application.java
│                   ├── entity
│                      ├── User.java
│                   ├── service
│                      ├── UserService.java
│                   ├── web
│                      ├── UserController
│       └── resources
│           ├── application.properties
│           ├── templates
│           └─── index.html
└── pom.xml

下面我们依次来看看相关的实践过程。

2.1、数据库操作

这里我们以 Mysql 数据库为例,采用 Spring 的 JdbcTemplate 模板来操作数据的的增删改查,过程如下。

2.1.1、准备数据库表

先创建tb_user表,包含属性id、name、age,可以通过执行下面的建表语句。

CREATE TABLE `tb_user` (
  `id` bigint(20) unsigned NOT NULL,
  `name` varchar(30) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.1.2、添加相关依赖包

在我们访问数据库的时候,需要先配置一个数据库驱动包,通常采用 JDBC 方式方式访问,需要在pom.xml中引入相关依赖包。

<!--spring jdbc-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--mysql 驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

2.1.3、添加数据源配置

与此同时,还需要在application.properties文件中配置相关的数据源访问地址。

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

2.1.4、编写领域对象

根据数据库中创建的 User 表,创建对应的领域对象。

package com.example.springboot.entity;

public class User {

    /**
     * 用户ID
     */
    private Long id;

    /**
     * 用户名称
     */
    private String name;

    /**
     * 用户年龄
     */
    private Integer age;

    // set、get方法等...
}

2.1.5、编写数据访问对象

通过JdbcTemplate实现对tb_user表中的数据访问操作。

package com.example.springboot.service;

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 查询用户
     * @return
     */
    public List<User> getAll() {
        List<User> users = jdbcTemplate.query("select id, name, age from tb_user", (resultSet, i) -> {
            User user = new User();
            user.setId(resultSet.getLong("id"));
            user.setName(resultSet.getString("name"));
            user.setAge(resultSet.getInt("age"));
            return user;
        });
        return users;
    }

    /**
     * 通过ID查询用户
     * @param id
     * @return
     */
    public User getById(Long id) {
        User target = jdbcTemplate.queryForObject("select id, name, age from tb_user where id = ?", new BeanPropertyRowMapper<User>(User.class),id);;
        return target;
    }

    /**
     * 创建用户
     * @param entity
     * @return
     */
    public int create(User entity){
        return jdbcTemplate.update("insert into tb_user(id, name, age) values(?, ?, ?)", entity.getId(), entity.getName(), entity.getAge());
    }

    /**
     * 修改用户
     * @param entity
     * @return
     */
    public int updateById(User entity){
        return jdbcTemplate.update("update tb_user set  name = ?, age = ? where id = ? ", entity.getName(), entity.getAge(), entity.getId());
    }


    /**
     * 删除用户
     * @param id
     * @return
     */
    public int deleteById(Long id) {
        return jdbcTemplate.update("delete from tb_user where id = ?", id);
    }
}

2.1.6、编写单元测试用例

在src/test/java目录下,编写单元测试用例,验证代码中的增、删、改、查操作的正确性,包名与主目录中保持一致。

package com.example.springboot;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void test(){
        // 插入5条数据
        userService.create(new User(1L, "张三", 20));
        userService.create(new User(2L, "李四", 21));
        userService.create(new User(3L, "王五", 22));

        // 查询全部数据
        List<User> dbList1 = userService.getAll();
        System.out.println("第一次全量查询结果:" + dbList1.toString());

        // 修改数据
        userService.updateById(new User(2L, "赵六", 21));

        // 查询指定数据
        User updateObj = userService.getById(2l);
        System.out.println("查询[id=2]结果:" + updateObj.toString());

        // 删除数据
        userService.deleteById(2L);
        userService.deleteById(3L);

        // 查询全部数据
        List<User> dbList2 = userService.getAll();
        System.out.println("第二次全量查询结果:" + dbList2.toString());
    }
}

单元测试,运行后的输出结果:

第一次全量查询结果:[User{id=1, name='张三', age=20}, User{id=2, name='李四', age=21}, User{id=3, name='王五', age=22}]
查询[id=2]结果:User{id=2, name='赵六', age=21}
第二次全量查询结果:[User{id=1, name='张三', age=20}]

此时操作数据库中的表数据,已经正常流通了。

上面介绍的JdbcTemplate只是最基本的几个操作,更多其他数据访问操作的使用可以参考:JdbcTemplate API

2.2、Thymeleaf 模板

在传统的 Java web 工程中,通常会采用 JSP 来编写页面并进行数据展示。而在 Spring Boot 框架中,推荐使用 Thymeleaf 模板引擎来开发 Web 页面。

Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎,与 JSP、Velocity、FreeMarker 等类似,都可以轻易的与 Spring MVC 等 Web 框架进行集成作为 Web 应用的模板引擎。

下面我们一起来看下简单的页面集成应用。

2.2.1、添加相关依赖包

在 SpringBoot 项目中使用 Thymeleaf 时,只需要添加所需的模板引擎模块依赖包即可,内容如下。

<!--thymeleaf模板引擎-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2.2.2、添加相关的配置参数

与此同时,还需要在application.properties文件中配置 thymeleaf 模版扫描路径,比如如下配置。

# thymelea模板配置
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true

其中spring.thymeleaf.prefix就是模板引擎扫描的路径。

2.2.3、创建页面模板

根据上一步映射的模板路径, 在模板路径src/main/resources/templates下新建模板文件index.html,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home Page</title>
</head>
<body>
<h1>Hello !</h1>
<table>
    <thead>
    <tr>
        <th>用户ID</td>
        <th>用户名称</td>
        <th>用户年龄</td>
    </tr>
    </thead>
    <tbody>
    <tr th:each="prod:${allUsers}">
        <td th:text="${prod.id}">100</td>
        <td th:text="${prod.name}">张三丰</td>
        <td th:text="${prod.age}">99</td>
    </tr>
    </tbody>
</table>
</body>
</html>

2.2.4、编写页面请求对象

最后编写一个页面请求对象,用来处理路径的请求,将数据渲染到index页面上,具体实现如下:

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/")
    public String index(ModelMap map) {
        // 查询所有的用户
        List<User> users =  userService.getAll();
        map.addAttribute("allUsers", users);
        return "index";
    }
}

将上文中的三条数据插入到数据库,以便展示。

2.2.5、测试页面展示情况

最后将服务启动,在浏览器发起请求,地址为http://localhost:8080/,展示结果如下:

图片图片

说明页面渲染正常,符合预期效果。

更多 Thymeleaf 的页面语法,可以访问 Thymeleaf 的官方文档来深入学习使用。

2.3、web 基本特性

除了以上功能,SpringBoot 还有几个常用特性功能,比如 SpringMVC 中的接口开发、过滤器、拦截器、aop 代理、异常处理等。

下面,我们一起简要的看看相关特性的用法。

2.3.1、接口开发

当与其它项目对接的时候,通常会采用 json 数据格式进行请求和返回,在传统的 SpringMVC 项目中,我们通常需要在每个接口方法上加@ResponseBody注解,以便数据以 json 格式返回给用户。

在 Spring Boot 框架中,我们只需要在接口类上添加@RestController注解,即可实现@Controller和@ResponseBody一样的效果。

示例如下。

@RestController
public class ApiController {

    @Autowired
    private UserService userService;

    @GetMapping("/getUsers")
    public List<User> getUsers() {
        // 查询所有的用户
        System.out.println("收到查询用户的请求");
        List<User> users =  userService.getAll();
        return users;
    }
}

将服务启动,访问http://localhost:8080/getUsers,看看控制台输出结果。

图片图片

可以看到,与预期一致。

如果是页面开发,只要使用@Controller注解即可,以免无法渲染数据。

2.3.2、过滤器

过滤器在 web 项目开发过程中经常会用到,比如用于收集调用日志、排除有 XSS 威胁的字符等,过滤器本质不属于 SpringBoot 自带的功能,而是 Servlet 提供的功能,SpringBoot 对此做了集成管理,实现方式也很简单。

首先创建一个过滤器实现类,示例如下。

public class LogFilter implements Filter {


    @Override
    public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        System.out.println("日志过滤器,request url :"+request.getRequestURI());
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

然后将过滤器注册到 SpringBoot 中,示例如下。

@Configuration
public class FilterConfig {

    /**
     * 添加过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean helloFilterRegistrationBean() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setName("logFilter"); // 指定过滤器名称
        registration.setFilter(new LogFilter()); // 指定过滤器实现类
        registration.setUrlPatterns(Collections.singleton("/*"));// 指定拦截路径
        registration.addInitParameter("paramName", "paramValue");// 指定初始化参数
        registration.setOrder(1);// 指定顺序
        return registration;
    }
}

将服务启动,访问http://localhost:8080/getUsers,看看控制台输出结果。

日志过滤器,request url :/getUsers
收到查询用户的请求

说明过滤器已经正常工作了。

2.3.3、拦截器

拦截器在 web 项目开发过程中也经常会用到,比如用于用户权限的拦截等等。拦截器属于 SpringMVC 自带的功能,因此 SpringBoot 默认就支持,实现方式也很简单。

首先创建一个拦截器实现类,示例如下。

public class SignInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("方法前拦截,request url:" +  request.getRequestURI());
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        System.out.println("方法中拦截(不能拦截异常),request url:" +  request.getRequestURI());
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("方法后拦截(能拦截异常),request url:" +  request.getRequestURI());
    }
}

然后,将拦截器注册到拦截器链中。

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 将SignInterceptor添加到拦截器链中,并且指定拦截路径和拦截顺序
        registry.addInterceptor(new SignInterceptor()).addPathPatterns("/*").order(1);
    }
}

将服务启动,访问http://localhost:8080/getUsers,看看控制台输出结果。

方法前拦截,request url:/getUsers
收到查询用户的请求
方法中拦截(不能拦截异常),request url:/getUsers
方法后拦截(能拦截异常),request url:/getUsers

可以发现,过滤器的执行顺序在拦截器之前。

其中拦截器中postHandle()和afterCompletion()方法,都可以实现对接口执行后进行拦截,两者不同点在于:

  • postHandle()方法无法拦截异常;
  • afterCompletion()方法可以拦截异常;

可以新增一个getUsersError()方法,增加运行时异常。

@GetMapping("/getUsersError")
public List<User> getUsersError() {
    // 查询所有的用户
    System.out.println("收到查询用户的请求");
    if(1==1){
        throw new NullPointerException("异常测试");
    }
    List<User> users =  userService.getAll();
    return users;
}

再次请求访问http://localhost:8080/getUsersError,控制台输出结果如下。

方法前拦截,request url:/getUsersError
收到查询用户的请求
方法后拦截,request url:/getUsersError

当出现异常时,可见postHandle()方法,没有被执行。

2.3.4、aop 代理

aop 动态代理也是 web 项目开发过程中常用的功能特性,熟悉 Spring 的同学可能知道,Spring 的动态代理技术使用了 aspectj 框架的注解来实现切面技术,因此在使用的时候,需要添加相关的依赖包。

首先在pom.xml文件中添加 aspectj 依赖包,示例如下。

<!--添加 aspectj 依赖包 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

编写动态代理类,代理com.example.springboot.web包下所有类的public方法。

@Order(1)
@Component
@Aspect
public class ControllerAspect {

    /***
     * 定义切入点
     */
    @Pointcut("execution(public * com.example.springboot.web..*.*(..))")
    public void methodAdvice(){}

    /**
     * 方法调用前通知
     */
    @Before(value = "methodAdvice()")
    public void before(JoinPoint joinPoint){
        System.out.println("代理-> 来自Before通知,方法名称:" +  joinPoint.getSignature().getName());
    }

    /**
     * 方法调用后通知
     */
    @After(value = "methodAdvice()")
    public void after(JoinPoint joinPoint){
        System.out.println("代理-> 来自After通知,方法名称:" +  joinPoint.getSignature().getName());
    }

    /**
     * 方法调用后通知,方法正常执行后,有返回值,会通知;如果抛异常,不会通知
     */
    @AfterReturning(value = "methodAdvice()", returning = "returnVal")
    public void afterReturning(JoinPoint joinPoint,Object returnVal){
        System.out.println("代理-> 来自AfterReturning通知,方法名称:" +  joinPoint.getSignature().getName() + ",返回值:" + returnVal.toString());
    }

    /**
     * 方法环绕通知
     */
    @Around(value = "methodAdvice()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("代理-> 来自Around环绕前置通知,方法名称:" +  joinPoint.getSignature().getName());
        Object returnValue = joinPoint.proceed();
        System.out.println("代理-> 来自Around环绕后置通知,方法名称:" +  joinPoint.getSignature().getName());
        return returnValue;
    }


    /**
     * 抛出异常通知
     */
    @AfterThrowing(value = "methodAdvice()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        System.out.println("代理-> 来自AfterThrowing通知,方法名称:" +  joinPoint.getSignature().getName() + ",错误信息:"   + ex.getMessage());
    }
}

将服务启动,访问http://localhost:8080/getUsers,控制台输出结果如下。

代理-> 来自Around环绕前置通知,方法名称:getUsers
代理-> 来自Before通知,方法名称:getUsers
收到查询用户的请求
代理-> 来自Around环绕后置通知,方法名称:getUsers
代理-> 来自After通知,方法名称:getUsers
代理-> 来自AfterReturning通知,方法名称:getUsers,返回值:[User{id=1, name='张三', age=20}, User{id=2, name='李四', age=21}, User{id=3, name='王五', age=22}]

访问http://localhost:8080/getUsersError,控制台输出结果如下。

代理-> 来自Around环绕前置通知,方法名称:getUsersError
代理-> 来自Before通知,方法名称:getUsersError
收到查询用户的请求
代理-> 来自After通知,方法名称:getUsersError
代理-> 来自AfterThrowing通知,方法名称:getUsersError,错误信息:异常测试

可以很清晰的看到,当出现异常时AfterReturning()通知方法和Around环绕后置通知方法都不会执行,异常信息会进入到AfterThrowing() 通知方法中。

2.3.5、异常处理

Spring Boot 对异常处理也做了很多的支持,开发者可以通过@ExceptionHandler注解来全局代理异常信息,实现方式也很简单。

编写一个全局异常处理类,示例如下。

@ControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理Exception异常
     * @param ex
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Map exceptionHandler(Exception ex){
        Map<String,Object> errorMap = new HashMap<>();
        errorMap.put("code","500");
        errorMap.put("message",ex.getMessage());
        return errorMap;
    }
}

将服务启动,访问http://localhost:8080/getUsersError,控制台输出结果如下。

可以看到,异常请求被成功接管。

03、参考

1、https://springdoc.cn/spring-boot/index.html

责任编辑:武晓燕 来源: 潘志的研发笔记
相关推荐

2011-03-08 16:50:35

2020-09-04 15:38:19

Web前端开发项目

2021-12-06 09:00:00

开发WebDjango

2021-03-02 06:32:03

Ansible系统运维

2020-11-25 19:05:50

云计算SaaS公有云

2019-11-13 15:44:17

Kafka架构数据

2021-03-15 08:18:10

Web安全黑客漏洞

2015-03-11 09:55:08

Web开发初学指南Web开发指南

2023-06-27 08:34:32

2020-05-11 09:54:33

JavaScript开发技术

2013-04-10 15:14:18

Web App开发WebApp

2023-02-08 16:20:39

2021-03-01 13:00:21

Ansible系统运维

2022-03-15 08:00:00

Flutter开发工具

2022-11-04 14:58:59

应用开发鸿蒙

2011-06-08 13:08:03

WEB设计

2021-07-28 06:51:08

FlaskPythonWeb

2010-12-23 13:45:23

Office 2010批量激活

2020-11-13 05:49:09

物联网城域网IOT

2019-09-02 14:51:33

点赞
收藏

51CTO技术栈公众号