Spring Boot 学习笔记

SpringBoot 简介

  1. 为什么要使用 Spring Boot

    因为 Spring,SpringMVC 需要使用的大量的配置文件 (xml文件)

    还需要配置各种对象,把使用的对象放入到 spring 容器中才能使用对象

    需要了解其他框架配置规则。

  2. SpringBoot 就相当于 不需要配置文件的 Spring+SpringMVC。 常用的框架和第三方库都已经配置好了。

    拿来就可以使用了。

  3. SpringBoot开发效率高,使用方便多了

JavaConfig

使用 java 类作为 xml 配置文件的替代, 是配置 spring 容器的纯 java 的方式。

在这个 java 类中可以创建 java 对象,把对象放入 spring 容器中(注入到容器)。

对于此JavaConfig相关的示例创建的是普通的Java项目,并非SpringBoot项目!

@Configuration

  • 放在类的上面,表示此类作为配置文件使用,相当于一个配置类

@Bean

  • 放在配置类中的方法上面,用来声明对象,将对象注入到容器中
  • 方法返回值需要是注入的对象类型
  • 若没有配置@Bean中的 name 属性,则从容器中获取该对象需要用其方法名
  • 若配置了@Bean中的 name 属性,则从容器中获取该对象需要用配置的 name 名称

需要使用到两个注解:

  • @Configuration:放在一个类的上面,表示这个类是作为配置文件使用的。
  • @Bean:放在配置类中的方法上,声明对象,把对象注入到容器中。

配置类示例:

/**
 * Configuration:表示当前类是作为配置文件使用的。 就是用来配置容器的
 *       位置:在类的上面
 *
 *  SpringConfig这个类就相当于beans.xml
 */
@Configuration
public class SpringConfig {

    /**
     * 创建方法,方法的返回值是对象。 在方法的上面加入@Bean
     * 方法的返回值对象就注入到容器中。
     *
     * @Bean: 把对象注入到spring容器中。 作用相当于<bean>
     *
     *     位置:方法的上面
     *
     *     说明:@Bean,不指定对象的名称,默认是方法名是 id
     *
     */
    @Bean
    public Student createStudent(){
        Student s1  = new Student();
        s1.setName("张三");
        s1.setAge(26);
        s1.setSex("男");
        return s1;
    }

    /***
     * 指定对象在容器中的名称(指定<bean>的id属性)
     * @Bean的name属性,指定对象的名称(id)
     */
    @Bean(name = "lisiStudent")
    public Student makeStudent(){
        Student s2  = new Student();
        s2.setName("李四");
        s2.setAge(22);
        s2.setSex("男");
        return s2;
    }
}

测试:

public class MyTest {

    @Test
    public void test01() {
        // 使用配置文件方式声明的bean,需要用 ClassPathXmlApplicationContext,参数传配置文件路径
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Student student = (Student) context.getBean("myStudent");
        System.out.println(student);
    }

    @Test
    public void test02() {
        // 使用配置类方式将对象注入到容器,需要使用 AnnotationConfigApplicationContext,参数传配置类的class 
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        // 配置类中方法的Bean设置name属性,则通过name属性名获取对象;否则默认可通过其方法名获取
        Student s = (Student) ctx.getBean("lisiStudent");
        // Student s = (Student) ctx.getBean("makeStudent");
        // Student s = (Student) ctx.getBean("createStudent");
        System.out.println(s);
    }
}

小结:

  • 使用配置类的方式,需要在配置类上使用@Configuration;需要在配置类中的方法上使用@Bean声明对象,将对象注入到容器中。
  • 配置类中的方法返回值类型是需要注入的对象类型;若未配置@Bean中的 name 属性,则获取该对象时需要使用方法名获取;若配置了@Bean中的 name 属性,则通过配置的 name 名称获取该对象。
  • 创建容器对象时,不再使用ClassPathXmlApplicationContext接口实现类,不再通过配置文件来注入到容器对象中;而是使用AnnotationConfigApplicationContext接口实现类,通过配置类的 class 来将对象注入到容器。

@ImportResource

  • 使用在配置类的上面
  • 作用是导入其他的 xml 配置文件, 等于在 xml 中使用 <import resources="其他配置文件"/>
  • 需要在 value 属性中指定配置文件的路径
@Configuration
@ImportResource(value = "classpath:applicationContext.xml")
public class SpringConfig {
}

@PropertySource

  • 使用在配置类的上面
  • 用来读取 properties 属性配置文件
  • 使用属性配置文件可以实现外部化配置 ,在程序代码之外提供数据。

@ComponentScan:使用在配置类上,扫描指定包中注解,来创建对象以及给属性赋值

示例:

--配置类--
@Configuration
@ImportResource(value = "classpath:applicationContext.xml")  // 导入xml配置文件
@PropertySource(value = "classpath:config.properties")  // 读取属性配置文件
@ComponentScan(basePackages = "com.luis.vo")  // 声明组件扫描器,扫描指定包中的注解
public class SpringConfig {
}

--config.properties--
tiger.name=东北虎
tiger.age=3 

--vo数据类--    
@Component("tiger")
public class Tiger {

    @Value("${tiger.name}")
    private String name;

    @Value("${tiger.age}")
    private Integer age;    
}  

--测试--
@Test
public void test04() {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    Tiger tiger = (Tiger) ctx.getBean("tiger");
    System.out.println(tiger);
}    

SpringBoot 入门

SpringBoot 是 Spring 中的一个成员,可简化 Spring,SpringMVC 的使用。

其核心还是 IOC 容器。

SpringBoot 特点

  • Create stand-alone Spring applications

    创建 spring 应用

  • Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)

    有内嵌的 tomcat,jetty,Undertow

  • Provide opinionated 'starter' dependencies to simplify your build configuration

    提供了 starter 起步依赖,简化应用的配置。

    比如使用 MyBatis 框架,需要在 Spring 项目中,配置 MyBatis 的对象 SqlSessionFactory ,Dao 的代理对象

    如今在 SpringBoot 项目中,在 pom.xml 里面,只需加入一个 mybatis-spring-boot-starter 依赖即可

  • Automatically configure Spring and 3rd party libraries whenever possible

    尽可能去配置 spring 和第三方库,也叫做自动配置(就是把 spring 中的,第三方库中的对象都创建好,放到容器中,开发人员可以直接使用)

  • Provide production-ready features such as metrics, health checks, and externalized configuration

    提供了健康检查,统计,外部化配置

  • Absolutely no code generation and no requirement for XML configuration

    不用生成代码,不用使用xml,做配置

创建 SpringBoot 项目的三种方式

创建 SpringBoot 项目有三种方式:

  1. 使用 Spring 提供的初始化器,就是使用向导创建 SpringBoot 应用(使用 https://start.spring.io 地址)【联网】

  2. 使用 Spring 提供的初始化器,就是使用向导创建 SpringBoot 应用(使用国内的 https://start.springboot.io 地址)【联网】

    还可以选择使用阿里云的地址进行创建:https://start.aliyun.com

  3. 先直接创建 maven 项目,然后补充相关依赖,补全目录结构即可【不需联网】

创建简单 SpringBoot web 项目示例:(以下选择向导方式创建)

  1. 新建 SpringBoot 模块,选择 Spring Initializr向导创建

  2. 配置项目相关信息,依赖选择 Spring Web 进行创建

  3. 整理项目结构以及 pom 文件

  4. 在主启动类所在的包下创建 controller 控制器类

    @Controller
    public class HelloSpringBoot {
    
        @RequestMapping("/hello")
        @ResponseBody
        public String helloSpringBoot() {
            return "欢迎使用SpringBoot";
        }
    }
    
  5. 查看是否配置有 Spring Boot 启动服务,若没有则进行配置

  6. 运行主启动类的 main 方法或 IDEA 主面板运行项目

  7. 浏览器输入地址访问 http://localhost:8080/hello(默认8080端口,无上下文)

@SpringBootApplication

  • 其自动配置在主类之上,每个 SpringBoot 项目都会有一个主类

    @SpringBootApplication
    public class SpringbootTest03Application {
        public static void main(String[] args) {
            SpringApplication.run(SpringbootTest03Application.class, args);
        }
    }
    
@SpringBootApplication
是一个复合注解,包含以下三个注解(以下注解的功能其全具备)
@SpringBootConfiguration  -----> 该注解标注的类可当做配置类使用
@EnableAutoConfiguration  -----> 启用自动配置,将常用框架以及第三方对象创建好
@ComponentScan  -----> 可扫描到该注解标注的类的包及其子包下其他注解
    
详解:    
1.@SpringBootConfiguration
    
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
说明:使用了@SpringBootConfiguration注解标注的类,可以作为配置文件使用,
    可以使用@Bean声明对象,注入到容器,
    故主类可作为一个配置类使用,在其类中可在方法上使用@Bean声明对象

2.@EnableAutoConfiguration
启用自动配置,把java对象配置好,注入到spring容器中;例如可以把mybatis的对象创建好,放入到容器中。
    
3.@ComponentScan 
扫描器,找到注解,根据注解的功能创建对象,给属性赋值等。
默认扫描的包: @ComponentScan所标注的类所在的包和子包。    

SpringBoot 的配置文件

避免中文乱码,需要在 IDEA 中设置 encoding 为 UTF-8

配置文件名: application

扩展名有:properties( k=v) 以及 yml ( k: v)

可使用 application.properties,application.yml 两种文件格式

创建 SpringBoot 项目时默认创建的是 application.properties 配置文件格式

以下为配置示例:

例1:application.properties 中设置端口和上下文

# 设置端口
server.port=8888
# 设置访问应用的上下文路径
server.servlet.context-path=/myWeb

例2:application.yml 中设置端口和上下文(推荐使用

这种格式的配置文件,结构比较清晰

注意:value 值前有一个空格

server:
  port: 8083
  servlet:
    context-path: /myboot

注意:如果同时存在两种格式的配置文件,即 application.properties 和 application.yml 两个配置文件同时存在,则默认使用 application.properties 这种格式中的配置!

多环境配置的应用

有开发环境,测试环境,上线的环境。

每种环境有不同的配置信息,例如端口,上下文访问路径,数据库 url,用户名,密码等,所以需要多种环境配置。

使用多环境配置文件,可以方便的切换不同的配置。

多环境配置方式

  1. 创建多个配置文件,文件命名规则:application-环境名称.properties(yml)【一般环境名使用 dev/test/online 这几种】

    创建开发环境的配置文件:application-dev.yml

    #项目开发使用的环境
    server:
      port: 8080
      servlet:
        context-path: /mydev
    

    创建测试环境的配置文件:application-test.yml

    #项目测试使用的环境
    server:
      port: 8081
      servlet:
        context-path: /mytest
    

    创建上线环境的配置文件:application-online.yml

    #项目上线使用的环境
    server:
      port: 8082
      servlet:
        context-path: /myonline
    
  2. 在主配置文件 application.yml 中激活要使用的环境【激活的配置信息是根据多环境文件的命名来确定】

    主配置文件中激活要使用的环境:application.yml

    #激活使用的环境
    spring:
      profiles:
        active: online
    

使用 @Value 读取配置文件数据

我们可以在主配置文件中使用 key-value 键值对的格式进行数据的配置;

因为主配置文件已经由 Spring 自动管理,所以可以直接在自己写的控制器类的属性上通过 @Value("${server.port}")读取配置文件中数据,并对属性进行赋值。

@Controller
public class MyController {

    @Value("${server.port}")
    private Integer port;
    @Value("${server.servlet.context-path}")
    private String contextPath;
    @Value("${project.name}")
    private String projectName;
    @Value("${project.dev}")
    private String projectDev;

    @RequestMapping("/data")
    @ResponseBody
    public String queryData() {
        return port + "========> port " + contextPath + "========> contextPath "
                + projectName + "========> projectName " + projectDev + "========> projectDev";
    }
}

@ConfigurationProperties

  • 使用位置:类的上面
  • 作用:把配置文件中符合条件的数据映射为 java 对象
  • 需要配置其 prefix 属性:即配置文件中的某些 key 开头的内容

使用示例:@ConfigurationProperties(prefix = "school")

使用解释:类上添加@ConfigurationProperties注解后,其将从属性配置文件中找以配置的 prefix 属性值,即school开头的配置,将所有 school后面的配置参数与该类中的所有属性相比较,如果匹配成功,则将配置文件中配置的对应的数据赋值给该类中同名属性,完成数据映射。

使用示例:

  1. 在配置文件 application.properties 中配置自定义数据

    #配置端口号
    server.port=8888
    #配置上下文访问路径
    server.servlet.context-path=/myboot
    
    #自定义数据
    school.name=哈师大
    school.addr=东北
    school.level=2
    
  2. 创建数据对象

    @Component
    @ConfigurationProperties(prefix = "school")
    public class SchoolInfo {
    
        private String name;
        private String addr;
        private Integer level;
        // getter,setter,toString...
    }    
    
  3. 创建控制器类

    @Controller
    public class MyController {
    
        @Resource
        private SchoolInfo info;
    
        @RequestMapping("/info")
        @ResponseBody
        public String querySchoolInfo() {
            return "info = " + info;
        }
    }
    
  4. 运行,访问测试

    访问:http://localhost:8888/myboot/info
    结果:info = SchoolInfo{name='哈师大', addr='东北', level=2}
    

SpringBoot 中使用 JSP(不推荐,了解即可)

SpringBoot 默认不支持 JSP,需要进行相关配置才能使用 JSP。

JSP 技术慢慢被淘汰,有更好的技术替代它。

使用 SpringBoot 框架时不推荐使用 JSP,后面将使用 Thymeleaf 模板技术完全替代 JSP!

使用步骤:

  1. 创建 SpringBoot 项目,整理项目结构和 pom 文件

  2. 添加一个处理 JSP 的依赖,负责编译 JSP 文件(如果需要使用 servlet jsp jstl 功能则需要另外加对应依赖)

    <!--处理JSP的依赖-->
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
    </dependency>
    
    <dependency>
    	<groupId>javax.servlet</groupId>
    	<artifactId>jstl</artifactId>
    </dependency>
    
    <dependency>
    	<groupId>javax.servlet</groupId>
    	<artifactId>javax.servlet-api</artifactId>
    </dependency>
    
    <dependency>
    <groupId>javax.servlet.jsp</groupId>
    	<artifactId>javax.servlet.jsp-api</artifactId>
    	<version>2.3.1</version>
    </dependency>
    
  3. 在 main 目录下创建一个名为 webapp 的目录(名称固定);在项目工程结构中设置该目录为 web 资源目录(进入项目结构设置后,点击 Web 选项,从 Web Resource Directories 下添加新创建的 webapp 目录为 web 资源目录即可)

  4. 在 webapp 目录下新建 index.jsp 用来显示 controller 中的数据

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>jsp</title>
    </head>
    <body>
        SpringBoot中使用JSP显示数据:${data}
    </body>
    </html>
    
  5. 创建 controller 控制器类,指定数据和视图

    @Controller
    public class JspController {
    
        @RequestMapping("/myjsp")
        public String doJsp(Model model) {
            // 将数据放到request中(与request.setAttribute作用相同)
            model.addAttribute("data", "SpringBoot使用JSP");
            return "index";
        }
    }
    
  6. 配置文件中配置视图解析器,端口号以及上下文等信息

    #配置端口号
    server.port=7070
    #配置上下文访问路径
    server.servlet.context-path=/myboot
    
    #配置视图解析器
    # / = src/main/webapp
    spring.mvc.view.prefix=/
    spring.mvc.view.suffix=.jsp
    
  7. 在 pom.xml 中指定 jsp 文件编译后的存放目录【在 pom 的 build 标签下进行指定】

    修改 pom 文件后,记得重新加载下,确保 pom 文件资源完全刷新!

    <!-- 指定jsp编译后的存放目录 -->
    <resources>
        <resource>
            <!-- jsp原来的目录 -->
            <directory>src/main/webapp</directory>
            <!-- 指定编译后的存放目录 -->
            <targetPath>META-INF/resources</targetPath>
            <!-- 指定治理后的目录和文件 -->
            <includes>
                <include>**/*.*</include>
            </includes>
        </resource>
    </resources>
    
  8. 直接运行并启动主程序,输入地址访问测试

    http://localhost:7070/myboot/myjsp
    

两点需要注意下:

  • 必须添加处理 jsp 的依赖
  • 必须指定 jsp 编译后的存放目录

原因:因为 SpringBoot 使用的是内嵌的 Tomcat

SpringBoot 使用容器 ApplicationContext

使用场景:不想启动整个项目,只想测试某个功能,就可直接通过run方法获取容器对象,通过容器对象进行相关测试。

如果想通过代码,从容器中获取对象:

通过SpringApplication.run(Application.class, args); 其返回值即可获取容器对象。

SpringApplication.run(SpringbootUseJspApplication.class, args)返回的就是一个 ApplicationContext 容器对象!

解析:SpringApplication.run(SpringbootUseJspApplication.class, args);

点进其run方法:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class[]{primarySource}, args);
}   

查看ConfigurableApplicationContext类:
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {...}    

总结:通过run方法获取的是容器对象ApplicationContext的子类,即也是一个容器对象。

容器使用示例:

  1. 创建 SpringBoot 项目时选择 Spring Web 依赖,整理项目目录和 pom 文件

  2. 创建 service 接口及其实现类,定义测试方法

    public interface UserService {
        void sayHello(String name);
    }
    
    @Service("userService")
    public class UserServiceImpl implements UserService {
    
        @Override
        public void sayHello(String name) {
            System.out.println("hello " + name);
        }
    }
    
  3. 在主启动类中直接获取并使用容器对象进行测试

    @SpringBootApplication
    public class SpringbootUseContainerApplication {
    
        public static void main(String[] args) {
            // 直接通过run方法获取容器对象
            ApplicationContext ctx = SpringApplication.run(SpringbootUseContainerApplication.class, args);
            // 从容器中获取指定对象
            UserService userService = (UserService) ctx.getBean("userService");
            // 调用对象的方法进行测试
            userService.sayHello("luis");
        }
    }
    

CommandLineRunner 和 ApplcationRunner 接口

介绍:这两个接口都有一个 run 方法。

执行时机:容器对象创建好后自动会执行接口中的 run() 方法

使用场景:需要在容器启动后执行一些内容,比如读取配置文件,数据库连接之类的。

使用方式:实现接口,重写 run 方法,方法中写需要在容器启动后待执行的内容。

@FunctionalInterface
public interface CommandLineRunner {
    void run(String... args) throws Exception;
}

@FunctionalInterface
public interface ApplicationRunner {
    void run(ApplicationArguments args) throws Exception;
}

使用示例:(以下以实现 CommandLineRunner接口为例)

  1. 创建 SpringBoot 项目,不用选择依赖,整理项目目录和 pom 文件

  2. 新建 service 接口以及实现类,定义测试方法

    public interface UserService {
        void sayHello(String name);
    }
    
    @Service("userService")
    public class UserServiceImpl implements UserService {
        @Override
        public void sayHello(String name) {
            System.out.println("你好," + name);
        }
    }
    
  3. 让启动类实现 CommandLineRunner接口,重写 run 方法进行测试

    @SpringBootApplication
    public class SpringbootRunApplication implements CommandLineRunner {
    
        @Resource
        private UserService userService;
    
        public static void main(String[] args) {
            System.out.println("main start");
            // 创建容器对象
            SpringApplication.run(SpringbootRunApplication.class, args);
            System.out.println("main end");
        }
    
        @Override
        public void run(String... args) throws Exception {
            System.out.println("容器对象创建后执行此run方法");
            // 此方法在容器对象创建后自动执行
            // 可在此调用容器对象中的方法
            userService.sayHello("jack");
        }
    }
    
  4. 运行主程序,进行测试

    运行结果:

    main start
    容器对象创建后执行此run方法
    你好,jack
    main end
    

SpringBoot 和 Web 组件

讲三个内容:拦截器、Servlet、Filter

SpringBoot 中使用拦截器

拦截器是 SpringMVC 中的一种对象,能拦截对 Controller 的请求。

在框架中有系统实现好的拦截器, 也可以自定义拦截器,实现对请求的预先处理。

回顾 SpringMVC 中使用拦截器

  1. 创建类实现 SpringMVC 框架的 HandlerInterceptor 接口,重写对应的方法

    public interface HandlerInterceptor {
     default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         return true;
     }
    
     default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
     }
    
     default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
     }
    }
    
  2. 在 SpringMVC 的配置文件中,声明拦截器

    <mvc:interceptors>
    	<mvc:interceptor>
        	<mvc:path="url" />
            <bean class="拦截器类全限定名称"/>
        </mvc:interceptor>
    </mvc:interceptors>
    

SpringBoot 中使用拦截器

示例:

  1. 创建 SpringBoot 项目,选择 Spring Web 依赖,整理项目目录和 pom 文件

  2. 创建拦截器类,实现 HandlerInterceptor接口,重写相关方法

    public class LoginInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("执行LoginInterceptor拦截器类的preHandle方法");
            return true;
        }
    }
    
  3. 创建自定义配置类实现 WebMvcConfigurer接口,重写addInterceptors接口方法,在此方法中进行拦截器的相关配置(注入拦截器对象,指定拦截地址等)

    @Configuration // 成为配置类=====
    public class MyAppConfig implements WebMvcConfigurer {
    
        // 重写addInterceptors方法,添加拦截器对象,注入到容器中
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
    
            // 创建拦截器对象
            HandlerInterceptor loginInterceptor = new LoginInterceptor();
    
            // 指定拦截的请求URI地址
            String[] path = {"/user/**"};
            // 指定不拦截的URI地址
            String[] excludePath = {"/user/login"};
    
            // 添加拦截器对象,指定拦截以及不拦截的URI地址
            registry.addInterceptor(loginInterceptor)
                    .addPathPatterns(path)
                    .excludePathPatterns(excludePath);
        }
    }
    
  4. 创建控制器类,进行资源访问拦截测试

    @Controller
    @RequestMapping("/user")
    public class BootController {
    
        @RequestMapping("/account")
        @ResponseBody
        public String userAccount() {
            return "访问/user/account地址";
        }
    
        @RequestMapping("/login")
        @ResponseBody
        public String userLogin() {
            return "访问/user/login地址";
        }
    }
    
  5. 运行主程序,进行访问测试

    分别访问下列两个地址:
    http://localhost:8080/user/account
    http://localhost:8080/user/login
    访问的测试结果:
    访问http://localhost:8080/user/account时,后台有拦截器拦截信息输出【此请求走拦截器,被拦截器拦截】
    访问http://localhost:8080/user/login时,后台无拦截器拦截信息输出【此请求不走拦截器,不会被拦截器拦截】
    

SpringBoot 中使用 Servlet

使用步骤:

  1. 创建 SpringBoot 项目,选择 Spring Web 依赖,整理项目目录和 pom 文件

  2. 创建 servlet 类,继承 HttpServlet,重写相关方法

    public class MyServlet extends HttpServlet {
    
        // 直接重写service方法,则doGet和doPost请求都直接走此方法
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
            // 设置字符编码
            response.setContentType("text/html;charset=UTF-8");
    
            // 使用应答对象HttpServletResponse,输出应答结果
            PrintWriter out = response.getWriter();
            out.print("SpringBoot中使用Servlet");
            out.flush();
            out.close();
        }
    }
    
  3. 创建自定义配置类,在类中定义指定返回值类型的方法,注册 Servlet 对象

    @Configuration
    public class WebApplicationConfig {
    
        // ====定义指定返回值类型的方法,注册servlet对象====
        @Bean
        public ServletRegistrationBean servletRegistrationBean() {
    
            // 两种方式注册servlet:
    
            // 方式一:使用ServletRegistrationBean对象的有参构造
            // 下列对象构造方法参数:第一个是需要注册的servlet对象,第二个是对应servlet的访问地址
            // ServletRegistrationBean bean = new ServletRegistrationBean(new MyServlet(), "/login1", "/login2");
    
            // 方式二:使用ServletRegistrationBean对象的无参构造
            ServletRegistrationBean bean = new ServletRegistrationBean();
            bean.setServlet(new MyServlet());
            bean.addUrlMappings("/login3", "/login4");
            return bean;
        }
    }
    
  4. 运行主程序,输入地址访问测试

    http://localhost:8080/login1
    http://localhost:8080/login2
    http://localhost:8080/login3
    http://localhost:8080/login4
    

SpringBoot 中使用过滤器

Filter 是 Servlet 规范中的过滤器,可以处理请求,对请求的参数,属性进行调整。

常常在过滤器中处理字符编码。

SpringBoot 中使用过滤器 Filter:

使用步骤:

  1. 创建 SpringBoot 项目,选择 Spring Web 依赖,整理项目目录和 pom 文件,刷新 pom

  2. 创建自定义的过滤器类,实现 javax.servlet.Filter 接口,重写 doFilter 方法(注意实现的是 servlet 中的 Filter 接口)

    public class MyFilter implements Filter {
        
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("执行自定义过滤器类MyFilter中的doFilter方法");
            // 放行
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }
    
  3. 创建自定义配置类,在类中定义指定返回值类型的方法,注册过滤器对象

    @Configuration
    public class WebApplicationConfig {
    
        @Bean
        public FilterRegistrationBean filterRegistrationBean() {
            
            // 注册自定义过滤器类,并指定过滤的地址
            FilterRegistrationBean bean = new FilterRegistrationBean();
            bean.setFilter(new MyFilter());
            bean.addUrlPatterns("/user/*");
            return bean;
        }
    }
    
  4. 创建控制器类,进行资源访问过滤测试

    @Controller
    public class CustomFilterController {
    
        @RequestMapping("/user/login")
        @ResponseBody
        public String userLogin() {
            return "访问/user/login";
        }
    
        @RequestMapping("/query")
        @ResponseBody
        public String query() {
            return "访问/query";
        }
    }
    
  5. 运行主启动类,输入访问地址测试

    http://localhost:8080/query
    http://localhost:8080/user/login
    

SpringBoot 中使用字符集过滤器

一般,都是使用字符集过滤器 CharacterEncodingFilter解决 post 请求中文乱码的问题。

CharacterEncodingFilter : 解决 post 请求中文乱码的问题。

在 SpringMVC 框架, 需要在 web.xml 注册过滤器并配置其相关属性。

在SpringBoot 中使用字符集过滤器解决 post 请求乱码有两种方式:

使用示例:

  1. 创建 SpringBoot 项目,选择 Spring Web 依赖,整理项目目录和 pom 文件,刷新 pom

  2. 创建自定义 servlet,处理请求

    public class MyServlet extends HttpServlet {
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
            response.setContentType("text/html"); // 此处并没有设置字符编码方式
    
            PrintWriter out = response.getWriter();
            out.write("没有设置编码方式,默认使用ISO-8859-1,会出现中文乱码");
            out.flush();
            out.close();
        }
    }
    
  3. 创建自定义配置类,注册 servlet,设置访问路径

    @Configuration
    public class WebSystemConfig {
    
        // 注册servlet,设置访问路径
        @Bean
        public ServletRegistrationBean servletRegistrationBean() {
    
            ServletRegistrationBean bean = new ServletRegistrationBean();
            bean.setServlet(new MyServlet());
            bean.addUrlMappings("/myservlet");
            return bean;
        }
    }
    
  4. 启动主程序,输入访问地址进行测试

    http://localhost:8080/myservlet
    结果:出现中文乱码
    ?????????????ISO-8859-1????????
    

方式一:配置类中设置字符编码(不推荐)

不仅需要在配置类中注册字符集过滤器类,设置字符编码,还需在配置文件中关闭默认启用的过滤器。

  1. 在自定义配置类中需创建指定返回值类型的方法,注册 CharacterEncodingFilter字符集过滤器类,设置字符编码
@Configuration
public class WebSystemConfig {

    // 注册servlet,设置访问路径
    @Bean
    public ServletRegistrationBean servletRegistrationBean() {

        ServletRegistrationBean bean = new ServletRegistrationBean();
        bean.setServlet(new MyServlet());
        bean.addUrlMappings("/myservlet");
        return bean;
    }

    // ==================================================================
    // 注册CharacterEncodingFilter,并设置字符编码方式
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {

        FilterRegistrationBean reg = new FilterRegistrationBean();

        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        // 设置字符编码方式
        filter.setEncoding("UTF-8");
        // 指定request,response都使用encoding的值
        filter.setForceEncoding(true);

        reg.setFilter(filter); // 注册字符集过滤器对象CharacterEncodingFilter
        reg.addUrlPatterns("/*"); // 设置过滤地址

        return reg;
    }
    // ==================================================================    
}
  1. 修改 application.properties 文件,让自定义的过滤器起作用

    #SpringBoot中默认已经配置了CharacterEncodingFilter。 编码默认ISO-8859-1
    #设置enabled=false 作用是关闭系统中配置好的过滤器, 使用自定义的CharacterEncodingFilter
    server.servlet.encoding.enabled=false
    

方式二:配置文件中设置字符编码(推荐)

只需要通过几行配置,即可完成字符编码的设置。

#让系统的CharacterEncdoingFilter生效(默认为true)
server.servlet.encoding.enabled=true
#指定使用的编码方式
server.servlet.encoding.charset=UTF-8
#强制request,response都使用charset属性的值
server.servlet.encoding.force=true

ORM 操作 MySQL

ORM 是“对象-关系-映射”的简称。(Object Relational Mapping,简称ORM)

使用 MyBatis 框架操作 MySQL。

使用 MyBatis 框架操作数据,使用 SpringBoot 框架集成 MyBatis。

SpringBoot 集成 Mybatis

以下是集成步骤示例,创建 dao 接口的代理对象有好几种方式可供选择,以下方式并不完美,注意后续自己修改!

前期准备
数据库名:springdb
数据库表:student
字段:id (int auto_increment) name(varchar) age(int)

步骤:

  1. 创建 SpringBoot 项目,添加 Spring Web、MyBatis、MySQL 依赖,整理项目目录和 pom 文件,刷新 pom

  2. 创建实体类 model

    public class Student {
        private Integer id;
        private String name;
        private Integer age;
        // getter,setter,toString...
    }    
    
  3. 写 dao 接口和 mapper 文件

    此处 dao 接口上使用了 @Mapper注解,告诉MyBatis这是 dao 接口,创建此接口的代理对象

    @Mapper  // 告诉MyBatis这是dao接口,创建此接口的代理对象
    public interface StudentDao {
    
        Student selectById(@Param("stuId") Integer id);
    }
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.luis.dao.StudentDao">
    
        <select id="selectById" resultType="com.luis.model.Student">
            select id,name,age from student where id=#{stuId}
      </select>
    
    </mapper>
    
  4. 创建 service 接口及其实现类

    // 接口
    public interface StudentService {
    
        Student queryStudentById(Integer id);
    }
    
    // 实现类
    @Service
    public class StudentServiceImpl implements StudentService {
    
        @Resource
        private StudentDao studentDao;
    
        @Override
        public Student queryStudentById(Integer id) {
            return studentDao.selectById(id);
        }
    }
    
  5. 创建控制器类

    @Controller
    @RequestMapping("/student")
    public class StudentController {
        
        @Resource
        private StudentService studentService;
    
        @RequestMapping("/queryById")
        @ResponseBody
        public String queryStudentById(Integer id) {
            Student student = studentService.queryStudentById(id);
            return student.toString();
        }
    }
    
  6. 在 pom 文件的 build 标签下添加 mapper 文件的 resources 插件

    <!--   resources插件,将mapper文件复制到classes目录下     -->
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
    
  7. 在主配置文件中配置端口,上下文访问路径,数据库连接等信息

    server.port=8888
    server.servlet.context-path=/orm
    
    #配置数据库连接信息 注意mysql新旧版本配置不同
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/springdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=luis
    
  8. 运行启动类,输入访问地址测试【注意:填的id必须数据库中有,否则会出现空指针异常】

    http://localhost:8888/orm/student/queryById?id=29
    

日志配置

在主配置文件中配置 mybatis 日志:(可选不同的日志打印,如选 log4j 这种的,则另需添加相关依赖)

# 日志配置
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

第一种方式:使用@Mapper(不建议)

  • 使用在类上
  • 作用:告诉 MyBatis 这是 dao 接口,会自动创建此接口的代理对象
  • 缺点:每一个需要创建代理对象的 dao 接口都需要加上该注解,dao 接口多的时候不方便!

示例:

@Mapper  // 告诉MyBatis这是dao接口,创建此接口的代理对象
public interface StudentDao {

    Student selectById(@Param("stuId") Integer id);
}

第二种方式:使用@MapperScan

  • 通常使用在主启动类上
  • basePackages属性:通常配置 dao 接口所在包名
  • 作用:找到 Dao 接口以及 Mapper 文件(可直接扫描到该包下所有的 dao 接口和 mapper 文件)
  • 优点:使用该注解直接扫描指定包,不用在每一个 dao 接口上分别加 @Mapper注解

示例:

@SpringBootApplication
@MapperScan(basePackages = "com.luis.dao") // 扫描指定包,找到dao接口和mapper文件,mybatis自动创建接口的代理对象
public class SpringbootMapperApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootMapperApplication.class, args);
    }

}

第三种方式:Mapper文件和Dao接口分开管理(开发常用,推荐使用)

之前我们的 dao 目录下既有 Java 文件,也有 xml/mapper 文件;如果希望 Java 目录下只有 Java 文件,将 xml/mapper 文件放到 resources 下分开管理,那么只使用之前的 @MapperScan注解是扫描不到 resources 下的 Mapper 文件的,此时我们就需要在配置文件中指定 Mapper 文件的位置。

示例:

操作:在 resources 下创建子目录(自定义的),例如 mapper,用来存放 mapper 映射文件,将之前 dao 下的 mapper 文件全剪切到 mapper 目录下。

  1. 同样,我们仍然需要在主启动类上使用 @MapperScan注解指定 dao 所在的包,扫描 dao 接口:
@SpringBootApplication
@MapperScan(basePackages = "com.luis.dao") // 扫描指定包,找到dao接口和mapper文件,mybatis自动创建接口的代理对象
public class SpringbootMapperApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootMapperApplication.class, args);
    }

}
  1. 在主配置文件中指定 mapper 映射文件的位置:
#指定mapper文件的位置
mybatis.mapper-locations=classpath:mapper/*.xml
  1. 配置资源插件,确保资源文件编译到 classes 目录下:
<!--   resources插件,将所有资源文件复制到classes目录下【配置在pom.xml的build标签下】     -->
<resources>
    <resource>
        <directory>src/main/resources</directory>
        <includes>
            <include>**/*.*</include>
        </includes>
    </resource>
</resources>

注意:如果项目编译后 .xml/properties/yml 文件没有出现在 classes 目录下,则需要在 pom 中添加 resources 资源插件。

事务及mybatis自动代码生成器的使用

回顾 Spring 框架中的事务:

  1. 管理事务的对象:事务管理器(接口,接口有很多的实现类)

    例如:使用 Jdbc 或 mybatis 访问数据库,使用的事务管理器:DataSourceTransactionManager

  2. 声明式事务:使用 xml 配置文件或者使用注解说明事务控制的内容

    控制事务:隔离级别,传播行为,超时时间

  3. 事务处理方式:

    1. Spring 框架中的 @Transactional
    2. Spring 支持 aspectj 框架的事务配置,可在 xml 配置文件中,声明事务控制的内容

SpringBoot 中使用事务:上面的两种方式都可以。

使用步骤:(以下以注解方式演示事务的使用:)

  1. 在业务方法的上面加入 @Transactional ,加注解后,方法就有事务功能

  2. 明确的在主启动类的上面,加入@EnableTransactionManager,启用事务管理器(可不用添加此注解

/**
     * @Transactional 表示方法有事务支持,抛出运行时异常,则进行回滚
     * 使用MySQL默认的传播行为、隔离级别、超时时间
     */
@Transactional // 可通过属性配置传播行为、隔离级别、超时时间
@Override
public int addUser(User user) {
    System.out.println("业务方法addUser开始执行");
    int rows = userMapper.insert(user);
    System.out.println("业务方法addUser结束执行");

    // 模拟发生运行时异常
    // int num = 10 / 0;
    return rows;
}

SpringBoot 中通过注解使用事务,一句话说明:直接在需要开启事务的业务方法上添加 @Transactional 注解即可!

示例:(有mybatis自动代码生成器的使用示例,可自动生成 model 实体类, dao 接口以及 mapper 映射文件)

准备数据库表:
数据库名:springdb 表名:user
字段:id(int auto_increment)name(varchar)age(int)
  1. 创建 SpringBoot 项目,添加 Spring Web、MyBatis、MySQL 依赖,整理项目目录和 pom 文件

  2. 在 pom 的 plugins 标签下添加 mybatis 自动代码生成插件

    <!--mybatis自动代码生成插件-->
    <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.3.6</version>
        <configuration>
            <!--配置文件的位置:在项目的根目录下,和src平级-->
            <configurationFile>GeneratorMapper.xml</configurationFile>
            <verbose>true</verbose>
            <overwrite>true</overwrite>
        </configuration>
    </plugin>
    
  3. 在 pom 的 build 标签下添加资源插件,处理资源文件,刷新 pom

    <!--   处理资源文件     -->
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/**.*</include>
            </includes>
        </resource>
    </resources>
    
  4. 在项目根目录下创建 GeneratorMapper.xml文件,其与 src 平级;将下列内容拷贝到文件中;依照注释修改配置,修改完后,点开右侧 maven 快捷窗口,使用当前项目下 plugins 下 mybatis-genetate 下的 mybatis 自动代码生成插件进行生成。

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    
    <generatorConfiguration>
    
        <!-- 指定连接数据库的JDBC驱动包所在位置,指定到你本机的完整路径 -->
        <classPathEntry location="D:\Java\tools\mysql-connector-java-8.0.28.jar"/>
    
        <!-- 配置table表信息内容体,targetRuntime指定采用MyBatis3的版本 -->
        <context id="tables" targetRuntime="MyBatis3">
    
            <!-- 抑制生成注释,由于生成的注释都是英文的,可以不让它生成 -->
            <commentGenerator>
                <property name="suppressAllComments" value="true" />
            </commentGenerator>
    
            <!-- 配置数据库连接信息 -->
            <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                            connectionURL="jdbc:mysql://localhost:3306/springdb?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT%2B8"
                            userId="root"
                            password="luis">
                <!--MySQL 不支持 schema 或者 catalog 所以需要添加这个-->
                <!--设置原因参考:https://blog.csdn.net/qq_40233736/article/details/83314596-->
                <property name="nullCatalogMeansCurrent" value="true" />
            </jdbcConnection>
    
            <!-- 生成model类,targetPackage指定model类的包名, targetProject指定生成的model放在eclipse的哪个工程下面-->
            <javaModelGenerator targetPackage="com.luis.model"
                                targetProject="D:\1a-Projects\springboot-projects\springboot-transactional\src\main\java">
                <property name="enableSubPackages" value="false" />
                <property name="trimStrings" value="false" />
            </javaModelGenerator>
    
            <!-- 生成MyBatis的Mapper.xml文件,targetPackage指定mapper.xml文件的包名, targetProject指定生成的mapper.xml放在eclipse的哪个工程下面 -->
            <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
                <property name="enableSubPackages" value="false" />
            </sqlMapGenerator>
    
            <!-- 生成MyBatis的Mapper接口类文件,targetPackage指定Mapper接口类的包名, targetProject指定生成的Mapper接口放在eclipse的哪个工程下面 -->
            <javaClientGenerator type="XMLMAPPER" targetPackage="com.luis.dao" targetProject="src/main/java">
                <property name="enableSubPackages" value="false" />
            </javaClientGenerator>
    
            <!-- 数据库表名及对应的Java模型类名 -->
            <table tableName="user" domainObjectName="User"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
    
            <table tableName="b_bid_info" domainObjectName="BidInfo"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
    
    
            <table tableName="b_income_record" domainObjectName="IncomeRecord"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
    
    
            <table tableName="b_recharge_record" domainObjectName="RechargeRecord"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
    
    
            <table tableName="u_user" domainObjectName="User"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
    
    
            <table tableName="u_finance_account" domainObjectName="FinanceAccount"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
        </context>
    
    </generatorConfiguration>
    
  5. 主配置文件配置

    #配置端口
    server.port=9090
    #context-path
    server.servlet.context-path=/mytrans
    
    #数据库连接配置
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/springdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=luis
    
    #配置mapper文件路径
    mybatis.mapper-locations=classpath:mapper/*.xml
    #开启日志
    mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
  6. 使用 mybatis 生成器生成的 model、dao、mapper 文件

    // model
    public class User {
        private Integer id;
        private String name;
        private Integer age;
        // get,set
    }
    
    // dao
    public interface UserMapper {
        int deleteByPrimaryKey(Integer id);
    
        int insert(User record);
    
        int insertSelective(User record);
    
        User selectByPrimaryKey(Integer id);
    
        int updateByPrimaryKeySelective(User record);
    
        int updateByPrimaryKey(User record);
    }
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.luis.dao.UserMapper">
        <resultMap id="BaseResultMap" type="com.luis.model.User">
            <id column="id" jdbcType="INTEGER" property="id" />
            <result column="name" jdbcType="VARCHAR" property="name" />
            <result column="age" jdbcType="INTEGER" property="age" />
        </resultMap>
        <sql id="Base_Column_List">
            id, name, age
        </sql>
        <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
            select 
            <include refid="Base_Column_List" />
            from user
            where id = #{id,jdbcType=INTEGER}
        </select>
        <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
            delete from user
            where id = #{id,jdbcType=INTEGER}
        </delete>
        <insert id="insert" parameterType="com.luis.model.User">
            insert into user (id, name, age
            )
            values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}
            )
        </insert>
        <insert id="insertSelective" parameterType="com.luis.model.User">
            insert into user
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="id != null">
                    id,
                </if>
                <if test="name != null">
                    name,
                </if>
                <if test="age != null">
                    age,
                </if>
            </trim>
            <trim prefix="values (" suffix=")" suffixOverrides=",">
                <if test="id != null">
                    #{id,jdbcType=INTEGER},
                </if>
                <if test="name != null">
                    #{name,jdbcType=VARCHAR},
                </if>
                <if test="age != null">
                    #{age,jdbcType=INTEGER},
                </if>
            </trim>
        </insert>
        <update id="updateByPrimaryKeySelective" parameterType="com.luis.model.User">
            update user
            <set>
                <if test="name != null">
                    name = #{name,jdbcType=VARCHAR},
                </if>
                <if test="age != null">
                    age = #{age,jdbcType=INTEGER},
                </if>
            </set>
            where id = #{id,jdbcType=INTEGER}
        </update>
        <update id="updateByPrimaryKey" parameterType="com.luis.model.User">
            update user
            set name = #{name,jdbcType=VARCHAR},
            age = #{age,jdbcType=INTEGER}
            where id = #{id,jdbcType=INTEGER}
        </update>
    </mapper>
    
  7. 创建 service 接口及其实现类,在实现类的业务方法上添加 @Transactional注解,开启事务支持

    public interface UserService {
        int addUser(User user);
    }
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @Resource
        private UserMapper userMapper;
    
        /**
         * @Transactional 表示方法有事务支持,抛出运行时异常,则进行回滚
         * 使用MySQL默认的传播行为、隔离级别、超时时间
         */
        @Transactional
        @Override
        public int addUser(User user) {
            System.out.println("业务方法addUser开始执行");
            int rows = userMapper.insert(user);
            System.out.println("业务方法addUser结束执行");
    
            // 模拟发生运行时异常
            int num = 10 / 0; // 测试时通过注释以及放开观察事务控制
            return rows;
        }
    }
    
  8. 创建控制器类

    @Controller
    public class UserController {
        
        @Resource
        private UserService userService;
    
        @RequestMapping("/addStudent")
        @ResponseBody
        public String addStudent(String name, Integer age) {
    
            User user = new User();
            user.setName(name);
            user.setAge(age);
            int rows = userService.addUser(user);
            return "添加结果:" + rows;
        }
    }
    
  9. 主启动类上添加@EnableTransactionManagement注解,启用事务管理器,与业务方法上的@Transactional配套使用;继续在主启动类上添加@MapperScan注解,指定dao所在的包,扫描dao包下dao接口和mapper文件。

    @SpringBootApplication
    @EnableTransactionManagement // 启用事务管理器,与业务方法上的@Transactional一起使用
    @MapperScan(basePackages = "com.luis.dao") // 指定dao所在的包,扫描dao包下dao接口和mapper文件
    public class SpringbootTransactionalApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringbootTransactionalApplication.class, args);
        }
    }
    
  10. 运行主启动类,浏览器输入访问地址;配合业务方法中设置的运行时异常(分别注释和放开),观察访问结果以及数据库表中数据变化进行测试

    http://localhost:9090/mytrans/addUser?name=luis&age=5
    测试结果:发生运行时异常,进行了事务回滚,数据操作撤销,但主键会自增;无运行时异常,数据添加成功
    结论:该业务方法支持了事务
    

接口架构风格——RESTful

接口的概念

  • 接口: API(Application Programming Interface,应用程序接口)是一些预先定义的接口(如函数、HTTP接口),或指软件系统不同组成部分衔接的约定。 用来提供应用程序与开发人员基于某软件或硬件得以访问的一组例程,而又无需访问源码,或理解内部工作机制的细节。

此接口不是指 Java 中接口 Interface,而是一种应用程序接口,指可访问 servlet,controller 的 url,调用其他程序的函数!

前后端分离

前后端分离的思想:前端从后端剥离,形成一个前端工程,前端只利用Json来和后端进行交互,后端不返回页面,只返回Json数据。前后端之间完全通过public API约定。

前后端分离(解耦)的核心思想是:前端Html页面通过Ajax调用后端的RestFul API并使用Json数据进行交互。

注: 【在互联网架构中,web服务器:一般指像nginx,apache这类的服务器,他们一般只能解析静态资源。应用服务器:一般指像tomcat,jetty,resin这类的服务器可以解析动态资源也可以解析静态资源,但解析静态资源的能力没有web服务器好。】

一般只有Web服务器才能被外网访问,应用服务器只能内网访问。

————————————————
版权声明:本文为CSDN博主「山河远阔」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_37539378/article/details/79956760

前后端分离并非仅仅只是一种开发模式,而是一种架构模式(前后端分离架构)。
千万不要以为只有在撸代码的时候把前端和后端分开就是前后端分离了,需要区分前后端项目!
前端项目与后端项目是两个项目,放在两个不同的服务器,需要独立部署,两个不同的工程,两个不同的代码库,不同的开发人员。
前后端工程师需要约定交互接口,实现并行开发,开发结束后需要进行独立部署,前端通过ajax来调用http请求调用后端的restful api。
前端只需要关注页面的样式与动态数据的解析&渲染,而后端专注于具体业务逻辑。
————————————————下面原文很有阅读价值!
版权声明:本文为CSDN博主「山河远阔」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_37539378/article/details/79956760

REST 介绍

  • REST (英文:Representational State Transfer ,简称 REST;中文: 表现层状态转移 )

  • 一种互联网软件架构设计的风格,但它并不是标准,它只是提出了一组客户端和服务器交互时的架构理念和设计原则,基于这种理念和原则设计的接口可以更简洁,更有层次。

  • 任何的技术都可以实现这种理念,如果一个架构符合 REST 原则,就称它为 RESTFul 架构

比如我们要访问一个 http 接口:http://localhost:8080/boot/order?id=1021&status=1
采用 RESTFul 风格则 http 地址为:http://localhost:8080/boot/order/1021/1

表现层状态转移::

  • 表现层就是视图层,显示资源的,通过视图页面,jsp 等等显示操作资源的结果。

  • 状态: 资源变化

  • 转移:资源是可以变化的。资源能创建,new 状态,资源创建后可以查询资源,能看到资源的内容,这个资源内容,可以被修改,修改后,资源和之前的不一样。

REST中的要素:

  • 用 REST 表示资源和对资源的操作。在互联网中,表示一个资源或者一个操作。

  • 资源是使用 url 表示的,在互联网中,使用的图片,视频,文本,网页等等都是资源。

  • 资源用名词表示

操作方式:

一句话说明 REST: 使用 url 表示资源 ,使用 http 动作操作资源。

RESTful 优点

  • 轻量,直接基于 http,不再需要任何别的诸如消息协议
    get/post/put/delete 为 CRUD 操作
  • 面向资源,一目了然,具有自解释性。
  • 数据描述简单,一般以 xml,json 做数据交换。
  • 无状态,在调用一个接口(访问、操作资源)的时候,可以不用考虑上下文,不用考虑当前状态,
    极大的降低了复杂度。
  • 简单、低耦合

REST 操作资源

方式:使用 http 中不同动作(请求方式)表示对资源的不同操作(CURD)

示例:

  • GET:查询资源 -- sql select

​ 处理单个资源:用其单数方式

http://localhost:8080/myboot/student/1001

http://localhost:8080/myboot/student/1001/1

​ 处理多个资源:使用复数形式

http://localhost:8080/myboot/students/1001/1002

  • POST:创建资源 -- sql insert

http://localhost:8080/myboot/student

​ 在post请求中传递数据

<form action="http://localhost:8080/myboot/student" method="post">
    姓名:<input type="text" name="name" />
    年龄:<input type="text" name="age" />
</form>
  • PUT: 更新资源 -- sql update
<form action="http://localhost:8080/myboot/student/1" method="post">
    姓名:<input type="text" name="name" />
    年龄:<input type="text" name="age" />
    <input type="hidden" name="_method" value="PUT" />
</form>
  • DELETE: 删除资源 -- sql delete
<a href="http://localhost:8080/myboot/student/1">删除1的数据</a>

需要的分页,排序等参数,依然放在 url 的后面,例如

http://localhost:8080/myboot/students?page=1&pageSize=20

SpringBoot 开发 RESTful

SpringBoot 开发 RESTful 主要是通过以下几个注解实现:

  1. @PathVariable :从 url 中获取简单类型数据【最重要的一个注解】

  2. @GetMapping:支持的 get 请求方式,等同于@RequestMapping(method=RequestMethod.GET)

  3. @PostMapping :支持 post 请求方式 ,等同于@RequestMapping(method=RequestMethod.POST)

  4. @PutMapping:支持 put 请求方式,等同于@RequestMapping(method=RequestMethod.PUT)

  5. @DeleteMapping:支持 delete 请求方式,等同于@RequestMapping( method=RequestMethod.DELETE)

  6. @RestController:一个复合注解,是@Controller@ResponseBody的组合。

    在类的上面使用@RestController,表示当前类的所有方法都加入了@ResponseBody

使用示例:

  1. 创建 SpringBoot 项目,选择 Spring Web 依赖,整理 pom 文件,刷新 pom

  2. 创建控制器类,使用相关注解

    /**
     * @RestController:复合注解,包含@Controller和@ResponseBody
     *      控制器类上加上此注解,则类中所有方法都是返回对象数据
     */
    @RestController
    public class MyRestController {
    
        /**
         * @GetMapping:接收get请求
         * @PathVariable(路径变量):获取url中数据
         *      使用位置:控制器方法的形参前
         *      注解的属性value:路径变量名
         * 注意:当@PathVariable的路径变量名value与形参名相同时,属性value值可以省略
         * 查询资源:使用GET请求方式
         * http://localhost:9999/myboot/student/1002
         */
        @GetMapping("/student/{stuId}") // 接收get请求
        public String queryStudent(@PathVariable("stuId") Integer studentId) {
            return "查询的学生id:" + studentId;
        }
    }
    
  3. 主配置文件中配置端口和上下文路径

    server.port=9999
    server.servlet.context-path=/myboot
    
  4. 运行主启动类,浏览器输入访问地址进行测试

    http://localhost:9999/myboot/student/1002
    

基于以上,进行其他请求测试:

控制器类中添加 post 请求处理方法:

/**
     * 创建资源:使用POST请求方式
     * http://localhost:9999/myboot/student/luis/22
     * 注意:当@PathVariable的路径变量名value与形参名相同时,属性value值可以省略
     */
@PostMapping("/student/{name}/{age}") // 接收post请求
public String addStudent(@PathVariable String name,
                         @PathVariable Integer age) {
    return "创建student信息:" + name + " " + age;
}

在 resources/static 下创建 addStudent.html 页面发送 post 请求:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>post请求创建资源</title>
</head>
<body>
    <p>创建学生信息</p>
    <form action="student/luis/22" method="post">
        <input type="submit" value="提交post创建学生信息">
    </form>
</body>
</html>

运行主启动类,先访问页面,在页面上发送 post 请求:

http://localhost:9999/myboot/addStudent.html

可以发现,开发中模拟各种 post 请求,都需要自己创建表单页面,十分不便!

可以使用 Postman 工具,可模拟发送各种请求,使用王妈妈提供的汉化版本,免安装,免登陆!

控制器类中分别创建更新资源和删除资源的处理方法,启动项目,在 Postman 上进行访问测试:

/**
     * 修改资源:使用PUT请求方式,浏览器使用POST处理PUT请求
     * http://localhost:9999/myboot/student/luis/24
     * 注意:当@PathVariable的路径变量名value与形参名相同时,属性value值可以省略
     */
@PutMapping("/student/{name}/{age}") // 修改资源使用@PutMapping
public String modifyStudent(@PathVariable String name,
                            @PathVariable Integer age) {
    return "修改信息:" + name + " " + age;
}

/**
     * 删除资源:使用DELETE请求方式,浏览器使用POST处理DELETE请求
     * http://localhost:9999/myboot/student/3
     * 注意:当@PathVariable的路径变量名value与形参名相同时,属性value值可以省略
     */
@DeleteMapping("/student/{id}")
public String removeStudentById(@PathVariable Integer id) {
    return "删除信息:id = " + id;
}

在页面或ajax中支持put和delete请求

浏览器是不支持 PUT 和 DELETE 请求的,只支持 GET 和 POST;即在表单中只支持 get 和 post!

在 SpringMVC 中有一个过滤器,支持 post 请求转为 put 和 delete

过滤器:org.springframework.web.filter.HiddenHttpMethodFilter

作用:把请求中的 post 请求转为 put 或 delete

使用说明:在表单中想发送 put 或 delete 请求,需配置请求方式为 post,然后在表单的隐藏域标签中指定name=_methodvalue=put/delete;在 SpringBoot 的配置文件中启用 SpringMVC 的过滤器后,会自动将 post 请求转为指定的 put 或 delete。

实现步骤:

  1. application.properties(yml) 开启使用 HiddenHttpMethodFilter 过滤器

    #启用过滤器,支持put和delete请求
    spring.mvc.hiddenmethod.filter.enabled=true
    
  2. 在请求页面中,包含 _method 参数,其值是 put 或 delete,发起这个请求需使用 post 方式

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>测试表单提交方式</title>
    </head>
    <body>
        <form action="student/test" method="post">
            <!--    在隐藏域标签中指定将post转化为何种请求方式    -->
            <input type="hidden" name="_method" value="put"/>
            <input type="submit" value="表单提交put请求"/>
        </form>
    
        <form action="student/test" method="post">
            <!--    在隐藏域标签中指定将post转化为何种请求方式    -->
            <input type="hidden" name="_method" value="delete"/>
            <input type="submit" value="表单提交delete请求"/>
        </form>
    </body>
    </html>
    

附控制器测试方法:

@RestController
public class MyRestController {

    @PutMapping("/student/test")
    public String testPut() {
        return "put请求成功";
    }

    @DeleteMapping("/student/test")
    public String testDelete() {
        return "delete请求成功";
    }
}

使用REST注意

使用 REST 注意,必须保证请求地址 URL 加上请求方式唯一,不然会出现冲突。

如下面两个请求处理方法,在请求时就会出现冲突,无法确定用哪个方法处理请求!

@GetMapping("/student/{id}")
public String queryId(@PathVariable Integer id) {
    return "查询的学生id:" + id;
}

@GetMapping("/student/{age}")
public String queryAge(@PathVariable Integer age) {
    return "查询的学生age:" + age;
}

SpringBoot 集成 Redis

  • Redis:一个NoSQL数据库,常用作缓存使用(cache)

  • Redis 的五种数据类型:string、hash、set、zset、list

  • Redis 是一个中间件,是一个独立的服务器。

  • Java 中著名的客户端:Jedis,lettuce,Redisson

SpringBoot 中有 RedisTemplate 和 StringRedisTemplate,都可以处理和 redis 的数据交互。

PS:其实使用的 RedisTemplate,其底层还是调用的 lettuce 客户端方法来处理数据。

配置 Windows 版本的 redis

临时性使用 redis ,可直接在 Windows 上使用解压后的安装包,启动服务端和客户端即可!(网盘有压缩包,解压即可使用)

Redis-x64-3.2.100.rar 解压缩到一个非中文的目录

redis-server.exe:服务端,启动后,不要关闭

redis-cli.exe:客户端,访问 redis 中的数据

redisclient-win32.x86_64.2.0.jar:Redis 的一个图形界面客户端

执行方式:在这个文件所在的目录,执行 java -jar redisclient-win32.x86_64.2.0.jar 即可

SpringBoot 集成 Redis

步骤:

  1. 创建 SpringBoot 项目,选择 Spring Web 和 Redis 依赖,整理 pom 并刷新

  2. application.properties/yml 中配置连接 redis 的相关参数

    server.port=7777
    server.servlet.context-path=/myredis
    
    #配置redis:host ip password
    spring.redis.host=localhost
    spring.redis.port=6379
    #spring.redis.password=123
    
  3. 创建控制器类,创建请求处理方法,获取操作 redis 数据的对象,处理数据交互

    @RestController
    public class RedisController {
    
        /**
         * 注入RedisTemplate(处理和redis的数据交互)
         * 其默认使用的jdk序列化,可读性差(查看数据库中数据有类似乱码问题)
         * 三种泛型情况:
         *      RedisTemplate<String,String>
         *      RedisTemplate<Object,Object>
         *      RedisTemplate
         */
        @Resource
        private RedisTemplate redisTemplate;
    
        /**
         * 注入StringRedisTemplate(处理和redis的数据交互)
         * 其默认使用的String序列化,可读性好(数据库中数据正常显示)
         */
        @Resource
        private StringRedisTemplate stringRedisTemplate;
    
        @PostMapping("/redis/addstring")
        public String addToRedis(String key, String value) { // 使用传统风格
            // 添加数据到redis(使用RedisTemplate对象)
            ValueOperations valueOperations = redisTemplate.opsForValue(); // 获取redis中操作string类型数据的对象
            valueOperations.set(key, value);
            return "成功添加数据到redis";
        }
    
        @GetMapping("/redis/getstring")
        public String getKFromRedis(String key) { // 使用传统风格
            // 从redis获取数据(使用RedisTemplate对象)
            ValueOperations valueOperations = redisTemplate.opsForValue(); // 获取redis中操作string类型数据的对象
            Object o = valueOperations.get(key);
            return key + " = " + key + ", value = " + o;
        }
    
        @PostMapping("/redis/{k}/{v}")
        public String addStringKV(@PathVariable String k,
                                  @PathVariable String v) { // 使用RESTful风格
            // 添加数据到redis(使用StringRedisTemplate对象)
            ValueOperations<String, String> stringStringValueOperations = stringRedisTemplate.opsForValue();
            stringStringValueOperations.set(k, v);
            return "成功添加数据到redis";
        }
    
        @GetMapping("/redis/{k}")
        public String getK(@PathVariable String k) { // 使用RESTful风格
            // 从redis获取数据(使用StringRedisTemplate对象)
            ValueOperations<String, String> stringStringValueOperations = stringRedisTemplate.opsForValue();
            String v = stringStringValueOperations.get(k);
            return k + " = " + k + ", v = " + v;
        }
    }
    
  4. 运行主启动类,使用 Postman 进行请求测试

对比 RedisTemplate 和 StringRedisTemplate

  • StringRedisTemplate:使用的是 String 的序列化方式,把 k,v 都是作为 String 处理,可读性好。

    但是有局限性,只能存 String 类型,且只能是 String 的序列化方式,无法修改为其他的序列化方式。

  • RedisTemplate:默认使用的是 JDK 的序列化方式,把 k,v 经过了 JDK 序列化存到 redis;

    经过了 JDK 序列化的 k,v 在数据库中不能直接识别。

    其默认使用的 jdk 序列化,但可以修改为其他的序列化方式。

    其使用比 StringRedisTemplate 更加广泛,可修改为其他的序列化方式,不仅仅只能存字符串。

序列化概述

序列化和反序列化:

  • 序列化:把对象转化为可传输的字节序列的过程称为序列化。

  • 反序列化:把字节序列还原为对象的过程称为反序列化。

为什么需要序列化?

  • 序列化最终目的是为了对象可以跨平台存储以及进行网络传输。我们进行跨平台存储和网络传输的方式就是 IO,而我们的 IO支持的数据格式就是字节数组。我们必须在把对象转成字节数组的时候就制定一种规则(序列化),以便我们从 IO 流里面读出数据的时候以这种规则把对象还原回来(反序列化)。

什么情况下需要序列化?

  • 凡是需要进行“跨平台存储”和”网络传输”的数据,都需要进行序列化。

本质上存储和网络传输都需要经过把一个对象状态保存成一种跨平台识别的字节格式,然后其他的平台才可以通过字节信息解析还原对象信息。

序列化的方式:

序列化只是一种拆装和组装对象的规则,这种规则有多种多样,比如现在常见的序列化方式有:

JDK(不支持跨语言)、JSON、XML、Hessian、Kryo(不支持跨语言)、Thrift、Protofbuff 等

例如:

java 的序列化:把 java 对象转为 byte[] 二进制数据

json 的序列化:就是将对象转换为 JSON 字符串格式

json 的反序列化:就是将JSON 字符串格式转换为对象

Student( name=zs, age=20) ---- { "name":"zs", "age":20 }

设置 RedisTemplate 的序列化方式

@PostMapping("/redis/addData")
public String addData(String k, String v) {

    // 设置key,value均使用String序列化方式(在进行存取值之前设置)
    // 注意,key 和 value 的序列化方式可分别设置
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new StringRedisSerializer());

    // 获取redis中操作string类型数据的对象
    ValueOperations valueOperations = redisTemplate.opsForValue();
    // 向redis中添加数据
    valueOperations.set(k, v);
    return "设置RedisTemplate序列化方式,并向redis中添加数据";
}

设置为 json 序列化方式

设置 json 序列化,存储时把对象转为 json 格式字符串进行存储;获取时将 json 字符串格式转为对象。

如果是 String 序列化方式,则只能存储字符串类型,无法存储对象。

示例:

  1. 准备 vo 数据对象 Student(需要进行序列化

    public class Student implements Serializable {
        private static final long serialVersionUID = 684647176563341892L; // 添加后就不要再改动!
        private Integer id;
        private String name;
        private Integer age;
        // getter,setter,toString
    }    
    
  2. 写处理请求的处理器方法

    @PostMapping("/redis/addjson")
    public String addJson() {
    
        // 准备对象数据
        Student student = new Student();
        student.setId(111);
        student.setName("luis");
        student.setAge(22);
    
        // 设置key的序列化方式为String(默认的是jdk方式)
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 设置value的序列化方式为json(默认的是jdk方式)
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Student.class));
        // 获取redis中操作string类型数据的对象
        ValueOperations valueOperations = redisTemplate.opsForValue();
        // 向redis中存数据
        // 会依据value设置的json序列化方式自动将Student对象转换为json格式字符串
        valueOperations.set("mystudent", student);
        return "key-string序列化,value-json序列化";
    }
    
    @GetMapping("/redis/getjson")
    public String getJson() {
    
        // 设置key的序列化方式为String(默认的是jdk方式)
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 设置value的序列化方式为json(默认的是jdk方式)
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Student.class));
        // 获取redis中操作string类型数据的对象
        ValueOperations valueOperations = redisTemplate.opsForValue();
        // 从redis中获取数据
        // 会依据value设置的json序列化方式自动将json格式字符串转换为Student对象,反序列化回来
        Object o = valueOperations.get("mystudent");
        return "json反序列化:" + o;
    }
    
  3. 使用 Postman 发请求测试

小结

  • 临时性使用 redis 或小测试,可直接在 Windows 上使用 redis 的安装包,解压即可使用
  • SpringBoot 中使用 Redis 主要是合理选择 RedisTemplate 或 StringRedisTemplate 来处理和 Redis 的交互
  • 处理单纯的字符串数据,可直接使用 RedisTemplate 对象处理数据交互,其采用 String 序列化方式。
  • 处理对象类型的数据,需要选择 StringRedisTemplate 来处理和 Redis 的交互,其默认采用 JDK 序列化方式;需手动设置改变其序列化方式为 json;还可设置成其他的序列化方式。
  • 数据在网络上传输,或者跨平台传输都需要进行序列化!所以将需要传输的数据,如对象类型的,进行实现序列化接口的操作,并设定序列化版本号!序列化版本号设置后不要再改动!
  • 创建资源使用 POST 请求;获取资源使用 GET 请求;修改资源使用 PUT 请求;删除资源使用 DELETE 请求。
  • 熟悉 Postman 测试工具:熟练选择请求方式、根据传统风格以及 RESTful 风格的快速准确填充数据

SpringBoot 集成 Dubbo

开发中使用 SpringBoot 集成 Dubbo 进行分布式开发!

公共工程负责提供实体 bean 以及业务接口;

服务提供者工程负责实现公共工程中的业务接口,提供并暴露服务,供消费者调用;

服务消费者工程负责调用服务提供者提供的服务。

需要三个工程:接口工程【java】、服务提供者工程【可web】、服务消费者工程【web】
接口工程【java】:负责实体 bean 以及业务接口(提供者和消费者均依赖于它)
服务提供者工程【可web】:负责实现接口工程中的服务接口,提供服务,暴露服务,供消费者调用
服务消费者工程【web】:负责调用提供者提供的服务   

示例:

1.创建接口/公共工程

  1. 创建一个 maven 项目,无需选择模板,名为 interface-api(公共的接口工程)

  2. 创建 vo 数据类 Student,进行序列化

    public class Student implements Serializable {
        private static final long serialVersionUID = -3498691273686241331L;
        private Integer id;
        private String name;
        private Integer age;
        // getter,setter,toString
    }    
    
  3. 定义服务接口(业务接口)

    public interface StudentService {   
        Student queryStudent(Integer id);
    }
    

2.创建服务提供者工程

  1. 创建 SpringBoot 项目,无需添加其他依赖,项目命名 springboot-service-provider(服务提供者工程)

  2. 在其 pom 文件中加入公共工程依赖、dubbo 起步依赖和 Zookeeper 依赖,整理 pom,刷新

    <!--公共项目(接口工程)的gav-->
    <dependency>
        <groupId>com.luis</groupId>
        <artifactId>interface-api</artifactId>
        <version>1.0.0</version>
    </dependency>
    <!--dubbo起步依赖-->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.8</version>
    </dependency>
    <!--zookeeper依赖-->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-dependencies-zookeeper</artifactId>
        <version>2.7.8</version>
        <type>pom</type>
        <exclusions>
            <!-- 排除log4j依赖,解决多次引入 -->
            <exclusion>
                <artifactId>slf4j-log4j12</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    

    注意:服务提供者中如果要使用到redis,并涉及json序列化操作,在不添加web起步依赖下,必须加下列jackson依赖!

    <!--单独添加下面四个Jackson的依赖(或者直接将web起步依赖加上)-->
    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.2.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jdk8 -->
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jdk8</artifactId>
        <version>2.13.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310 -->
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
        <version>2.13.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-parameter-names -->
    <dependency>
        <groupId>com.fasterxml.jackson.module</groupId>
        <artifactId>jackson-module-parameter-names</artifactId>
        <version>2.13.2</version>
    </dependency>
    
  3. 实现接口工程中的业务方法,使用 dubbo 中的注解暴露服务

    // @Component 可不用加此注解创建对象!加@DubboService后,该注解可自行创建该类对象。
    //@DubboService(interfaceClass = StudentService.class, version = "1.0", timeout = 5000)//暴露服务
    @DubboService(version = "1.0", timeout = 5000) // 暴露服务(interfaceClass属性可不加)
    public class StudentServiceImpl implements StudentService {
        @Override
        public Student queryStudent(Integer id) {
    
            Student student = new Student();
            if (1001 == id) {
                student.setId(1001);
                student.setName("1001-luis");
                student.setAge(22);
            } else {
                student.setId(1002);
                student.setName("1002-jack");
                student.setAge(24);
            }
            return student;
        }
    }
    
  4. 配置 application.properties/yml 文件

    #配置服务名称
    spring.application.name=studentservice-provider
    
    #配置扫描的包,扫描@DubboService
    dubbo.scan.base-packages=com.luis.service
    
    #配置dubbo协议(使用zookeeper注册中心就不用配置)
    #dubbo.protocol.name=dubbo
    #dubbo.protocol.port=20881
    
    #配置zookeeper注册中心
    dubbo.registry.address=zookeeper://localhost:2181
    
  5. 在主启动类上添加注解启用dubbo

    @SpringBootApplication
    @EnableDubbo // 启用dubbo,该注解包含@EnableDubboConfig和@DubboComponentScan
    public class SpringbootServiceProviderApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringbootServiceProviderApplication.class, args);
        }
    }
    

3.创建服务消费者工程

  1. 创建 SpringBoot 项目,添加 Spring Web 依赖,项目命令为 springboot-consumer(服务消费者工程)

  2. 在其 pom 文件中加入公共工程依赖、dubbo 起步依赖和 Zookeeper 依赖,整理 pom,刷新。

    其需要的依赖和服务消费者工程所需相同!

    <!--公共项目(接口工程)的gav-->
    <dependency>
        <groupId>com.luis</groupId>
        <artifactId>interface-api</artifactId>
        <version>1.0.0</version>
    </dependency>
    <!--dubbo起步依赖-->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.8</version>
    </dependency>
    <!--zookeeper依赖-->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-dependencies-zookeeper</artifactId>
        <version>2.7.8</version>
        <type>pom</type>
        <exclusions>
            <!-- 排除log4j依赖 -->
            <exclusion>
                <artifactId>slf4j-log4j12</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    
  3. 创建控制器类,写请求处理方法,进行远程接口调用

    @RestController
    public class DubboController {
    
        // 引用远程服务,将创建好的代理对象,注入给studentService
        //@DubboReference(interfaceClass = StudentService.class, version = "1.0")
        @DubboReference(version = "1.0") // 如果不指定interfaceClass,则默认采用引用类型的class
        private StudentService studentService;
    
        @GetMapping("/query")
        public String queryStudent() {
            // 调用远程接口,获取对象
            Student student = studentService.queryStudent(1001);
            return "调用远程接口获取的对象:" + student;
        }
    }
    
  4. 在主启动类上添加启用dubbo的注解

    @SpringBootApplication
    @EnableDubbo // 启用Dubbo
    public class SpringbootConsumerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringbootConsumerApplication.class, args);
        }
    }
    
  5. 配置 application.properties/yml

    #指定服务名称
    spring.application.name=consumer-application
    #指定注册中心
    dubbo.registry.address=zookeeper://localhost:2181
    

4.启动 zookeeper 和服务

  1. 启动 zookeeper 注册中心(双击 zookeeper/bin 目录下的 zkServer.cmd)

  2. 运行服务提供者模块的主启动类

  3. 运行服务消费者模块的主启动类

  4. 根据服务消费者提供的访问接口进行服务访问测试

    http://localhost:8080/query
    

SpringBoot 项目打包

打包为 war 文件

示例:

  1. 创建 SpringBoot 项目,选择 Spring Web 依赖,整理项目目录

  2. 此处直接列举整个 pom,依据注释的内容进行添加,然后刷新 pom

    <?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.6.6</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <groupId>com.luis</groupId>
        <artifactId>springboot-war</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <!--指定打包类型-->
        <packaging>war</packaging>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <!--处理jsp的依赖-->
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-jasper</artifactId>
            </dependency>
            <!--servlet,jsp,jstl需要用再加-->
    
            <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>
            </dependency>
        </dependencies>
    
        <build>
            <!--打包后的文件名称-->
            <finalName>myboot</finalName>
            
            <!--resources插件-->
            <resources>
                <!--将jsp编译到指定的目录-->
                <resource>
                    <directory>src/main/webapp</directory>
                    <targetPath>META-INF/resources</targetPath>
                    <includes>
                        <include>**/*.*</include>
                    </includes>
                </resource>
                <!--将src/main/java下的文件包含到classes下-->
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.*</include>
                    </includes>
                </resource>
                <!--将src/main/resources下的文件包含到classes下-->
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.*</include>
                    </includes>
                </resource>
            </resources>
    
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
  3. 在 main 目录下,与 java 和 resources 目录平级,创建名为 webapp 的目录;在项目工程结构中指定该目录为 web 资源目录

  4. 在 webapp 目录下创建 index.jsp 页面,用来显示 controller 中的数据

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>index.jsp</title>
    </head>
    <body>
        index.jsp,显示controller中的数据:${data}
    </body>
    </html>
    
  5. 创建 controller 控制器类,处理请求

    @Controller
    public class JspController {
    
        @RequestMapping("/main")
        public String main(Model model) {
            model.addAttribute("data", "SpringBoot打包为war文件");
            return "index";
        }
    }
    
  6. 配置 application.properties/yml

    server.port=9001
    server.servlet.context-path=/myjsp
    
    #指定视图解析器
    spring.mvc.view.prefix=/
    spring.mvc.view.suffix=.jsp
    
  7. 让主启动类继承 SpringBootServletInitializer并重写 configure 方法,修改返回内容。

    /**
     * 让主启动类继承 SpringBootServletInitializer 并重写重写 configure 方法才可以使用外部 Tomcat!
     * SpringBootServletInitializer 就是原有 web.xml 的替代。
     */
    @SpringBootApplication
    public class SpringbootWarApplication extends SpringBootServletInitializer {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringbootWarApplication.class, args);
        }
    
        // 重写此configure方法,修改返回内容!
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
            return builder.sources(SpringbootWarApplication.class);
        }
    }
    
  8. 运行主启动类,浏览器输入访问地址,测试

    http://localhost:9001/myjsp/main
    
  9. 测试正常后,使用 maven 的 lifecycle 中 package 打包命令进行打包(右侧 maven 窗口中);【建议先clean再package】

    在 target 目录下即可看到打包完成的 war 包。

  10. 将打包好的 war 包放到 Tomcat 的 webapps 目录下,启动 Tomcat;

    若是本地 Tomcat,则通过http://localhost:8080/myboot/main可访问该项目。

重点小结

  • 指定打包类型为 war
  • 指定打包后名称
  • 添加资源插件
  • 主启动类继承 SpringBootServletInitializer并重写 configure 方法,修改返回内容。
  • 使用 package 命令打包

打包为 jar 文件

示例:

  1. 创建 SpringBoot 项目,选择 Spring Web 依赖,整理项目目录

  2. 此处直接列举整个 pom,依据注释的内容进行添加,然后刷新 pom

    <?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.6.6</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <groupId>com.luis</groupId>
        <artifactId>springboot-jar</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
    
            <!--处理jsp的依赖-->
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-jasper</artifactId>
            </dependency>
            <!--servlet,jsp,jstl需要用再加-->
    
            <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>
            </dependency>
        </dependencies>
    
        <build>
            <!--打包后的文件名称-->
            <finalName>myboot</finalName>
    
            <!--resources插件-->
            <resources>
                <!--将jsp编译到指定的目录-->
                <resource>
                    <directory>src/main/webapp</directory>
                    <targetPath>META-INF/resources</targetPath>
                    <includes>
                        <include>**/*.*</include>
                    </includes>
                </resource>
                <!--将src/main/java下的mapper文件包含到classes下-->
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.xml</include>
                    </includes>
                </resource>
                <!--将src/main/resources下的文件包含到classes下-->
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.*</include>
                    </includes>
                </resource>
            </resources>
    
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <!--  打包jar,有jsp文件时,必须指定maven-plugin插件版本为 1.4.2.RELEASE  -->
                    <version>1.4.2.RELEASE</version>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
  3. 在 main 目录下,与 java 和 resources 目录平级,创建名为 webapp 的目录;在项目工程结构中指定该目录为 web 资源目录

  4. 在 webapp 目录下创建 main.jsp 页面,用来显示 controller 中的数据

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
      main.jsp,显示数据:${data}
    </body>
    </html>
    
  5. 创建控制器类,处理请求

    @Controller
    public class HelloController {
    
        @RequestMapping("/hello")
        public ModelAndView hello() {
            ModelAndView mv = new ModelAndView();
            mv.addObject("data", "SpringBoot打包为jar文件");
            mv.setViewName("main");
            return mv;
        }
    }
    
  6. 配置 application.properties/yml

    server.port=9002
    server.servlet.context-path=/myboot
    
    #视图解析器
    spring.mvc.view.prefix=/
    spring.mvc.view.suffix=.jsp
    
  7. 运行主启动类,浏览器输入地址访问测试

    http://localhost:9002/myboot/hello
    
  8. 测试正常后,使用 maven 的 lifecycle 中 package 打包命令进行打包(右侧 maven 窗口中);【建议先clean再package】

    在 target 目录下即可看到打包完成的 jar 包。

  9. 在 jar 包所在目录,开启 cmd 窗口,输入 java -jar myboot.jar,回车,即可独立运行项目;

    (可以将java -jar myboot.jar这段命令写进一个文件,改后缀为 .bat,放在 jar 包同级下,双击即可运行项目)

    后台信息打印可看到端口和项目访问上下文路径;

    此时使用的是内嵌的 tomcat,输入http://localhost:9002/myboot/hello即可访问。

重点小结

  • 指定打包名称
  • 添加资源插件
  • 指定 maven-plugin 插件版本
  • 使用 package 命令打包

Thymeleaf 模板引擎

Thymeleaf 介绍

  • Thymeleaf 是一个流行的模板引擎,该模板引擎采用 Java 语言开发
  • 模板引擎是一个技术名词,是跨领域跨平台的概念,在 Java 语言体系下有模板引擎,在
    C#、PHP 语言体系下也有模板引擎,甚至在 JavaScript 中也会用到模板引擎技术,Java 生态下
    的模板引擎有 Thymeleaf 、Freemaker、Velocity、Beetl(国产) 等
  • Thymeleaf 对网络环境不存在严格的要求,既能用于 Web 环境下,也能用于非 Web 环境
    下。在非 Web 环境下,他能直接显示模板上的静态数据;在 Web 环境下,它能像 Jsp 一样从
    后台接收数据并替换掉模板上的静态数据。它是基于 HTML 的,以 HTML 标签为载体,
    Thymeleaf 要寄托在 HTML 标签下实现
  • Spring Boot 集成了 Thymeleaf 模板技术,并且 Spring Boot 官方也推荐使用 Thymeleaf 来
    替代 JSP 技术
    ,Thymeleaf 是另外的一种模板技术,它本身并不属于 Spring Boot,Spring Boot
    只是很好地集成这种模板技术,作为前端页面的数据展示,在过去的 Java Web 开发中,我们
    往往会选择使用 Jsp 去完成页面的动态渲染,但是 jsp 需要翻译编译运行,效率低。

Thymeleaf 的官方网站:http://www.thymeleaf.org
Thymeleaf 官方手册:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

完善 Thymeleaf 语法提示

  • 添加了 Thymeleaf 依赖后,在创建的 html 标签中使用其相关语法,若想使用 IDEA 的语法提示,还需进行以下设置:

    在默认创建的 html 的 <html lang="en">中补充xmlns:th="http://www.thymeleaf.org",如下:

    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    

使用示例

  1. 创建 SpringBoot 项目,选择 Spring Web 和 Thymeleaf 依赖,整理 pom,并刷新

  2. 写控制器类,处理请求,转发页面到指定的 Thymeleaf 页面,显示数据

    @Controller
    public class MyController {
    
        @GetMapping("/hello")
        public String helloThymeleaf(HttpServletRequest request, Model model) {
    
            // 将数据放到request作用域
            request.setAttribute("data1", "欢迎使用Thymeleaf模板引擎");
            // 将数据放到request作用域
            model.addAttribute("data2", "我是后台的数据");
    
            // 指定转发的逻辑视图(使用Thymeleaf模板引擎,其通常使用的html)
            // 模板引擎默认配置了resources/tmeplates下的以.html结尾文件的视图解析
            return "hello";
        }
    }
    
  3. 在 resources/templates 下,创建 hello.html 页面显示数据(可在 html 标签中使用 Thymeleaf)

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <!-- xmlns:th="http://www.thymeleaf.org" 是手动加的,加上后idea使用Thymeleaf会有相关提示 -->
    <head>
        <meta charset="UTF-8">
        <title>hello.html</title>
    </head>
    <body>
        <h3>Thymeleaf使用示例</h3>
        <!--  后台有数据填充,则替换p标签的value内容;没有,则不替换  -->
        <p th:text="${data1}">想显示数据</p>
        <p th:text="${data2}">静态展示数据</p>
    </body>
    </html>
    
  4. 配置 application.properties/yml

    #关闭模板缓存,开发阶段配置,让修改立即生效(项目上线后需修改为false,效率更高)
    spring.thymeleaf.cache=false
    # 去掉html5的语法验证(thymeleaf对html的标签约束非常严格,所有的标签必须有开有闭,比如<br></br>或者<br/>是可以的,但是<br>会报错,
    # 配置spring.thymeleaf.mode=LEGACYHTML5 目的就是为了解决这个问题,可以使页面松校验。)
    spring.thymeleaf.mode=LEGACYHTML5
    
    
    #以下是一些可能用到的配置,需要改变时配置即可,一般不需要配置,使用默认即可
    #-------------------------------------------------
    #编码格式(默认是UTF-8)
    spring.thymeleaf.encoding=UTF-8
    #模板的类型(默认是HTML)
    spring.thymeleaf.mode=HTML
    #模板的前缀(默认是classpath:/templates/,其下html文件不用配置视图解析器,直接可用逻辑名称)
    spring.thymeleaf.prefix=classpath:/templates/
    #模板的后缀(默认是.html)
    spring.thymeleaf.suffix=.html
    #-------------------------------------------------
    
  5. 运行主启动类,浏览器输入地址访问测试;

    然后不通过地址,直接在 idea 中点击右上角浏览器标识,打开 hello.html,查看效果

    http://localhost:8080/hello
    

    结果:后台没有数据传过来时,显示的是页面原有数据;后台有数据传过来,则覆盖原有数据进行显示!

    使用 Thymeleaf 可实现前后端同时有效开发,互不影响;前端可用自己的示例数据,后端只需进行数据替换!

表达式

1.标准变量表达式

  • 语法:${key}

  • 作用:从 request 作用域中获取 key 对应的文本数据;

    可使用request.setAttribute()model.addAttribute()向 request 中存数据

  • 使用:在页面的 html 标签中,使用th:text="${key}"表达式

  • 示例:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>expression1</title>
    </head>
    <body>
        <p3>标准变量表达式:${key}</p3>
        <div>
            <p>获取相关数据:</p>
            <p th:text="${msg}">msg</p>
            <p th:text="${myuser.id}">id</p>
            <p th:text="${myuser.name}">name</p>
            <p th:text="${myuser.sex}">sex</p>
            <p th:text="${myuser.age}">age</p>
        </div>
    </body>
    </html>
    

2.选择/星号变量表达式

  • 语法: *{key}

  • 作用:获取这个 key 对应的数据;*{key}需要和th:object这个属性一起使用,简化对象属性值的获取。

  • 示例:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>expression2</title>
    </head>
    <body>
        <p3>选择变量表达式:*{key}</p3>
        <p>获取对象相关数据:</p>
        <div th:object="${myuser}">
            <p th:text="*{id}">id</p>
            <p th:text="*{name}">name</p>
            <p th:text="*{sex}">sex</p>
            <p th:text="*{age}">age</p>
        </div>
        <p>使用*{}完整表示对象的属性值,此用法其实和${key}作用相同,了解即可</p>
        <p th:text="*{myuser.name}">name</p>
    </body>
    </html>
    

3.链接表达式

  • 语法:@{url}

  • 作用:表示链接,可使用在如下等情境中

    <script src="...">,<link href="...">,<a href="..">,<form action="...">,<img src="...">
    
  • 示例:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>链接表达式</title>
    </head>
    <body>
        <p3>链接绝对路径</p3><br/>
        <a th:href="@{http://www.baidu.com}">链接到百度</a><br/>
    
        <p3>链接的是相对路径</p3><br/>
        <a th:href="@{/tpl/queryAccount}">相对地址,没有参数</a><br/>
        <p3>链接的相对地址,使用字符串链接传递参数</p3><br/>
        <a th:href="@{'/tpl/queryAccount?id=' + ${userId}}">获取model中的数据添加到链接地址中(不推荐此种拼接方式)</a><br/>
    
        <p3>推荐使用的传参方式:</p3><br/>
        <a th:href="@{/tpl/queryAccount(id=${userId})}">传递一个参数(参数值可自己设置,也可从作用域中获取)</a><br/>
        <a th:href="@{/tpl/queryUser(name='李四',age=20)}">传递多个参数(参数值可自己设置)</a><br/>
        <a th:href="@{/tpl/queryUser(name=${name},age=${age})}">传递多个参数(参数值可自己设置,也可从作用域中获取)</a><br/>
    </body>
    </html>
    

Thymeleaf 属性

  • 大部分属性和 html 的一样,只不过前面加了一个 th 前缀;加了 th 前缀,表示经过了模板引擎处理;经过了模板引擎处理的属性,就可以使用其相关表达式,可获取动态变量。

  • th:actionth:methodth:hrefth:srcth:textth:styleth:nameth:value

  • 基本属性示例:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>模板属性</title>
        <!--  引入jQuery  -->
        <script th:src="@{/js/jquery-3.4.1.js}" type="text/javascript"></script>
    
        <script type="text/javascript">
            $(function() {
                $("#btn").click(function(){
                    alert("按钮单击绑定======");
                })
            })
            function btnClick() {
                alert("按钮单击回调");
            }
        </script>
    
    </head>
    <body>
        <p3>使用模板属性可获取动态变量</p3>
        
        <form th:action="@{/login}" th:method="${methodAttr}">
            <input th:type="text" th:name="${paramname}" th:value="${paramvalue}" />
            <input type="button" value="按钮" id="btn" th:onclick="btnClick()" />
        </form>
    
        <p th:style="${textcolor}">获取动态变量,改变字体颜色</p>
        <p th:style="'color:skyblue'">此处经过模板处理,若仍手动设置,需要使用字符串</p>
        <p style="color: red">原版的改变字体颜色</p>
    
    </body>
    </html>
    

th:each

  • each 循环,可以循环 List 集合,Array 数组,Map 集合
  • 其实整体循环方式差不多,只不过数据存储方式不一样,存取方式有一些差异。

th:each循环List/Array

each 循环集合 List 和循环数组 Array 语法相同!

语法:在一个 html 标签中,如下列方式使用 th:each(以下以 div 为例,实际多循环表格标签)

<div th:each="集合循环成员,循环状态变量:${key}">
    <p th:text="${集合循环成员}"></p>
</div>

集合循环成员,循环状态变量:两个名称都是自定义的。 
“循环状态变量”这个名称可不定义,默认名称是"集合循环成员Stat"

如果不定义“循环状态变量”,则直接可这样写:(但要使用其时,直接使用"集合循环成员Stat"这个默认的名称即可)
<div th:each="集合循环成员:${key}">
    <p th:text="${集合循环成员}"></p>
</div>

具体语法说明:

th:each="user,iterStat:${userlist}"中的${userList}是后台传过来的集合
◼  user
定义变量,去接收遍历${userList}集合中的一个数据
◼  iterStat
${userList} 循环体的信息
◼  其中 user 及 iterStat 自己可以随便取名
◼  interStat 是循环体的信息,通过该变量可以获取如下信息
index: 当前迭代对象的 index(从0开始计算)
count: 当前迭代对象的个数(从1开始计算)这两个用的较多
size: 被迭代对象的大小
current: 当前迭代变量
even/odd: 布尔值,当前循环是否是偶数/奇数(从0开始计算)
first: 布尔值,当前循环是否是第一个
last: 布尔值,当前循环是否是最后一个
注意:循环体信息 interStat 也可以不定义,则默认采用迭代变量加上 Stat 后缀,即 userStat

示例:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>eachList.html</title>
</head>
<body>
    <p3>each循环List</p3>
    <table border="1">
        <thead>
            <tr>
                <th>id</th>
                <th>name</th>
                <th>sex</th>
                <th>age</th>
                <th>userStat</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="user,userStat:${myusers}">
                <td th:text="${user.id}"></td>
                <td th:text="${user.name}"></td>
                <td th:text="${user.sex}"></td>
                <td th:text="${user.age}"></td>
                <td th:text="${userStat}"></td>
            </tr>
        </tbody>
    </table>

    <br/>

    <table border="1">
        <thead>
        <tr>
            <th>id</th>
            <th>name</th>
            <th>sex</th>
            <th>age</th>
        </tr>
        </thead>
        <tbody>
        <!-- 可不写循环体信息userStat,默认会自动加上去 -->
        <tr th:each="user:${myusers}">
            <td th:text="${user.id}"></td>
            <td th:text="${user.name}"></td>
            <td th:text="${user.sex}"></td>
            <td th:text="${user.age}"></td>
        </tr>
        </tbody>
    </table>

</body>
</html>

th:each循环Map

语法:在一个 html 标签中,如下列方式使用 th:each(以下以 div 为例,实际多循环表格标签)

<div th:each="集合循环成员,循环的状态变量:${key}">
    <p th:text="${集合循环成员.key}" ></p>
    <p th:text="${集合循环成员.value}"></p>
</div>

集合循环成员,循环的状态变量:两个名称都是自定义的。 
“循环的状态变量”这个名称可以不定义,默认是"集合循环成员Stat"

key:map集合中的key
value:map集合key对应的value值

循环 List-Map 示例

示例循环一个稍复杂点的数据类型如:List<Map<String,Student>>

第一次循环,取出每一个 Map;

第二次循环,取出每一个 Map 中的 key 和 value。

<div th:each="lm:${listMap}">
    <div th:each="map:${lm}">
        <p th:text="${map.key}"></p>
        <p th:text="${map.value}"></p>        
    </div>
</div>

th:ifth:unless

th:if:判断语句,当条件为 true,显示 html 标签体内容,反之不显示;没有 else 语句

示例:

<div th:if="10 > 0">条件为true才显示此内容</div>

<p th:if="${sex=='m'}">性别是男才显示</p>
<p th:if="${isLogin}">isLogin为true才显示</p>
<p th:if="${age>18}">年龄大于18才显示</p>
<!--""空字符是true-->
<p th:if="${str}">当str为空串表示true,会显示</p>
<!--null是false-->
<p th:if="${str}">当str为null表示false,不会显示</p>

th:unless:判断语句,当条件为 false,显示 html 标签体内容,反之不显示;没有 else 语句

示例:

<div th:unless="10 < 0">条件为false才显示此内容</div>
动态变量控制的示例此处省略,和if的使用相同,就是显示条件相反!

th:swithth:case

th:switch 和 Java 中的 switch 是一样的;

进行条件匹配,按上下顺序匹配,一旦匹配成功则结束;否则使用默认的;无论如何,最终只有一个匹配上!

  • 语法:
<div th:switch="要比对的值">
    <p th:case="值1">
        结果1
    </p>
    <p th:case="值2">
        结果2
    </p>
    <p th:case="*">
        默认结果
    </p>
    以上的case只有一个语句执行
    
</div>

示例:

<div th:switch="${sex}">
    <p th:case="m">男</p>
    <p th:case="f">女</p>
    <p th:case="*">性别未知</p>
</div>

th:inline

  • 作用:不依赖于 html 标签,直接通过内联表达式[[${key}]]来获取动态数据

  • th:inline有三个取值类型 (text, javascript 和 none),通常使用 text 和 javascript 这两个

  • 语法:th:inline="text/javascript"

  • 内联表达式:[[${key}]]

内联 text

  • 作用:在 html 标签外,获取动态数据。

  • 语法:th:line="text",不指定则默认使用内联 text!

  • 特点:可显示的指定使用内联 text,不指定则默认使用内联 text!

  • 示例:

    <!-- 可显示的指定使用内联text -->
    <div th:inline="text">
        <p>性别:[[${sex}]],年龄[[${age}]]</p>
    </div>
    
    <!-- 没有指定内联类型,默认使用内联text -->
    <div>
        <p>性别:[[${sex}]],年龄[[${age}]]</p>
    </div>
    

内联 javascript

  • 作用:可以在 js 中,获取模版中的数据

  • 语法:th:inline="javascript",必须指定!

  • 示例:

    <button onclick="fun()">单击按钮</button>
    
    <script type="text/javascript" th:inline="javascript">
        var name = [[${myuser.name}]];
        var id = [[${myuser.id}]];
        
        function fun() {
            alert("click 用户是"+name+",他的 id 是"+id);
        }
    </script>
    

自面量

  • 指在模板文件的表达式中使用的一些数据,如文本、数字、boolean、null
 <div style="margin-left: 400px">
     <h3>文本字面量: 使用单引号括起来的字符串</h3>
     <p th:text="'我是'+${name}+',我所在的城市是'+${city}">数据显示</p>

     <h3>数字字面量</h3>
     <p th:if="${20>5}"> 20大于 5</p>

     <h3>boolean字面量</h3>
     <p th:if="${isLogin == true}">用户已经登录系统</p>

     <h3>null字面量</h3>
     <p th:if="${myuser != null}">有myuser数据</p>
</div>

字符串连接

在模板文件的表达式中连接字符串有两种语法:

  1. 将字符串使用单引号括起来,使用加号连接其他的字符串或者表达式:
<p th:text="'我是'+${name}+',我所在的城市是'+${city}">数据显示</p>
  1. 使用双竖线,将需要拼接的字符串和表达时候写在双竖线中即可:|字符串和表达式|
<p th:text="|我是${name},我所在城市是${city}|">
    显示数据
</p>

运算符

  • 算术运算:+ , - , * , / , %
  • 关系比较:> , < , >= , <= ( gt , lt , ge , le )
  • 相等判断:== , != ( eq , ne )

使用示例:

<div style="margin-left: 400px">
    <h3>使用运算符</h3>
    <p th:text="${age > 10}">年龄大于 10 </p>
    <p th:text="${20 + 30}">显示运算结果</p>
    <p th:if="${myuser == null}">myuser是null</p>
    <p th:if="${myuser eq null}">myuser是null</p>
    <p th:if="${myuser ne null}">myuser不是null</p>

    <p th:text="${isLogin == true ? '用户已经登录' : '用户需要登录'}"></p>
    <p th:text="${isLogin == true ? (age > 10 ? '年龄大于10' : '年龄小于10') : '用户需要登录'}"></p>
</div>

三元运算符:
	表达式  ? true的结果 : false的结果

三元运算符可以嵌套

Thymeleaf 基本对象

常用的内置对象有以下几个:

  1. #request:表示 HttpServletRequest 对象

  2. #session:表示 HttpSession 对象

  3. session:表示一个 Map 对象,是 #session的简单表示方式,用来便捷获取 session 中指定 key 的值。

    #session.getAttribute("loginname") 等同 session.loginname
    

使用示例:

 <div style="margin-left: 350px">
     <h3>内置对象 #request,#session,session 的使用</h3>
     <p>获取作用域中的数据</p>
     <p th:text="${#request.getAttribute('requestData')}"></p>
     <p th:text="${#session.getAttribute('sessionData')}"></p>
     <p th:text="${session.loginname}"></p>

     <br/>
     <br/>
     <h3>使用内置对象的方法</h3>
     getRequestURL=<span th:text="${#request.getRequestURL()}"></span><br/>
     getRequestURI=<span th:text="${#request.getRequestURI()}"></span><br/>
     getQueryString=<span th:text="${#request.getQueryString()}"></span><br/>
     getContextPath=<span th:text="${#request.getContextPath()}"></span><br/>
     getServerName=<span th:text="${#request.getServerName()}"></span><br/>
     getServerPort=<span th:text="${#request.getServerPort()}"></span><br/>
</div>

Thymeleaf 内置工具类对象

内置工具类:Thymeleaf 自己的一些类,提供对日期、数字、字符串、list集合等的一些处理方法。

下面是一些常用的内置工具类对象:

  • #dates:处理日器的工具类

  • #numbers:处理数字的工具类

  • #strings:处理字符串的工具类

  • #lists:处理 list 集合的工具类

使用示例:

<div style="margin-left: 350px">
    <h3>日期类对象 #dates</h3>
    <p th:text="${#dates.format(mydate )}"></p>
    <p th:text="${#dates.format(mydate,'yyyy-MM-dd')}"></p>
    <p th:text="${#dates.format(mydate,'yyyy-MM-dd HH:mm:ss')}"></p>
    <p th:text="${#dates.year(mydate)}"></p>
    <p th:text="${#dates.month(mydate)}"></p>
    <p th:text="${#dates.monthName(mydate)}"></p>
    <p th:text="${#dates.createNow()}"></p>
    <br/>

    <h3>内置工具类 #numbers,操作数字的</h3>
    <p th:text="${#numbers.formatCurrency(mynum)}"></p>
    <p th:text="${#numbers.formatDecimal(mynum,5,2)}"></p>

    <br/>
    <h3>内置工具类 #strings, 操作字符串</h3>
    <p th:text="${#strings.toUpperCase(mystr)}"></p>
    <p th:text="${#strings.indexOf(mystr,'power')}"></p>
    <p th:text="${#strings.substring(mystr,2,5)}"></p>
    <p th:text="${#strings.substring(mystr,2)}"></p>
    <p th:text="${#strings.concat(mystr,'---java开发的黄埔军校---')}"></p>
    <p th:text="${#strings.length(mystr)}"></p>
    <p th:text="${#strings.length('hello')}"></p>
    <p th:unless="${#strings.isEmpty(mystr)}"> mystring 不是 空字符串  </p>

    <br/>
    <h3>内置工具类 #lists, 操作list集合</h3>
    <p th:text="${#lists.size(mylist)}"></p>
    <p th:if="${#lists.contains(mylist,'a')}">有成员a</p>
    <p th:if="!${#lists.isEmpty(mylist)}"> list 集合有多个成员</p>

    <br/>
    <h3>处理null</h3>
    <!-- 对象前加问号,会自动查询其是否为null,不是null则继续获取;是null则不获取。 -->
    <p th:text="${zoo?.dog?.name}"></p>

</div>

自定义模板

  • 作用:内容复用,定义一次,在其他的模板文件中可多次使用。

1.自定义模板

  • 模板定义语法:th:fragment="模板自定义名称"

  • 示例:(在 head.html 中创建下列模板)

    <div th:fragment="top">
        <p>百度地址</p>
        <p>www.baidu.com</p>
    </div>
    

2.使用自定义模板

有两种使用的语法格式:

  1. ~{文件名称 :: 自定义的模板名}
  2. 文件名称 :: 自定义的模板名

PS:文件名称指的自定义的模板所在的 html 文件名,不含后缀

对于模板,有两种常用的使用方式:包含模板(th:include),插入模板(th:insert)

a.插入模板
  • 解释:是在原有标签中,将自定义的模板添加进来,不会失去原有标签,但原有标签下的内容将失去。

  • 语法

    • 格式1:th:insert="~{文件名称 :: 自定义的模板名}"
    • 格式2:th:insert="文件名称 :: 自定义的模板名"
  • 示例:(在 test.html 中,将 head.html 中名为 top 的模板插入到指定标签中)

    使用格式1:

    <div th:insert="~{head :: top}">
        此div标签下内容将会丢失,但div标签仍然存在!
    </div>
    

    使用格式2:

    <div th:insert="head :: top">
        此div标签下内容将会丢失,但div标签仍然存在!
    </div>
    
b.包含模板
  • 解释:用自定义的模板替换原有标签,原有标签将不存在

  • 语法

    • 格式1:th:include="~{文件名称 :: 自定义的模板名}"
    • 格式2:th:include="文件名称 :: 自定义的模板名"
  • 示例:(在 test.html 中,将 head.html 中名为 top 的模板包含(替换)到指定的标签)

    使用格式1:

    <div th:include="~{head :: top}">
        整个div标签将被完全替换为自定义的模板!
    </div>
    

    使用格式2:

    <div th:include="head :: top">
        整个div标签将被完全替换为自定义的模板!
    </div>
    

3.整个 html 作为模板

如果要将整个 html 的内容作为模板插入(insert)或包含(include)到指定的标签中,可使用下列几种方式:

示例:将整个 head.html 作为模板,插入到指定标签处

a.插入整个 html 模板
方式一:
<div th:insert="head :: html">
    将整个 head.html 作为模板,插入到此标签处!
</div>

方式二:
<div th:insert="head">
    将整个 head.html 作为模板,插入到此标签处!
</div>

示例:将整个 head.html 作为模板,包含(替换)到指定标签处

b.包含整个 html 模板
方式一:
<div th:include="head :: html">
    将整个 head.html 作为模板,包含(替换)到此标签处!
</div>

方式二:
<div th:include="head">
    将整个 head.html 作为模板,包含(替换)到此标签处!
</div>

4.使用其他目录中的模板

说明:如果要使用的自定义模板或要使用的整个 html 模板与当前页面不在同一目录下,则在指定使用模板的文件名时需要使用相对路径即可!

例如:在 test.html 某标签中要将同级 common 目录下 left.html 作为整个模板来使用:

方式一:插入
<div th:insert="common/left :: html"></div>
方式二:插入
<div th:insert="common/left"></div>

方式一:包含
<div th:include="common/left :: html"></div>
方式二:包含
<div th:include="common/left"></div>

以上是使用其他目录中的整个 html 作为模板示例;使用其他目录中指定 html 页面中的某个自定义模板用法和其相同。

注解总结

Spring + SpringMVC + SpringBoot

创建对象的:
@Controller:放在类的上面,创建控制器对象,注入到容器中
@RestController:放在类的上面,创建控制器对象,注入到容器中。
             作用:复合注解,含@Controller,@ResponseBody,使用这个注解类的,里面的控制器方法的返回值都是数据。

@Service:放在业务层的实现类上面,创建service对象,注入到容器
@Repository:放在dao层的实现类上面,创建dao对象,放入到容器。没有使用这个注解,是因为现在使用MyBatis框架,dao对象				是MyBatis通过代理生成的。不需要使用@Repository,所以没有使用。
@Component:放在类的上面,创建此类的对象,放入到容器中。 

赋值的:
@Value:简单类型的赋值,例如在属性的上面使用@Value("李四") private String name
          还可以使用@Value获取配置文件者的数据(properties或yml)。 
          @Value("${server.port}") private Integer port

@Autowired:引用类型赋值自动注入的,支持byName, byType. 默认是byType 。 放在属性的上面,也可以放在构造         		    方法的上面。 推荐是放在构造方法的上面
@Qualifer:给引用类型赋值,使用byName方式。   
            @Autowird, @Qualifer都是Spring框架提供的。

@Resource:来自jdk中的注解,javax.annotation。 实现引用类型的自动注入, 支持byName, byType.
             默认是byName, 如果byName失败,再使用byType注入;在属性上面使用。

其他:
@Configuration:放在类的上面,表示这是个配置类,相当于xml配置文件

@Bean:放在方法的上面,把方法的返回值对象,注入到spring容器中。

@ImportResource:加载其他的xml配置文件,把文件中的对象注入到spring容器中。

@PropertySource:读取其他的properties属性配置文件

@ComponentScan:扫描器,指定包名,扫描注解的

@ResponseBody:放在方法的上面,表示方法的返回值是数据,不是视图
@RequestBody:把请求体中的数据,读取出来,转为java对象使用。

@ControllerAdvice:控制器增强,放在类的上面,表示此类提供了方法,可以对controller增强功能。

@ExceptionHandler:处理异常的,放在方法的上面

@Transactional:处理事务的, 放在service实现类的public方法上面,表示此方法有事务

SpringBoot中使用的注解:
@SpringBootApplication:放在启动类上面,包含了@SpringBootConfiguration
                          @EnableAutoConfiguration,@ComponentScan
    
MyBatis相关的注解:
@Mapper:放在类的上面,让MyBatis找到接口,创建他的代理对象    
@MapperScan:放在主类的上面,指定扫描的包,把这个包中的所有接口都创建代理对象,将对象注入到容器中
@Param:放在dao接口的方法的形参前面,作为命名参数使用的。
    
Dubbo注解:
@DubboService:在提供者端使用的,暴露服务的,放在接口的实现类上面
@DubboReference:在消费者端使用的,引用远程服务,放在属性上面使用。
@EnableDubbo:放在主类上面,表示当前引用启用Dubbo功能。
posted @ 2022-10-28 19:12  luis林  阅读(168)  评论(0编辑  收藏  举报