spring 略介绍

第一章 Spring 入门

1.1为什么要学spring

spring是java语言中必须要掌握的框架,它涵盖了java的各个领域,基本上是全能的。

spring的核心是依赖注入(DI),spring所有的技术方案都是基于DI发展来的。

1.2 maven入门(上)

maven:一个项目管理和自动化工具
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mYGMOOXz-1604221023961)(https://style.youkeda.com/img/ham/course/j4/mvn.svg)]
注:如果说spring是java必须要学习的框架,那么maven就是必须要掌握的自动化工具。
借助maven的中央仓库,我们可以把任意代码分享给别人

maven使用惯例优于配置的原则,在没有要求定制之前,所有项目都遵循以下结构,如果代码放错位置将无法正确运行

目录                      目的
${basedir}                存放pom.xml和所有子目录
${basedir}/pom.xml        maven 项目的配置文件
${basedir}/src/main/java  项目java的源代码
${basedir}/src/main/resources 项目的资源
${basedir}/src/test/java    项目的测试类
${basedir}/src/test/resources 测试使用的资源              

maven 安装
可以参考网上的安装教程
maven命令

mvn clean compile   //编译命令 扫描java的源码并完成编译,完成后在根目录下生产target文件
mvn clean package   //编译并打包命令  先编译然后把所有的java文件和资源打包为java的一个压缩格式jar
mvn clean install   //执行安装命令   先编译然后打包最后安装到本都的maven仓库目录中
mvn compile exec:java -Dexec.mainClass=${main} //先编译然后再执行某一个类的命令 ${main}是一个变量 类名

1.3maven入门(中)


上图的5个概念都会运用到maven的配置文件 pom.xml中

POM:project object model
一个项目的所有配置都放在pom文件中,主要包括:定义项目的类型和名字
管理依赖关系
定制插件

maven坐标:必须指定,表示这个maven工程部署再maven仓库的文件位置

<groupId>com.youkeda.course</groupId>  //像一个文件夹,一般都要命名
<artifactId>app</artifactId>           //类似于文件,不能使用中文和特殊字符
<packaging>jar</packaging>             //打包成的文件工程 一般默认jar 也可以是war ear pom
<version>1.0-SNAPSHOT</version>        //版本号,后面加SNAPSHOT表示不稳定,随时可以修改 不加或者加RELEASE表示稳定
                                       版本号一般从1.0.0开始 第一位数字表示主版本号 第二位数字表示新增功能次数 第三
                                       位数字表示修复bug的次数  一般数字都不超过100

maven 属性配置:用来做参数设置

<properties>
    <java.version>1.8</java.version>        //表示设置一个参数,它的值是1.8
    <maven.compiler.source>${java.version}</maven.compiler.source> //这个参数表示指定maven编译时源代码的jdk版本
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>//工程代码源文件的文件编码格式 一般默认utf-8
    <maven.compiler.target>${java.version}</maven.compiler.target> //按照这个值来编译源代码 这是是指1.8
</properties>

1.4 maven入门(下)

依赖管理dependencies:用于指定当前工程依赖其他代码库
一般我们声明了依赖,会现在本地.m2文件夹内查找文件,没有找到就会去中央仓库下载,下载之后存放在.m2文件内

<dependencies>
    <dependency>                //可以有多个<dependency> <dependency>标签 内容就是maven坐标
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </dependency>
</dependencies>

一般我们把别人写的叫做三方库,自己和团队写的叫做二方库。

中央仓库:maven会把所有的jar都存放在中央仓库中,我们可以访问阿里云的镜像服务器查看(https://maven.aliyun.com/mvn/search)
间接依赖:如果一个remote工程依赖了 okhttp 库,而当前工程locale依赖了 remote工程,这个时候locale工程也会自动依赖了 okhttp
插件体系plugins:了解插件格式即可

<build>
    <plugins>
        <plugin>                                         //内容也是maven坐标
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
        </plugin>
    </plugins>
</build>

1.5简单实例

此处略去依赖配置,接口和实现类

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import com.youkeda.service.MessageService;

/**
 * Application
 */
@ComponentScan                                   //注解
public class Application {

    public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);  //spring式的面向接口
                                                                                 只关心接口,不关心实现类
        MessageService msgService = context.getBean(MessageService.class);        //spring bean
        System.out.println(msgService.getMessage());

    }
}

第二章 spring 依赖注入

2.1 java注解 (annotation)

例子

@Service                               //这个就是注解 
public class MessageServiceImpl implements MessageService{

    public String getMessage() {
         return "Hello World!";
    }

}

annotation:它是java推出的一种注释机制,和普通注释不同,他可以可以在编译,运行阶段读取。它也可以看成一个特殊的类,可以完成一些自定义的行为。
我们来看看service的源代码来帮助理解注解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m7uZz6HV-1604221023967)(https://style.youkeda.com/img/ham/course/j4/service1.svg)]
可以看出一个注解是由多个注解组合而成的
target:它自身也是一个注解,拥有数组属性,用于设定该注解的作用目标范围,因为是数组,所以可以作用多个范围
我们来列举几个常用的范围:ElementType.TYPE 表示可以作用于类,接口类,枚举类上
ElementType.FIFLD 表示可以作用于类的属性上
ElementType.METHOD表示可以作用于类的方法上
ElementType.PARAMETER表示可以作用于类的参数
例如

@Target({ElementType.TYPE,ElementType.METHOD})  //这个表示可以作用于类和方法上

@Service             //作用于类上
public class MessageServiceImpl implements MessageService{

    public String getMessage() {
         return "Hello World!";
    }

}

public class MessageServiceImpl implements MessageService{

    @ResponseBody    //作用于方法上
    public String getMessage() {
         return "Hello World!";
    }

}

retention
它自身也是一个注解,用于声明该注解的生命周期,即是在编译和运行的哪个阶段有效,它有三个值可以选择
1:SOURCE:纯注释作用
2:CLASS:编译阶段有效
3:RUNTIME:运行时有效
例子:

@Retention(RetentionPolicy.RUNTIME) //在运行阶段有效

document
它自身也是一个注解,它的作用是将注解中的元素包含到javaDoc文档中,一般都需要添加这个注解。

@interface
作用是声明当前的Java类型是注解,固定语法

annotation属性

String value() default "";     //String 类型 value名称  
Annotation 的属性有点像类的属性一样,它约定了属性的类型(这个类型是基础类型:String、boolean、int、long),
和属性名称(默认名称是 value ,在引用的时候可以省略),default 代表的是默认值。

有了这个属性,我们就可以正式的引用一个 Annotation 啦,比如

import org.springframework.stereotype.Service;

@Service                 //也可以写成 @Service(value="Demo") 或@Service("Demo")
public class Demo {

}

annotation属性可以有多个
例如

@AliasFor("name")   //name表示别名,表示用这个别名 也可以访问到这个属性
    String value() default "";


    @AliasFor("value")
    String name() default "";

2.2spring bean

Ioc(inversion of control,控制反转)容器是spring框架最核心的组件,没有ioc容器就没有spring框架。它可以减少计算机代码之间的耦合度
在spring框架中,通过依赖注入(DI)来实现ioc

在spring的世界中,所有的java对象都被称为bean,构成应用程序主干和由ioc容器管理的对象都被称作beans,beans和它们之间的依赖关系反应在容器
配置的元数据中,几乎所有的bean都是由接口+实现类完成的,用户想要获取bean的实例直接从ioc容器获取就可以了,不需要关心实现类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZvmLW5p-1604221023970)(https://style.youkeda.com/img/ham/course/j4/ioc.svg)]

spring配置元数据的方式有两种: 基于xml
基于annotation (主流)
我们以主流annotation方案来做讲解:
我们可以通过一个接口很容易的从ioc容器获得bean对象

org.springframework.context.ApplicationContext

annotation类型的ioc容器对应的类是

org.springframework.context.annotation.AnnotationConfigApplicationContext

我们在启动java程序之前必须先启动ioc容器

ApplicationContext context = 
    new AnnotationConfigApplicationContext("fm.douban");  //启动ioc容器,并且加载fm.douban下的bean
                                                            (只要引用了spring注解的类都会被加载)

AnnotationConfigApplicationContext这个类的构造函数有两种
1AnnotationConfigApplicationContext(String …basePackages)根据包名实例化(常用)
2AnnotationConfigApplicationContext(Class clazz)根据自定义包名扫描行为实例化

spring bean的注解有以下几种

org.springframework.stereotype.Service   //@Service代表 service bean
org.springframework.stereotype.Component //@Component 通用的bean注解 其余三个注解都扩展于Component
org.springframework.stereotype.Controller //@Controller 作用于web bean
org.springframework.stereotype.Repository //@Repository 作用于持久化相关bean

2.3 spring bean (下)

ioc容器可以看成一个大工厂,我们不关心如何生产产品,只需要知道如何使用产品即可
依赖注入第一步是启动ioc容器:(ApplicationContext context =
new AnnotationConfigApplicationContext(“fm.douban”);)
第二步是完成依赖注入行为
依赖注入是一种编程思想,简单来说就是一种获取其他实例的规范

我们用一个实例来理解依赖注入的方法和作用:在SujectServiceImpl实现类中引入SongService实例,调用方法

不使用依赖注入时的引用
public class SubjectServiceImpl implements SubjectService {

   private SongService songService;

   //缓存所有专辑数据
   private static Map<String, Subject> subjectMap = new HashMap<>();
   static {
       Subject subject = new Subject();
       //... 省略初始化数据的过程
       subjectMap.put(subject.getId(), subject);
   }

   @Override
   public Subject get(String subjectId) {
       Subject subject = subjectMap.get(subjectId);
       //调用 SongService 获取专辑歌曲
       List<Song> songs = songService.list(subjectId);
       subject.setSongs(songs);
       return subject;
   }

   public void setSongService(SongService songService) {           
       this.songService = songService;
   }
}


使用依赖注入时引用
import fm.douban.model.Song;
import fm.douban.model.Subject;
import fm.douban.service.SongService;
import fm.douban.service.SubjectService;
import org.springframework.beans.factory.annotation.Autowired;   //对比2
import org.springframework.stereotype.Service;     //对比1 

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service                                   //对比1 新增了@Service 注解
public class SubjectServiceImpl implements SubjectService {

   @Autowired                           //对比2   在变量上新增了@Autowired注解
   private SongService songService;

   //缓存所有专辑数据
   private static Map<String, Subject> subjectMap = new HashMap<>();

   static {
       Subject subject = new Subject();
       subject.setId("s001");
       //... 省略初始化数据的过程
       subjectMap.put(subject.getId(), subject);
   }

   @Override
   public Subject get(String subjectId) {
       Subject subject = subjectMap.get(subjectId);
       //调用 SongService 获取专辑歌曲
       List<Song> songs = songService.list(subjectId);
       subject.setSongs(songs);
       return subject;
   }                                                             //对比3 删除了setSongService方法
}

依赖注入总结:依赖注入让我们得到其他bean实例变得十分简单,只需要添加注解就可以了
我们渐渐可以发现spring的理论十分复杂,但是它的应用却十分的简单

2.4 spring resourse(上)

我们先来了解一下在java工程中文件的三种情况:1 文件在电脑的某个位置 例如d:/mywork/a.doc
2 文件在工程目录下 例如:mywork/toutiao.png
3 文件在工程的src/main/resources目录下,我们知道这是maven工程存放文件的地方
对于第一第二种情况,我们可以使用file对象直接读写,但是第三种情况file对象读取不到,因为文件已经在jar里了。
计算机系统和java系统是有差别的,maven工程编译后会自动去掉src/main/java、src/main/resources 目录

classpath:java内部的文件路径
java内部的文件路径指定的文件可以被解析为inputstream对象,可以借助java io读取
例子

<dependency>                                //添加依赖 commons-io库
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.6</version>
</dependency>

public class Test {

  public static void main(String[] args) {
    // 读取 classpath 的内容
    InputStream in = Test.class.getClassLoader().getResourceAsStream("data.json");//经常会用到 代码表示从java的类加载器的实例中
                                                                  查找文件    Test.class表示test.java编译后的 java class文件 
    // 使用 commons-io 库读取文本
    try {
      String content = IOUtils.toString(in, "utf-8");
      System.out.println(content);
    } catch (IOException e) {
      // IOUtils.toString 有可能会抛出异常,需要我们捕获一下
      e.printStackTrace();
    }
  }

}

2.5spring resource(下)

我们都知道spring最擅长的就是封装,spring resource也不例外,它可以把本地文件,classpath文件,远程文件都封装在resource对象里面然后统一加载
我们来具体看一下
在spring 中定义了一个org.springframework.core.io.Resource类来封装文件,这个类的优势在于它可以支持普通的file,也可以支持class.path文件
并且通过org.springframework.core.io.ResourceLoader服务来完成任意文件的读写,你可以在任意的spring bean中引入ResourceLoader

@Autowired
private ResourceLoader loader;

我们来演示一下spring如何读取文件:

1 先创建一个接口 fileservice
public interface FileService {

    String getContent(String name);

}

2创建它的实现类
import fm.douban.service.FileService;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;            //这个类用来封装文件
import org.springframework.core.io.ResourceLoader;     // 这个类读写文件
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.InputStream;

@Service
public class FileServiceImpl implements FileService {

    @Autowired
    private ResourceLoader loader;                  //读写文件

    @Override
    public String getContent(String name) {
        try {
            InputStream in = loader.getResource(name).getInputStream();
            return IOUtils.toString(in,"utf-8");
        } catch (IOException e) {
           return null;
        }
    }
}

3 调用这个接口
FileService fileService = context.getBean(FileService.class);
  String content = fileService.getContent("classpath:data/urls.txt");  //读取classpath内的文件
  System.out.println(content);        


 String content2 = fileService.getContent("file:mywork/readme.md");   //读取工程目录下的文件
 System.out.println(content2);       

String content2 = fileService.getContent("https://www.zhihu.com/question/34786516/answer/822686390");//加载远程文件
System.out.println(content2); 

2.6 spring bean 的生命周期

spring bean 提供的生命周期管理极大的提高了工程化的能力,更好的管理了bean.
什么是生命周期?生命周期是生命从开始到结束的整个流程 状态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NC841GXA-1604221023972)(https://style.youkeda.com/img/ham/course/j4/beaninstance.svg)]
其中init(初始化)方法是我们必须要掌握的,我们以SubjectServiceImpl为例,通过注解来声明init

import javax.annotation.PostConstruct;       // @PostConstruct 注解的路径

@Service
public class SubjectServiceImpl implements SubjectService {

  @PostConstruct                     //使用这个注解就表示这个方法在spring bean 启动后自动执行
  public void init(){
      System.out.println("启动啦");
  }

}  

有了init方法 我们就可以把静态代码块的内容放在init里面了

@Service
public class SubjectServiceImpl implements SubjectService {

  @PostConstruct
  public void init(){
      Subject subject = new Subject();
      subject.setId("s001");
      subject.setName("成都");
      subject.setMusician("赵雷");

      subjectMap.put(subject.getId(), subject);
  }

}  

小结:spring声明周期可以让我们更轻松的初始化一些行为和维护数据

第三章 spring mvc

3.1 spring boot介绍

spring boot是spring团队打造的一个新的面向服务的框架,它让java web工程的创建 运行和部署变得非常容易,我们必须要掌握
spring boot 多了一些工程方案比如下面4点:
1 java web容器的嵌入集成,默认集成了tomcat服务器,不需要再额外部署
2 自定义了工程打包格式,通过一个main方法就可以把工程启动
3 默认集成了你能想到的第三方框架和服务 比如数据库连接
4 提供了标准的属性配置文件,支持应用的参数动态配置

创建spring boot工程
我们可以从官方网站(https://start.spring.io/)完成创建,
参考视频(https://qgt-document.oss-cn-beijing.aliyuncs.com/PY1/11/fm%E5%88%9B%E5%BB%BA%E5%B7%A5%E7%A8%8B.mp4)
注意:创建工程的时候要选择web依赖

3.2 spring controller入门

spring mvc 是java web的一种(说明java web 还是有很多框架的)实现框架
java web的规范就是servlet技术,所有的java web 都实现了 servlet api的定义

我们来了解web服务做了哪些事情
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aN5XkJCW-1604221023973)(https://style.youkeda.com/img/ham/course/j4/springmvc1.svg)]
我们来理一下流程:当一个网页请求到了服务器之后,然后它就会进入spring boot 应用,最后匹配到一个controller(这也是一个bean),然后路由到
一个具体的bean方法,执行完后返回结果,输出给客户端。
可以看出,如果我们能够掌握spring controller就可以自己提供java web 服务了
spring controller(控制器)有三个核心点:
1 bean 的配置:controller注解运用
2 网络资源的加载:加载网页
3 网址路由的配置:requestmap 注解运用

1 controller注解
spring controller本身也是一个 spring bean,只是多了web能力,所以我们只需要在类上添加一个注解@Controller就可以了

import org.springframework.stereotype.Controller;

@Controller                              //注意添加注解  一般不需要特意实现接口
public class HelloControl {


}

2 加载网页
在spring boot应用中,网页一般存放在目录src/main/resources/static下
controller一般会自动加载static下的html内容,所以创建网站十分简单

import org.springframework.stereotype.Controller;

@Controller
public class HelloControl {

    public String say(){       //定义返回的类型为String
        return "hello.html";   //表示自动加载src/main/resources/static/hello.html文件 ,我们只要把hello.html文件写好即可

    }

}

static子目录 如果hello.html的路径为src/main/resources/static/html/hello.html,
那返回的应该是(return "html/hello.html)这样 才能正确加载

RequestMapping注解
对于web 服务器来说必须要实现的一个功能就是路由:解析url并提供资源给调用者

我们一般把controller类存放在control子包里面
配置路由,我们只需要添加一个注解RequestMapping即可

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; //引入注解

@Controller                              
public class HelloControl {

    @RequestMapping("/hello")            //添加注解
    public String say(){
        return "html/hello.html";
    }

}

3.3 Get Request(一)

我们都知道网络中常用两个协议get 和post 一般我们上网看视频图片都是get协议包括我们上节课学的spring controller也是get协议

通过get协议,我们可以动态的渲染网页,还可以掌握参数的解析,通过获取参数我们可以获取特定的代码逻辑
例如https://www.baidu.com/s?wd=test 可以知道我们在百度中搜索了test
我们来看看网址格式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h6OGjupa-1604221023974)(https://style.youkeda.com/img/ham/course/j4/url.svg)]
url解析非常简单而且重要,url由四个部分组成 协议+域名+路径+参数

获取url 参数
每个url都可以设定自定义的参数,就像百度自定义的参数wd一样
需求:我们现在以id来作为歌单的参数,那么歌单的url应该是这样的

https://域名/songlist?id=xxxx
http://localhost:8080/songlist?id=xxxx  //本地电脑开发的url

定义这个参数

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;  //引入注解 spring mvc的注解都在这个包里面 直接应用

@Controller
public class SongListControl {

    @RequestMapping("/songlist")
    public String index( @RequestParam("id") String id){   //定义参数十分简单,只需要添加注解即可 
        return "html/songList.html";
    }

}

使用这个参数来查找
例子

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class SongListControl {

    @RequestMapping("/songlist")
    public String index( @RequestParam("id") String id ){
        if ("38672180".equals(id)) {    //如果有这个参数,就返回songList这个界面
          return "html/songList.html";
        }  else {
          return "html/404.html";      //没有就返回我们定义的404界面
        }   
    }

}

获取多个参数

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class SongListControl {

  @RequestMapping("/songlist")
  public String index(@RequestParam("id") String id,  @RequestParam("pageNum") int pageNum){  //添加多个参数即可
     return "html/songList.html";
  }
}

注意点:如果设置了多个参数,那么url访问的时候必须要传递这些参数
小知识:所有的网站和程序都是通过request请求来搭建的。

3.4 get request(二)

@GetMapping
我们在controller那一节学习了@RequestMapping注解来解析url请求路径,这个注解默认支持所有的http method,放开所有的http method这样不是
很安全,我们可以用@GetMapping代替@RequestMappin注解。

import org.springframework.web.bind.annotation.*;


@GetMapping("/songlist")                               //替换掉注解@RequestMapping
public String index(@RequestParam("id") String id,@RequestParam("pageNum") int pageNum){
  return "html/songList.html";
}

我们可以使用下面的url进行访问
http://xxxx/songlist?id=xxx&pageNum=1  //多个参数用&隔开

非必须传递参数
如果想要某个参数不是一定要传递,那么我们可以参数修改注解

@GetMapping("/songlist")
public String index(@RequestParam(name="pageNum",required = false) int pageNum,@RequestParam("id") String id){
                                                       //参数名称为pageNum required = false表示非必须传递
  return "html/songList.html";
}

我们可以通过下面两种的url进行访问
http://xxxx/songlist?id=xxx&pageNum=1

http://xxxx/songlist?id=xxx

输出json数据
我们上面的例子返回的都是html页面,但现在web通用的数据是json,那我们要如何在spring中配置json数据呢?

@GetMapping("/api/foos")
@ResponseBody                                   //添加此注解即可
public String getFoos(@RequestParam("id") String id) {
  return "ID: " + id;
}
使用以下url进行访问
https://xxxx/api/foos?id=100

小知识:spring mvc会自动的把对象转化为json字符串输出到网页中,我们把这种输出json数据的方法叫做api。

第四章 spring template 入门

4.1 thymeleaf 入门

它是spring选择的默认模板方案,它可以支持多种格式内容的动态渲染
它的原理:数据+模板+引擎渲染出真实的页面
例如
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yCnwWTJd-1604221023975)(https://style.youkeda.com/img/ham/course/j4/template1.svg)]

如何初始化thymeleaf?
1添加maven依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

数据传递
因为spring mvc 把页面数据封装的十分完善,我们在方法参数里面引入一个model对象,就可以通过这个model对象传递数据到页面中了。
导入这个model类

import org.springframework.ui.Model;

@Controller
public class SongListControl {

  @Autowired
  private SongListService songListService;

  @RequestMapping("/songlist")
  public String index(@RequestParam("id")String id,Model model){   //引入一个model对象

    SongList songList = songListService.get(id);
    //传递歌单对象到模板当中
    //第一个 songList 是模板中使用的变量名
    // 第二个 songList 是当前的对象实例
    model.addAttribute("songList",songList);

    return "songList";
  }
}

模板文件
模板文件一般存放在工程的src/main/resources/templates目录下
所以上面的return "songList"会去查找src/main/resources/templates/songList.html文件,且自动匹配后缀.html

注意点:只要工程src/main/resources/templates目录下面放置了文件,那么这些文件就是模板,而且表示被使用
src/main/resources/static 这个不是模板,是静态文件,如果使用了模板就一定要添加依赖

4.2thymeleaf变量

thymeleaf模板语法非常强大,它可以看作一个动态编程语言,它支持许多语言的特性,包括 变量 循环条件等。
th:text属性是模板自定义的html标签属性,th是thymeleaf的缩写,看到th就表示使用了thymeleaf模板语法。
举例:

<span th:text="${msg}">Hello</span>   //表示用msg替代了span 标签内的hello内容
<span>msg/span>

注意变量的格式${xxx}

要调用模板的变量需要先设置模板变量

model.addAttribute("msg",str);     // 变量名为msg  赋值为str
model.addAttribute("songList",songList);  //变量名为songList  变量值为songList

对象变量
我们还可以设置对象变量,然后用.把属性调用出来

model.addAttribute("sl",songList);  //在控制器中 设置变量为sl,变量值为songList对象

 <span th:text="${sl.id}"></span>   //在模板中用.调用对象的属性
    <span th:text="${sl.name}"></span>

4.3thymeleaf循环语句

在thymeleaf模板语法中 th:each 就表示for循环

<ul th:each="song : ${songs}">             //${songs} 是设置的变量  song是遍历后的每一个对象
  <li th:text="${song.name}">歌曲名称</li>   //读取每个歌曲的名称
</ul> 

打印列表的索引值
th:each 还有另外一种写法

<ul th:each="song,it: ${songs}">      //it作为可选参数 需要统计数据的时候可以使用
  <li>
    <span th:text="${it.count}"></span>
    <span th:text="${song.name}"></span>
  </li>
</ul>

我们来看看it有哪些统计结果
it.index          //从0开始显示行数
it.count          //从1开始显示行数
it.size           //被迭代对象的大小,获取列表长度时可以用
it.current        //表示当前变量,上面等同于song
it.even/odd       //布尔值,当前循环是否时偶数 奇数
it.first          //布尔值,当前循环是否是第一个
it.last           //布尔值,当前循环是否是最后一个

4.4thymeleaf表达式

thymeleaf对动态数据的处理非常方便,它常用于两种场景:
1 字符串处理
2 数据转化
字符串处理:我们举一个时间的例子

<span th:text="'00:00/'+${totalTime}"></span>  //我们可以使用' '来包住文本 然后使用+连接变量 否则会报错

<span th:text="00:00/+${totalTime}"></span>  //报错

<span th:text="|00:00/${totalTime}|"></span> //也可以使用|包围住字符串

数据转化:
thymeleaf默认集成了大量的工具类可以方便的进行数据的转化
一般我们使用最多的便是dates,如果想处理LocalDate和LocalDateTime类,可以添加依赖

<dependency>
  <groupId>org.thymeleaf.extras</groupId>
  <artifactId>thymeleaf-extras-java8time</artifactId>
  <version>3.0.4.RELEASE</version>
</dependency>

此库会默认添加一个工具类temporals

变量和工具类的表达是不同的,变量使用的是${变量名},而工具类是用的是#{工具类}

如果是data

Date dateVar = new Date();  //date型 

模板中的使用方法
<p th:text="${#dates.format(dateVar, 'yyyy-MM-dd')}"></p>   
<p th:text="${#dates.format(dateVar, 'yyyy年MM月dd日')}"></p>
<p th:text="${#dates.format(dateVar, 'yyyy-MM-dd HH:mm:ss')}"></p>
<p th:text="${#dates.format(dateVar, 'yyyy年MM月dd日 HH时mm分ss秒')}"></p>

如果是LocalDate/LocalDateTime

LocalDateTime dateVar = LocalDateTime.now();

<p th:text="${#temporals.format(dateVar, 'yyyy-MM-dd')}"></p>
	<p th:text="${#temporals.format(dateVar, 'yyyy年MM月dd日')}"></p>

	<p th:text="${#temporals.format(dateVar, 'yyyy-MM-dd HH:mm:ss')}"></p>
	<p th:text="${#temporals.format(dateVar, 'yyyy年MM月dd日 HH时mm分ss秒')}"></p>

一些其他的方法:例如#strings

${#strings.toUpperCase(name)}       //把字符串改成全大写
${#strings.toLowerCase(name)}       //把字符串改为全小写
${#strings.arrayJoin(array,',')}    //把字符串数组拼接为字符串,并以,连接
${#strings.arraySplit(str,',')}      //把字符串分隔成一个数组,并以,作为分隔符,比如a,b执行后会变成
                                      ["a","b"];如果abc没有匹配到,执行后会变成["abc"]
${#strings.trim(str)}                //把字符串去空格,左右空格都会去掉
${#strings.length(str)}              //得到字符串的长度,也支持获取集合类的长度
${#strings.equals(str1,str2)}        //比较两个字符串是否相等
${#strings.equalsIgnoreCase(str1,str2)} //忽略大小写后比较两个字符串是否相等

完整的内置对象

#messages
#urls/uris
#conversions
#dates
#calendars
#numbers
#strings
#objects
#bools
#arrays
#lists
#sets
#maps
#aggregates
#ids

内联表达式可以把变量之间写在html中

<span>Hello [[${msg}]]</span>  //[[变量]]这种格式就是内联表达式

th:text和[[]]两种写法都是允许的,看个人习惯

4.5 thymeleaf条件语句

thymeleaf同样也是支持条件语句if/else的
th:if的使用

<span th:if="${user.sex == 'male'}"></span> //if表达式的值为true时渲染

th:unless代表的是否定条件,表示unless表达式的false时渲染

<span th:unless="${user.sex == 'male'}"></span>

thymeleaf对下面的表达式都判定为true

1值非空
2值是非0数字
3值是字符串但不是false, off or no

strings逻辑判断
我们常常使用#strings来做逻辑判断和数据处理

检验是否为空
${#strings.isEmpty(name)}        //检验字符串
${#strings.arrayIsEmpty(name)}  //检验数组
${#strings.listIsEmpty(name)}    //检验集合类

contains 检查是否含有某个字符串片段

${#strings.contains(name,'abc')}                              //检查是否含有abc这个字符串
<p th:if="${#strings.contains(str1,'人')}">匹配到人这个字啦</p>  //检查是否含有人这个字
#strings判断语法的其他几个常用方法
${#strings.containsIgnoreCase(name,'abc')}    //先忽略大小写字母,然后去判断是否包含指定的字符串
${#strings.startsWith(name,'abc')}            //判断字符串是不是以 abc 开头的
${#strings.endsWith(name,'abc')}              //判断字符串是不是以 abc 结束:::的

第五章 spring template进阶

5.1 thymeleaf表单

实例演示:
1图书模型

public class Book{
  // 主键
  private long id;             //可以使用long类型 这样id更容易搜索,但也更容易被爬取数据
  // 图书的名称
  private String name;
  // 图书的作者
  private String author;
  // 图书的描述
  private String desc;
  // 图书的编号
  private String isbn;
  // 图书的价格
  private double price;
  // 图书的封面图片
  private String pictureUrl;
  // 省略 getter、setter
}

2页面开发 :我们现在thymeleaf模板里面创建一个名为addBook.html的模板,然后再创建一个BookControl的控制器

<form>
  <div>
    <label>书的名称:</label>
    <input type="text" />
  </div>
  <div>
    <label>书的作者:</label>
    <input type="text" />
  </div>
  <div>
    <label>书的描述:</label>
    <textarea></textarea>
  </div>
  <div>
    <label>书的编号:</label>
    <input type="text" />
  </div>
  <div>
    <label>书的价格:</label>
    <input type="text" />
  </div>
  <div>
    <label>书的封面:</label>
    <input type="text" />
  </div>
  <div>
    <button type="submit">注册</button>
  </div>
</form>


配置control
package com.bookstore.control;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
public class BookControl {
  // 当页面访问 http://localhost:8080/book/add.html 时
  // 渲染 addBook.html 模板
  @GetMapping("/book/add.html")
  public String addBookHtml(Model model){
    return "addBook";
  }
}

3 保存书籍 新增一个control来处理保存书籍的逻辑

@PostMapping("/book/save")          //它只接受http method为post请求的数据
  public String saveBook(Book book){
    books.add(book);               //接受book数据并储存到books对象中
    return "saveBookSuccess";      //返回模板下的saveBookSuccess页面
  }

新增一个saveBookSuccess 页面
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <title>添加书籍</title>
</head>

<body>
  <h2>添加书籍成功</h2>
</body>

</html>

4 form 表单:对html页面需要做一些修改

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <title>添加书籍</title>
</head>

<body>
  <h2>添加书籍</h2>
  <form action="/book/save" method="POST">     // 1 action属性设置为后端的请求路径  method设置为post
    <div>
      <label>书的名称:</label>
      <input type="text" name="name">          //2 input 的name属性后面的值要和模型类book保持一致
    </div>
    <div>
      <label>书的作者:</label>
      <input type="text" name="author">        //保持一致
    </div>
    <div>
      <label>书的描述:</label>
      <textarea name="desc"></textarea>
    </div>
    <div>
      <label>书的编号:</label>
      <input type="text" name="isbn">        //保持一致
    </div>
    <div>
      <label>书的价格:</label>
      <input type="text" name="price">      //保持一致
    </div>
    <div>
      <label>书的封面:</label>
      <input type="text" name="pictureUrl">   //保持一致
    </div>
    <div>
      <button type="submit">注册</button>
    </div>
  </form>
</body>

</html>

5.2 spring validation(一)

我们可以借助spring validation来完成表单数据的验证
jsr是java规范提案,它向jcp提出新增一个标准化技术规范的请求。
jsr380是bean(实例化后的pojo类)验证的规范,下面是它的依赖,都已经自动配置好了,平时不需要我们手动配置

<dependency>
  <groupId>jakarta.validation</groupId>
  <artifactId>jakarta.validation-api</artifactId>
  <version>2.0.1</version>
</dependency>

validation注解:jsr380的注解可以直接作用到bean的属性上

@NotNull          //不允许为 null 对象
@AssertTrue      //是否为 true
@Size            //约定字符串的长度
@Min             //字符串的最小长度
@Max             //字符串的最大长度
@Email           //是否是邮箱格式
@NotEmpty        //不允许为null或者为空,可以用于判断字符串、集合,比如 Map、数组、List    常用
@NotBlank        //不允许为 null 和 空格

示例

package com.bookstore.model;

import javax.validation.constraints.*;

public class User {

    @NotEmpty(message = "名称不能为 null")       //注解
    private String name;

    @Min(value = 18, message = "你的年龄必须大于等于18岁")
    @Max(value = 150, message = "你的年龄必须小于等于150岁")  //注解可以不止一条
    private int age;

    @NotEmpty(message = "邮箱必须输入")
    @Email(message = "邮箱不正确")
    private String email;

    // standard setters and getters 
}

完成验证:我们先写好pojo类user ,再创建一个user/addUser.html 模板文件,用于管理员添加成员
然后写一个control完成验证 如下

package com.bookstore.control;

import com.bookstore.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;   //BindingResult的路径
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import javax.validation.Valid;   //@Valid的路径

@Controller
public class UserControl {

    @GetMapping("/user/add.html")
    public String addUser() {
        return "user/addUser";
    }

    @PostMapping("/user/save")
    public String saveUser(@Valid User user, BindingResult errors) {  //增加两个参数 注意顺序
        if (errors.hasErrors()) {
            // 如果校验不通过,返回用户编辑页面
            return "user/addUser";         
        }
        // 校验通过,返回成功页面
        return "user/addUserSuccess";
    }

}

最后创建一个user/addUserSuccess.html模板文件,用于返回成功页面

5.3 spring validation(二)

如果验证错误怎么把错误结果返回到模板中?要解决上面这个问题,我们需要解决两个关键点。
1 如何传递数据?
2 如何显示具体的字段错误?

control改造
如果我们想显示具体的字段信息,就需要结合模型来传输,例如上节课的user

@GetMapping("/user/add.html")
public String addUser(Model model) {
    User user = new User();           //先创建一个user实例
    model.addAttribute("user",user);  //传递到模板中
    return "user/addUser";
}

user/add/html模板改造,我们取处理错误的状态,增加错误的样式和文案
th:object :让表单验证状态生效

<form action="/user/save" th:object="${user}" method="POST">   //在form标签中新增一个th:object="${user}"属性
                                                          用于替换对象,使用了这个就不需要每次都编写 user.xxx,可以直接操作 xxx
  ...
</form>

th:classappend :错误提示

必须要现有一个错误的样式
.error {color: red;}

然后可以使用th:classpath支持动态管理样式
<div th:classappend="${#fields.hasErrors('name')} ? 'error' : ''"></div>   //使用了三元表达式 ${#fields.hasErrors('key')}
                                                                         专门为了验证场景而提供,key 就是对象的属性名称,比如User 
                                                                          对象的 name、age、email 等
<div class="error"> </div>  //上面错误信息含有name之后就会变成此标签

th:errors :显示错误信息

<p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>   //th:errors="*{age}"属性可以自动取出错误信息

th:field :显示上一次输入的内容

<div th:classappend="${#fields.hasErrors('age')} ? 'error' : ''">
  <label>年龄:</label>
  <input type="text" th:field="*{age}" />
  <p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>
</div>

5.4 thymeleaf布局 (layout)

在一个网站里面经常会出现重复的导航底部等内容,我们可以把导航和底部做成布局组件,然后每个页面都套用上
使用th:include + th:replace方案可以帮助我们完成布局的开发
这个方案很简单分成两步:
第一步 :创建布局组件

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>布局</title>
    <style>
        .header {background-color: #f5f5f5;padding: 20px;}
        .header a {padding: 0 20px;}
        .container {padding: 20px;margin:20px auto;}
        .footer {height: 40px;background-color: #f5f5f5;border-top: 1px solid #ddd;padding: 20px;}
    </style>
</head>
<body>

<header class="header">
    <div>
        <a href="/book/list.html">图书管理</a>
        <a href="/user/list.html">用户管理</a>
    </div>
</header>                                            //这里是头部组件

<div class="container" th:include="::content">页面正文内容</div>    //这里会被替换为正文  ::content选择器,
                                                                     表示加载当前页面的th:fragment的值

 
<footer class="footer">                             //这是底部组件
    <div>
        <p style="float: left">&copy; youkeda.com 2017</p>
        <p style="float: right">
            Powered by 优课达
        </p>
    </div>
</footer>

</body>

第二步 正文内容页面 我们会把组件加在正文上

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      th:replace="layout">                                    //要用的布局的名称 不要写错 这里用的是templates/layout.html
<div th:fragment="content">                            //fragment(片段) 就是让上面的选择器选择这个片段  注意和选择器值保持一致
    <h2>用户列表</h2>
    <div>
        <a href="/user/add.html">添加用户</a>
    </div>
    <table>
        <thead>
        <tr>
            <th>
                用户名称
            </th>
            <th>
                用户年龄
            </th>
            <th>
                用户邮箱
            </th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="user : ${users}">
            <td th:text="${user.name}"></td>
            <td th:text="${user.age}"></td>
            <td th:text="${user.email}"></td>
        </tr>
        </tbody>
    </table>
</div>

</html>

总结一下 即在布局中添加一个选择器,然后在正文中使用这个布局并选择这个选择器

第六章 spring boot入门

6.1 spring boot ComponentScan

当我们自动注入bean实例的时候,有时候会出现如下报错

Field subjectService in fm.douban.app.control.SongListControl required a bean of type
 'fm.douban.service.SubjectService' that could not be found.

这是因为spring 在扫描的时候,只会扫描启动类的包及之下的子包,如果所需要的实例不在启动类的包内就会报错。
我们如何来解决这个问题?
有两种方法解决这个问题,第一:

@SpringBootApplication(scanBasePackages={"fm.douban.app", "fm.douban.service"}) //为启动类的注解添加
                                               一个参数,告诉系统有需要额外扫描的包

第二种:

@ComponentScan({"fm.service", "fm.app"})  //如果不是启动类,可以使用单独的注解@ComponentScan,同样指定需要额外扫描的包
public class SpringConfiguration {
  ... ...
}

额外知识点:使用@RestController 注解的类,所有的方法都不会渲染thymeleaf模板,而是返回数据。相当于使用@Controller 的类的方法
上面加上@ResponseBody 注解

6.2 spring boot logger运用

在很多情况下,spring打印出的内容具体输出在什么地方我们是不能确定的,所以我们要使用日志系统。
日志系统有两个优势:1日志系统可以轻松的控制日志是否输出
2日志系统可以灵活的设置日志的细节
使用日志系统有两大步骤:1 配置
修改spring boot系统的标准配置文件 application.properties ,增加日志级别配置

logging.level.root=info      //表示所有的日志root都为info级别       不输出低级别debug的日志信息
logging.level.fm.douban.app=info   //为不同的包定义不同的级别 fm.douban.app 包及其子包中的所有的类都输出 info 级别的日志。

常用的日志级别

优先级               级别                       含义及作用
最高                 error                     错误日志信息
高                   warn                      暂时不出错,但高风险的警告信息日志
种                   info                      一般的提示语,普通数据等不要紧的信息日志
低                   debug                     开发阶段需要注意的调试信息日志

2 编码:实例化日志对象

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;

@RestController
public class SongListControl {
    private static final Logger LOG = LoggerFactory.getLogger(SongListControl.class);  //固定写法,定义一个类变量

    @PostConstruct
    public void init(){
        LOG.info("SongListControl 启动啦");                //在参数种输入日志内容,注意保持级别的一致
    }
}

6.3 spring boot properties

spring boot框架已经有许多配置,但是部分配置还是要我们自己设置,对于一些特定的情况,我们会在application.properties文件中进行配置
配置文件格式,和上节课一样

配置项名称=配置项值
logging.level.root=info
logging.level.fm.douban.app=info   //等号两边不加空格

它的一些默认规则:配置项名称能够准确表达意义,作用以及.分隔符
              相同前缀名的配置项名称放在一起
              不同前缀名的配置项之间空一行

配置的意义:避免硬编码,解耦
把可变的内容从代码中剥离出来,做到在不修改代码的情况下,修改可变或者常变的内容

自定义配置项
在application.properties文件中设置自定义配置项

song.name=God is a girl   //自定义配置项


使用自定义配置项
import org.springframework.beans.factory.annotation.Value;

public class SongListControl {
    @Value("${song.name}")                  //使用@Value注解即可
    private String songName; 
}

第7章 spring session

7.1 cookie

cookie是网络编程中一种最常用的技术,它主要用来识别用户身份,它是临时放在客户端的

cookie有读写两种操作,服务端会返回cookie给客户端,也会读取客户端提交的cookie数据

读cookie:在control类方法中添加一个HttpServletRequest参数,通过request.getCookies()取得cookie数组,然后遍历循环数组。

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

@RequestMapping("/songlist")
public Map index(HttpServletRequest request) {   //系统会自动传入方法参数所需要的HttpServletRequest 对象
  Map returnData = new HashMap();
  returnData.put("result", "this is song list");
  returnData.put("author", songAuthor);

  Cookie[] cookies = request.getCookies();
  returnData.put("cookies", cookies);

  return returnData;
}

使用注解读取cookie
如果知道cookie的名字,就可以通过注解直接读取,不需要再遍历数组
control 类的方法增加一个 @CookieValue(“xxxx”) String xxxx 参数即可,注意使用时要填入正确的 cookie名字

import org.springframework.web.bind.annotation.CookieValue;

@RequestMapping("/songlist")
public Map index(@CookieValue("JSESSIONID") String jSessionId) {   // 添加一个注解参数 ,填入正确的名字 名字填错了会报错
  Map returnData = new HashMap();
  returnData.put("result", "this is song list");
  returnData.put("author", songAuthor);
  returnData.put("JSESSIONID", jSessionId);

  return returnData;
}

写cookie
control 类的方法增加一个 HttpServletResponse 参数,调用 response.addCookie() 方法添加 Cookie 实例对象即可

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

@RequestMapping("/songlist")
public Map index(HttpServletResponse response) {   //系统会自动传入方法参数所需要的HttpServletResponse对象
  Map returnData = new HashMap();
  returnData.put("result", "this is song list");
  returnData.put("name", songName);

  Cookie cookie = new Cookie("sessionId","CookieTestInfo");
  // 设置的是 cookie 的域名,就是会在哪个域名下生成 cookie 值
  cookie.setDomain("youkeda.com");
  // 是 cookie 的路径,一般就是写到 / ,不会写其他路径的
  cookie.setPath("/");
  // 设置cookie 的最大存活时间,-1 代表随浏览器的有效期,也就是浏览器关闭掉,这个 cookie 就失效了。
  cookie.setMaxAge(-1);
  // 设置是否只能服务器修改,浏览器端不能修改,安全有保障
  cookie.setHttpOnly(false);
  response.addCookie(cookie);

  returnData.put("message", "add cookie successfule");
  return returnData;
}

7.2 spring session api

cookie是临时放在客户端的,有可能会被拦截或者伪造,所以我们一般用session(服务端)来存放重要的信息(用户id,登录状态等)

使用session会话机制时,Cookie 作为 session id 的载体与客户端通信。上一节课演示代码中,Cookie 中的 JSESSIONID 就是这个作用.
JSESSIONID是专门用来纪录用户session的

读操作:
与cookie 相似,从 HttpServletRequest 对象中取得 HttpSession 对象,使用的语句是 request.getSession()。
但不同的是,返回结果不是数组,是对象。在 attribute 属性中用 key -> value 的形式存储多个数据。
假设存储登录信息的数据 key 是 userLoginInfo,那么语句就是 session.getAttribute(“userLoginInfo”)

登录信息类:
登录信息实例对象因为要在网络之中传输,就必须实现序列化接口Serializable,不实现的话就会报错

演示两个属性
import java.io.Serializable;

public class UserLoginInfo implements Serializable {
  private String userId;
  private String userName;
}

读操作

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@RequestMapping("/songlist")
public Map index(HttpServletRequest request, HttpServletResponse response) {
  Map returnData = new HashMap();
  returnData.put("result", "this is song list");

  // 取得 HttpSession 对象
  HttpSession session = request.getSession();
  // 读取登录信息
  UserLoginInfo userLoginInfo = (UserLoginInfo)session.getAttribute("userLoginInfo");  //读取信息的方法getAttribute
  if (userLoginInfo == null) {
    // 未登录
    returnData.put("loginInfo", "not login");
  } else {
    // 已登录
    returnData.put("loginInfo", "already login");
  }

  return returnData;
}

写操作:
登录成功之后,使用setAttribute()方法纪录登录信息到session

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@RequestMapping("/loginmock")
public Map loginMock(HttpServletRequest request, HttpServletResponse response) {
  Map returnData = new HashMap();

  // 假设对比用户名和密码成功
  // 仅演示的登录信息对象
  UserLoginInfo userLoginInfo = new UserLoginInfo();
  userLoginInfo.setUserId("12334445576788");
  userLoginInfo.setUserName("ZhangSan");
  // 取得 HttpSession 对象
  HttpSession session = request.getSession();
  // 写入登录信息
  session.setAttribute("userLoginInfo", userLoginInfo);
  returnData.put("message", "login successfule");

  return returnData;

7.3 spring session配置

cookie 作为session id的载体,也可以修改属性
前置知识点:配置
spring boot 也提供了编程式的配置方式,用于配置bean

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration                        //在类上添加@Configuration注解,表示这是一个配置类,系统会自动扫描处理
public class SpringHttpSessionConfig {
  @Bean                               //在方法上添加@Bean注解,表示把此方法返回的对象实例注册成bean 
  public TestBean testBean() {
    return new TestBean();
  }
}

session配置
依赖库,在pom.xml添加此依赖库

<!-- spring session 支持 -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-core</artifactId>
</dependency>

配置类
在类上额外添加一个注解@EnableSpringHttpSession ,开启 session 。然后,注册两个 bean
CookieSerializer:读写Cookies中的SessionId信息
MapSessionRepository:Session信息在服务器上的存储仓库

import org.springframework.session.MapSessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;

import java.util.concurrent.ConcurrentHashMap;

@Configuration
@EnableSpringHttpSession
public class SpringHttpSessionConfig {
  @Bean
  public CookieSerializer cookieSerializer() {
    DefaultCookieSerializer serializer = new DefaultCookieSerializer();
    serializer.setCookieName("JSESSIONID");
        // 用正则表达式配置匹配的域名,可以兼容 localhost、127.0.0.1 等各种场景
    serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
    serializer.setCookiePath("/");
    serializer.setUseHttpOnlyCookie(false);
    // 最大生命周期的单位是分钟
    serializer.setCookieMaxAge(24 * 60 * 60);
    return serializer;
  }

  // 当前存在内存中
  @Bean
  public MapSessionRepository sessionRepository() {
    return new MapSessionRepository(new ConcurrentHashMap<>());
  }
}

7.4 spring request 拦截器

Spring 提供了 HandlerInterceptor(拦截器)解决当有大量页面页面功能需要判断用户是否登录的情况。
实现拦截器
1 创建拦截器:实现HandlerInterceptor接口,可以在三个地方进行拦截
一 controller方法执行之前,这是最常用的,例如:是否登录的验证就需要在preHandle()方法中处理
二 controller方法执行之后,例如纪录日志 统计方法执行时间等就需要在postHandle()方法中处理
三 整个请求完成之后,不经常用。例如统计整个请求的执行时间的时候就需要在afterCompletion()中处理

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class InterceptorDemo implements HandlerInterceptor {

  // Controller方法执行之前
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    // 只有返回true才会继续向下执行,返回false取消当前请求
    return true;
  }

  //Controller方法执行之后
  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
      ModelAndView modelAndView) throws Exception {

  }

  // 整个请求完成后(包括Thymeleaf渲染完毕)
  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 
throws Exception {

  }
}

2 实现 webmvcconfigurer
创建一个类实现WebMvcConfigurer,并实现 addInterceptors() 方法。这个步骤用于管理拦截器。
实现类上面要添加一个@Configuration 注解,让框架能自动扫描并处理。

管理拦截器,最重要的是设置拦截范围,常用addPathPatterns("/**") 表示拦截所有的 URL 。
也可以调用excludePathPatterns() 方法排除某些 URL,例如登录页本身就不需要登录,需要排除。

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebAppConfigurerDemo implements WebMvcConfigurer {

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    // 多个拦截器组成一个拦截器链
    // 仅演示,设置所有 url 都拦截
    registry.addInterceptor(new UserInterceptor()).addPathPatterns("/**");
  }
}

通常拦截器会放在一个包里,而用于管理拦截器的配置类通常会放在另一个包。

第八章 spring data 入门

###8.1 专属mongodb服务器
mongodb是一个基于分布式文件存储的数据库,特点是可扩展,高性能为web应用提供强大的支持。
1 购买服务器 在滴滴云上(https://www.didiyun.com)购买服务器
2 登录服务器
执行命令

ssh dc2-user@xxx.xxx.xxx.xxx  //@ 符号之后输入自己购买的云主机的公网地址

3 安装docker
Docker 是近年来非常流行的虚拟化技术,这节课就不细讲了,主要用 Docker 来安装 MongoDB,自动化程度较高、非常简便。
使用命令行终端登录服务器后,依次输入命令(每一行是一条命令,一行输入完毕立即回车)执行:

sudo yum -y update
sudo yum -y install epel-release
sudo yum -y install docker-io

有的同学购买的云服务器不是用 dc2-user 账户登录的,是用 root 账户登录的。 ssh root@ 登录后,输入命令可以省略掉 sudo
,直接输入 yum -y … 即可。下同。
4 安装mongodb

sudo systemctl start docker
sudo docker version                                                     //先启动docker
sudo docker pull mongo:latest
sudo docker images                                                      //下载镜像
sudo docker run -itd --name mongo -p 27017:27017 mongo --auth          //启动mongodb
sudo docker ps                                                         //检查mongodb是否启动成功
sudo docker exec -it mongo mongo admin                                //登录数据库

db.createUser({ user:'admin',pwd:'123456',roles:[ { role:'userAdminAnyDatabase', db: 'admin'}]});
db.auth('admin', '123456')              //创建一个用户 admin 密码自己设置 

5创建数据库

use practice                     //切换数据库

db.createUser({ user:'xxxx',pwd:'xxxxxx',roles:['root']}); //创建读写用户

db.auth('xxxx', 'xxxxxx')            //认证数据库

6 退出登录

exit

8.2 spring data mongodb配置

pom.xml依赖
如果已经创建工程,可以手动添加依赖库

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency

配置

在src/main/resources/application.properties 文件,增加配置项:

# 购买的云服务器的公网 IP
spring.data.mongodb.host=xxx.xxx.xxx.xxx
# MongoDB 服务的端口号
spring.data.mongodb.port=27017
# 创建的数据库及用户名和密码
spring.data.mongodb.database=practice
spring.data.mongodb.username=pppp
spring.data.mongodb.password=111aaa
别忘了替换成自己购买的的云主机 IP 地址,以及 MongoDB 服务的用户名和密码

云服务器安全设置:打开27017端口

8.3 spring data crud

对数据库的操作一定要放在@Service类中,而不能放在@Controller类中。
@Controller 类可以调用 @Service 类的方法,反之则不行,这是 spring mvc 经典设计理念

@Service 类主要用于不易变的核心业务逻辑。
@Controller 类与前端页面紧密配合,调用 @Service 服务读写数据,从而响应前端请求,
1新增数据

import org.springframework.data.mongodb.core.MongoTemplate;

  @Autowired            //使用 @Autowired  注解可以让系统自动注入实例 这里为MongoTemplate实例
  private MongoTemplate mongoTemplate;

  public void test() {
    Song song = new Song();
    song.setSubjectId("s001");
    song.setLyrics("...");
    song.setName("成都");

    mongoTemplate.insert(song);
  }

2 查询数据

mongoTemplate.findById(songId, Song.class)   //第一个参数为主键id 第二个参数为类名.class

3 修改数据:修改那条数据?
哪个字段修改为什么值?

// 修改 id=1 的数据
Query query = new Query(Criteria.where("id").is("1")); //使用条件对象Criteria构建条件对象Query的实例

// 把歌名修改为 “new name”
Update updateData = new Update();                      
updateData.set("name", "new name");                   //调用修改对象Update的方法.set()设置需要修改的字段

// 执行修改,修改返回结果的是一个对象
UpdateResult result = mongoTemplate.updateFirst(query, updateData, Song.class);  //第三个参数是具体的类
// 修改的记录数大于 0 ,表示修改成功
System.out.println("修改的数据记录数量:" + result.getModifiedCount());

4 删除数据
调用mongoTemplate.remove() 方法即可删除数据,参数是对象,表示需要删除哪些数据。
通过主键来删除数据,是比较好的方式,不容易误删

Song song = new Song();
song.setId(songId);

// 执行删除
DeleteResult result = mongoTemplate.remove(song);   
// 删除的记录数大于 0 ,表示删除成功
System.out.println("删除的数据记录数量:" + result.getDeletedCount());

8.4 spring data query

只根据主键查询是不能满足需求的,我们会使用多条件查询来进行复杂的查询操作

核心方法 
List<Song> songs = mongoTemplate.find(query, Song.class);  //第一个参数 查询对象Query实例 
                                                             第二个参数类名.class表示查询什么样的对象

查询方法简单,但查询条件却十分复杂
Query query = new Query(criteria); 

Criteria 条件对象一般有两种情况
1 单一条件 返回一个条件对象实例

Criteria criteria1 = Criteria.where("条件字段名").is("条件值")

2 组合条件 根据or或者and将多个字条件组合为复合条件
使用or关系

Criteria criteria = new Criteria();
criteria.orOperator(criteria1, criteria2);

使用and关系

Criteria criteria = new Criteria();
criteria.andOperator(criteria1, criteria2);

orOperator()和andOperator()的参数,都可以输入多个子条件,也可以输入子条件数组

当然,组合条件下也可以多层组合,子条件也可以由组合得来

import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Criteria;

public List<Song> list(Song songParam) {
    // 总条件
    Criteria criteria = new Criteria();
    // 可能有多个子条件
    List<Criteria> subCris = new ArrayList();
    if (StringUtils.hasText(songParam.getName())) {
      subCris.add(Criteria.where("name").is(songParam.getName()));
    }

    if (StringUtils.hasText(songParam.getLyrics())) {
      subCris.add(Criteria.where("lyrics").is(songParam.getLyrics()));
    }

    if (StringUtils.hasText(songParam.getSubjectId())) {
      subCris.add(Criteria.where("subjectId").is(songParam.getSubjectId()));
    }

    // 必须至少有一个查询条件
    if (subCris.isEmpty()) {
      LOG.error("input song query param is not correct.");
      return null;
    }

    // 三个子条件以 and 关键词连接成总条件对象,相当于 name='' and lyrics='' and subjectId=''
    criteria.andOperator(subCris.toArray(new Criteria[]{}));

    // 条件对象构建查询对象
    Query query = new Query(criteria);
    // 仅演示:由于很多同学都在运行演示程序,所以需要限定输出,以免查询数据量太大
    query.limit(10);
    List<Song> songs = mongoTemplate.find(query, Song.class);

    return songs;
}

8.5 spring data分页

分页功能的实现十分简单,只需要调用PageRequest.of() 方法构建一个分页对象,然后注入到查询对象即可。
pagerequest.of()方法第一个参数是页码,从0开始,第二个页码是每页的内容数量

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

Pageable pageable = PageRequest.of(0 , 20);  //第一个参数为页码,第二个参数为每页的内容数量
query.with(pageable);

对于分页这个操作,我们要知道总数之后,计算出一共要分多少页。所以还有两个步骤要做:
1 调用count(query, XXX.class)方法查询总数,第一个参数是查询条件,第二个参数表示查询什么样的对象。
2 根据结果,分页条件,总数三个数据,构建分页器对象。

import org.springframework.data.domain.Page;
import org.springframework.data.repository.support.PageableExecutionUtils;

// 总数
long count = mongoTemplate.count(query, Song.class);
// 构建分页器
Page<Song> pageResult = PageableExecutionUtils.getPage(songs, pageable, new LongSupplier() {、
                                                //第一个参数是查询结果  第二个参数是分页条件对象  
   第三个参数比较复杂,实现一个LongSupplier 接口的匿名类,在匿名类的 getAsLong() 方法中返回结果总数。方法返回值是一个 Page 分页器对象,
  @Override
  public long getAsLong() {
    return count;
  }
});
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值