前端程序员忙里偷闲入门一波SpringBoot

  1. Hello Spring

新建一个工程,并创建如下目录

//HelloSpringApplication
package com.example.hellospring;

import com.example.hellospring.service.MessageService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class HelloSpringApplication {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(HelloSpringApplication.class);
        MessageService messageService = applicationContext.getBean(MessageService.class);
        System.out.println(messageService.getMessage());
    }

}
复制代码
//MessageService
package com.example.hellospring.service;

public interface MessageService {
    String getMessage();
}
复制代码
//MessageServiceImpl
package com.example.hellospring.service.Impl;

import com.example.hellospring.service.MessageService;
import org.springframework.stereotype.Service;

@Service
public class MessageServiceImpl implements MessageService {
    @Override
    public String getMessage() {
        return "Hello Spring";

    }
}
复制代码

执行代码成功

  1. Spring 依赖注入

    1.   Spring注解(Annotation)

      

    1.     注解的定义

        注解通过 @interface 关键字进行定义。

        @interface就是声明当前的Java类型是Annotation,固定语法就是这样写就好

    public @interface TestAnnotation {
    }
    复制代码

        它的形式跟接口很类似,不过前面多了一个 @ 符号。上面的代码就创建了一个名字为 TestAnnotaion 的注解。

    1.     注解的应用

        创建一个类 Test,然后在类定义的地方加上 @TestAnnotation 就可以用 TestAnnotation 注解这个类了。可以简单理解为将 TestAnnotation 这张标签贴到 Test 这个类上面。

    @TestAnnotation
    public class Test {
    }
    复制代码

        要想注解能够正常工作,还需要介绍一下一个新的概念那就是元注解。

    1.     元注解

        元标签有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。

    1.     @Retention

        Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。

        它的取值如下:

    • RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
    • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
    • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

        我们可以这样的方式来加深理解,@Retention 去给一张标签解释的时候,它指定了这张标签张贴的时间。@Retention 相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期。

    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestAnnotation {
    }
    复制代码
    1.     @Documented

        顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。

        一般情况都会添加这个注解

    1.     @Target

            Target 是目标的意思,@Target 指定了注解运用的地方。你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。@Target 有下面的取值

      • ElementType.ANNOTATION_TYPE 可以给一个注解进行注解

      • ElementType.CONSTRUCTOR 可以给构造方法进行注解

      • ElementType.FIELD 可以给属性进行注解

      • ElementType.LOCAL_VARIABLE 可以给局部变量进行注解

      • ElementType.METHOD 可以给方法进行注解

      • ElementType.PACKAGE 可以给一个包进行注解

      • ElementType.PARAMETER 可以给一个方法内的参数进行注解

      • ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

        几个常用例子

    • ElementType.TYPE
     @Service
    public class MessageServiceImpl implements MessageService{
    
        public String getMessage() {
             return "Hello World!";
        }
    
    }
    复制代码
    • ElementType.METHOD
    public class MessageServiceImpl implements MessageService{
    
        @ResponseBodypublic String getMessage( ) {
             return "Hello World!";
        }
    
    }
    复制代码
    • ElementType.FIELD
    public class MessageServiceImpl implements MessageService{
    
        @Autowiredprivate WorkspaceService workspaceService;
    
    }
    复制代码
    • ElementType.PARAMETER
    public class MessageServiceImpl implements MessageService{
    
        public String getMessage( @RequestParam("msg")String msg) {
             return "Hello "+msg;
        }
    
    }
    复制代码
    1.       @Inherited

          Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。 说的比较抽象。代码来解释。

    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @interface Test {}
    @Test
    public class A {}
    public class B extends A {}
    复制代码

          注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。

          可以这样理解:

          老子非常有钱,所以人们给他贴了一张标签叫做富豪。

          老子的儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。

          老子的孙子长大了,自然也是富豪。

          这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签

    1.       @Repeatable

          Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

          什么样的注解会多次应用呢?通常是注解的值可以同时取多个。

          举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。

    @interface Persons {
        Person[]  value();
    }
    @Repeatable(Persons.class)
    @interface Person{
        String role default "";
    }
    @Person(role="artist")
    @Person(role="coder")
    @Person(role="PM")
    public class SuperMan{
    }
    复制代码
    1.       Annotation属性

      String value() default "";
      复制代码

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

              注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface TestAnnotation {
          int id();
          String msg();
      }
      复制代码

              上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。

              赋值的方式是在注解的括号内以 value=”” 形式,多个属性之前用 ,隔开。

      @TestAnnotation(id=3,msg="hello annotation")
      public class Test {
      }
      复制代码

          注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:

    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestAnnotation {
        public int id() default -1;
        public String msg() default "Hi";
    }
    复制代码

          TestAnnotation 中 id 属性默认值为 -1,msg 属性默认值为 Hi。

            另外,还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。

    public @interface Check {
        String value();
    }
    复制代码

          上面代码中,Check 这个注解只有 value 这个属性。所以可以这样应用。

    @Check("hi")
    int a;
    复制代码
    1.       Spring Bean

        loC(Inversion of Control,控制反转)容器是Spring框架最最核心的组件,没有loC容器就没有Spring框架。IoC可以用来减低计算机代码之间的耦合度。在Spring框架当中,主要通过依赖注入(Dependency Injection,简称D)来实现IoC。

        在Spring的世界中,所有的Java对象都会通过IoC容器转变为Bean(Spring对象的一种称呼,以后我们都用Bean来表示Java对象),构成应用程序主干和由Spring loC容器管理的对象称为beans,beans和它们之间的依赖关系反映在容器使用的配置元数据中。基本上所有的Bean都是由接口+实现类完成的,用户想要获取Bean的实例直接从IoC容器获取就可以了,不需要关心实现类

        Spring主要有两种配置元数据的方式,一种是基于XML、一种是基于Annotation方案的,目前主流的方案是基于Annotation的。

      启动IOC容器

    ApplicationContext context = new AnnotationConfigApplicationContext("fm.douban");
    复制代码

        这段代码的含义就是启动IOC容器,并且会自动加载包fm.douban下的Bean,哪些Bean会被加载呢?只要引用了Spring注解的类都可以被加载(前提是在这个包下的)

          Spring官方声明为Spring Bean的注解有如下几种:

          org.springframework.stereotype.Service

          org.springframework.stereotype.Component

          org.springframework.stereotype.Controller

          org.springframework.stereotype.Repository

          只要我们在类上引用这类注解,那么都可以被IOC容器加载

          @Component注解是通用的Bean注解,其余三个注解都是扩展自Component

          @Service正如这个名称一样,代表的是Service Bean

          @Controller作用于Web Bean

          @Repository作用于持久化相关Bean

          实际上这四个注解都可以被IOC容器加载,一般情况下,我们使用 @Service;如果是Web服务就使用@Controller

    package fm.douban;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import fm.douban.service.SongService;
    import fm.douban.model.Song;
    
    /** * Application */
    public class Application {
    
      public static void main(String[] args) {
    
        ApplicationContext context = new AnnotationConfigApplicationContext("fm.douban");
        // 从容器中获取歌曲服务。
        SongService songService = context.getBean(SongService.class);
        // 获取歌曲
        Song song = songService.get("001");
        System.out.println("得到歌曲:" + song.getName());
    
      }
    }
    复制代码

        完成IOC容器启动之后,就要开始依赖注入了

        未使用依赖注入

    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; } } 
    复制代码

        我们如何获取SongService的实例呢?是不是得需要一个外部的工厂给我们传递,调用setSongService方法传入进来?相当麻烦

      使用依赖注入

    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;
    import org.springframework.stereotype.Service;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @Service
    public class SubjectServiceImpl implements SubjectService {
    
        @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;
        }
    }
    复制代码

        做了三处改动

    • 新增@Service注解

    • 在SongService上增加 @Autowired

    • 删除setSongService方法

        加注解的作用,是让Spring系统自动管理各种实例。

        所谓管理,就是用@Service注解把SubjectServiceImpl,SongServiceImpl等等所有服务实现,都标记成Spring Bean;

        然后,在任何需要使用服务的地方,用@Autowired注解标记,告诉Spring这里需要注入实现类的实例。

        项目启动过程中,Spring会自动实例化服务实现类,然后自动注入到变量中,不需要开发者大量的写new代码了,就解决了上述的开发者需要大量写代码而导致容易出错的问题。

        @Service和@Autowired是相辅相成的:如果SongServicelmp,没有加@Service就意味着没有标记成Spring Bean,那么即使加了@Autowired也无法注入实例;而private SongService songService;属性忘记加@AutowiredSpring Bean亦无法注入实例。二者缺一不可。

        每个Annotation(注解)都有各自特定的功能,Spring检查到代码中有注解,就自动完成特定功能,减少代码量、降低系统复杂度。

    1.     Spring Resource

  • Java工程中文件的几种情况

  •   1.文件在电脑某个位置,比如说d:/mywork/a.doc

  •   2.文件在工程目录下,比如说 mywork/toutiao.png

  •   3.文件在工程的src/main/resources目录下,我们在Maven的知识里介绍过这是Maven工程存放文件的地方

  •   第一种和第二种情况都是使用File对象就可以读写啦,第三种情况比较特殊,因为Maven执行package的时候,会把resources目录下的文件一起打包进jar包里(我们之前提到过jar是ava的压缩文件)。显然在第三种情况,用File对象是读取不到的,因为文件已经在jar里啦。

classpath

在Java内部当中,我们一般把文件路径称为classpath,所以读取内部的文件就是从classpath内读取,classpath指定的文件不能解析成File对象,但是可以解析成InputStream,我们借助Java IO就可以读取出来了

classpath类似虚拟目录,它的根目录是从/开始代表的是src/main/java或者src/main/resources目录我们来看一下如何使用classpath读取文件,这次我们在resources目录下存放一个data.json文件。

Java拥有很多丰富的第三方类库给我们使用,读取文件,我们可以使用commons-io这个库来,需要我们在pom.xml下添加依赖

<dependency>
<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");
    // 使用 commons-io 库读取文本 try {
      String content = IOUtils.toString(in, "utf-8");
      System.out.println(content);
    } catch (IOException e) {
      // IOUtils.toString 有可能会抛出异常,需要我们捕获一下
      e.printStackTrace();
    }
  }

}
复制代码

Spring 封装读取文件服务

public interface FileService {

    String getContent(String name);

}
复制代码
//实现类
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 {

    @Autowiredprivate ResourceLoader loader;

    @Overridepublic String getContent(String name) {
        try {
            InputStream in = loader.getResource(name).getInputStream();
            return IOUtils.toString(in,"utf-8");
        } catch (IOException e) {
           return null;
        }
    }
}
复制代码
  //服务调用
  //获取resourse下
  FileService fileService = context.getBean(FileService.class);
  String content = fileService.getContent("classpath:data/urls.txt");
  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);
复制代码
  1. Spring Bean的生命周期

大部分时候,我们只需要掌握init方法即可,注意这个init方法名称可以是任意名称的,因为我们是通过注解来声明Init的

我们以SubjectServicelmpl为例

import javax.annotation.PostConstruct;

@Service
public class SubjectServiceImpl implements SubjectService {

  @PostConstruct
  public void init(){
      System.out.println("启动啦");
  }

}  
复制代码

只要在方法上添加 @PostConstruct,就表示该方法在Spring Bean启动后会自动执行

  1. Spring MVC

  1. Spring Controller

基本上所有的网页加载都是这样的一个过程。在Spring Boot方案里,一个网页请求到了服务器后,首先我们进入的是Java Web服务器,然后进入到Spring Boot应用,最后匹配到某一个SpringController(这其实也是一个Spring Bean),然后路由到具体某一个Bean的方法,执行完后返回结果,输出到客户端来。

SpringController三大技术点

  • Bean的配置:Controller注解运用
  • 网络资源的加载:加载网页
  • 网址路由的配置:RequestMapping注解运用
  1. Controller注解运用

import org.springframework.stereotype.Controller;

@Controller
public class HelloControl {


}
复制代码
  1. 加载网页

在Spring Boot应用中,一般把网页存放在src/main/resources/static目录下,如下图

import org.springframework.stereotype.Controller;

@Controller
public class HelloControl {

    public String say(){
        return "hello.html";
    }

}
复制代码

注意看上面的代码的say方法

  • 定义返回类型为String
  • return"hello.html"返回的是html文件路径当执行这段代码的时候,Spring Boot实际加载的是

src/main/resources/static/hello.html文件

我们前面学习过,resouces属于classpath类型的文件,SpringBoot很强大,自动帮我们做了加载

,所以我们只需要写hello.html即可

static子目录

如果我们要找的文件放在目录src/main/resources/static/html/hello.html

import org.springframework.stereotype.Controller;

@Controller
public class HelloControl {

    public String say(){
        return "html/hello.html";
    }

}
复制代码
  1. RequestMapping注解运用

Spring MVC完美的支持了路由能力,并且简化了路由配置,只需要在需要提供Web访问的方法上添加一个@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";
    }

}
复制代码
  1. Get Request

  • 定义参数

  •   在Spring MVC中,定义一个URL参数也非常重要,只需要我们在方法上面添加对应的参数和参数注解就可以了,可以看一下下面的代码

  • import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.*;
    
    @Controller
    public class SongListControl {
    
        @RequestMapping("/songlist")
        public String index( @RequestParam("id") String id){
            return "html/songList.html";
        }
    
    }
    复制代码
  •   请注意RequestParam注解的参数"id"这个值必须要和URL的param key一样哦,因为我们在ur中定义的是id,所以这里写id。

  •   如果访问

  • https://域名/songlist?listId=xxxx
    复制代码
  •   则代码为

  •  @RequestMapping("/songlist")
    public String index( @RequestParam("listId") String id){
        return "html/songList.html";
    }
    复制代码
  •   操作参数

  •   现在我们完善一下代码逻辑,我们都知道访问不同的歌单网址,打开的就是不同的歌单页面,所以在这里,我们模拟一下行为,根据歌单id选择页面

  •   由于我们还没有学习动态渲染页面,所以这里我们就硬编码两个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){
            if("38672180".equals(id)){
                return "html/songList.html";
            }else{
                return "html/404.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,  @RequestParam("pageNum") int pageNum){
         return "html/songList.html";
      }
    }
    复制代码
  •   @GetMaping

  •   我们在一开始学习了@RequestMapping注解用于解析URL请求路径,这个注解默认是支持所有的Http Method的。放开所有的HttpMethod这样不是很安全,一般我们还是会明确制定method,比如 说get请求

  •   可以用@GetMapping替换@RequestMapping

  • import org.springframework.web.bind.annotation.*;
    
    
    @GetMapping("/songlist")
    public String index( @RequestParam("id") String id, @RequestParam("pageNum") int pageNum){
      return "html/songList.html";
    }
    复制代码
  •   非必须传递参数

  •  @GetMapping("/songlist")
    public String index( @RequestParam(name="pageNum",required = false) int pageNum, @RequestParam("id") String id){
      return "html/songList.html";
    }
    复制代码
  •   输出 JSON 数据

  •   我们前面的例子都是返回HTML内容,但是有的时候作为服务端,我们只想返回数据,目前来说通用的Wb数据格式就是SON,在Spring当中配置JSON数据非常非常简单,如下

  •  @GetMapping("/api/foos")
    @ResponseBody
    public String getFoos( @RequestParam("id") String id) {
      return "ID: " + id;
    }
    复制代码
  •    增加 @ResponseBody注解即可

    1.   SpringBoot入门

    1.   Spring Boot ComponentScan

  •   fm.douban.app.AppApplication类是启动类。而Spring Boot框架就会默认扫描fm.douban.app包(启动类所在的包)及其所有子包(fm.douban.app.、fm.douban.app..*)进行解析。 如果要解析非以上内容

  •   解决方法

  •   为启动类的注解@SpringBootApplication加一个参数,告知系统需要额外扫描的包:

  •  @SpringBootApplication(scanBasePackages={"fm.douban.app", "fm.douban.service"})
    public class AppApplication {
      public static void main(String[] args) {
        SpringApplication.run(AppApplication.class, args);
      }
    }
    复制代码
    • 参数名是:scanBasePackages
    • 参数值是一个字符串数组,用于指定多个需要额外自动扫描的包。需要把所有的待扫描的包的前缀都写入。
  •   另一种写法

  •   如果不是

  •   Spring Boot启动类,可以使用独立的注解@ComponentScan作用也是一样的,用于指定多个需要额外自动扫描的包。这个知识点不太常用,但是也需要记住,未来有可能会用到的。

  •  @ComponentScan({"fm.service", "fm.app"})
    public class SpringConfiguration {
      ... ...
    }
    复制代码
    1.   Spring Boot Logger

  •   因为在Spring这种比较复杂的系统中,System.out.println()打印的内容会输出到什么地方,是不确定的。所以在企业级的项目中,都用日志系统来记录信息。

  •    日志系统的两大优势:

    • 日志系统可以灵活的配置日志的细节,例如输出格式,通常日志系统可以灵活的配置日志的细节,例如输出格式,通常日志的类名等信息,这样能很方便的观察日志分析问题。
    • 日志系统可以轻松的控制日志是否输出。例如淘宝这样超大型的网站,在开发阶段需要打印出调试信息,但是发布到正式环境就不能打印了,因为每天几十几百亿的访问量,大量调试信息到导致磁盘撑爆。这时候就需要控制日志的输出,而不是修改所有的代码。
  •   步骤

    1. 配置 修改Spring Boot系统的标准配置文件:application.properties(在项目的src/main/resources/目录下),增加日志级别配置:

      1.     `
      2. logging.level.root=info
        复制代码
  •   表示所有日志都为info级别

  •   我们也可以为不同的包定义不同的级别,例如

  • logging.level.fm.douban.app=info
    复制代码
  •   表面在fm.douban.app及其子包的所有类都输出info级别的日志

  •   日志优先级如下

  •   级别的作用:

  •     logging.level.root=warn意味着只输出warn级别及其以上的日志(warn,error)不输出低级别日志

    1. 编码
  • 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 启动啦");
        }
    }
    复制代码
  •   注意这里的方法名info()与日志级别一一对应

    1.   Spring Boot Properties

          框架为我们提供来了application.properties配置文件,来对默认的配置进行修改

          配置文件格式

          application.properties配置文件的格式也很简单。每一行是一条

          配置项:配置项名称=配置项值。

      logging.level.root=info
      logging.level.fm.douban.app=info
      复制代码

          配置的意义

          配置的主要作用,是把可变的内容从代码中剥离出来,做到在不修改代码的情况下,方便的修改这些可变的或常变的内容。这个过程称之为避免硬编码、做到解耦。

          怎么判断可变呢?通常跟项目运行相关的上下文环境,比如端口号、路径等可能变化信息,是可变的或常变的内容。但主要还是根据具体的项目经验积累,提前判断。 在经验欠缺的时候,主要是依靠代码重构,当一个值变化的时候,要有敏感度思考是否应该采用配置的方式。

          自定义配置项

          用@Value注解

      song.name=God is a girl
      复制代码
      import org.springframework.beans.factory.annotation.Value;
      
      public class SongListControl {
          @Value("${song.name}")private String songName;
      }
      复制代码

          

      1.     Spring Session

      1.     Cookie

              读Cookie

              为control类的方法增加一个HttpServletRequest参数,通过request.getCookies()取得cookie数组。然后再循环遍历数组即可。(下列演示代码省略循环代码)

        import javax.servlet.http.Cookie;
        import javax.servlet.http.HttpServletRequest;
        
        @RequestMapping("/songlist")
        public Map index(HttpServletRequest request) {
          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的重要属性

        • name

              名称

        • value

              值

        • domain

          •         表示 cookie 生效的域。为 null 表示此 cookie 只对当前域名有效。如果设置了域名,表示当前域名和子域名都有效。ds011.agent.youkeda.comyoukeda.com 的子域名
        • path

               表示cookie 生效的目录。为 null 表示此 cookie 只对当前请求的 URL 路径有效。如果设置了域名,表示当前 URL 路径和所有下级路径都有效。/ 表示整个网站都生效

        • maxAge

              有效时间,默认值为-1。

              负数表示关闭浏览器就删除cookie

                0表示立即浏览器立即删除此cookie

                正数表示多少秒后过期自动失效

        • httpOnly

          •         安全性设置。
          •         值为 true 表示通过 js 脚本将无法读取到cookie信息。
          •         false 表示不限制

              使用注解读取Cookie

              如果知道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) {
          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;
        }
        复制代码
        1.       Spring Session API

              Cookie放在客户端,可以存储用户登录信息,但如果真的把用户登录状态等重要信息放入cookie,会带来安全隐患,

              采用Session会话机制可以解决这个问题,用户D、登录状态等重要信息不存放在客户端,而是存放在服务端,从而避免安全隐患。通讯过程如下图所示:

              读操作

              与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");
          if (userLoginInfo == null) {
            // 未登录
            returnData.put("loginInfo", "not login");
          } else {
            // 已登录
            returnData.put("loginInfo", "already login");
          }
        
          return returnData;
        }
        复制代码

              写操作

        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;
        }
        复制代码
        1.       Spring Session 配置

              Spring提供了编程式配置方式,主要用于配置Bean

        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        
        @Configuration
        public class SpringHttpSessionConfig {
          @Beanpublic TestBean testBean() {
            return new TestBean();
          }
        }
        复制代码

              在类上添加@Configuration注解,就表示这是一个配置类,系统会自动扫描并处理。

              在方法上添加@Bean注解,表示把此方法返回的对象实例注册成Bean 跟@Service等写在类上的注解一样,都表示注册Bean

              Session配置

              依赖库

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

              配置类

              在类上额外添加一个注解:@EnableSpringHttpSession,开启session。然后,注册两个Bean

              CookieSerializer:读写Cookies中的Sessionld信息 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<>());
          }
        }
        复制代码
        1.       Spring Request 拦截器

              创建拦截器

              拦截器必须实现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 {
        
          }
        }
        复制代码

              实现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("/**");
          }
        }
        复制代码

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

              

        1.       Spring MongoDB

        1.       配置环境

              添加依赖库、

        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        复制代码

              添加配置项

              src/main/resources/application.properties

         # 购买的云服务器的公网 IP
        spring.data.mongodb.host=192.168.0.1
        # MongoDB 服务的端口号
        spring.data.mongodb.port=27017
        # 创建的数据库及用户名和密码
        spring.data.mongodb.database=practice
        复制代码

              购买的云服务器要开放27017端口

        1.       CURD

                  新增数据

          import org.springframework.data.mongodb.core.MongoTemplate;
          
             @Autowired
            private MongoTemplate mongoTemplate;
          
            public void test() {
              Song song = new Song();
              song.setSubjectId("s001");
              song.setLyrics("...");
              song.setName("成都");
          
              mongoTemplate.insert(song);
            }
          复制代码

                  查询数据

          mongoTemplate.findById(songId, Song.class)
          复制代码

                  修改数据

           // 修改 id=1 的数据
          Query query = new Query(Criteria.where("id").is("1"));
          
          // 把歌名修改为 “new name”
          Update updateData = new Update();
          updateData.set("name", "new name");
          
          // 执行修改,修改返回结果的是一个对象
          UpdateResult result = mongoTemplate.updateFirst(query, updateData, Song.class);
          // 修改的记录数大于 0 ,表示修改成功
          System.out.println("修改的数据记录数量:" + result.getModifiedCount());
          复制代码

                  删除数据

          Song song = new Song();
          song.setId(songId);
          
          // 执行删除
          DeleteResult result = mongoTemplate.remove(song);
          // 删除的记录数大于 0 ,表示删除成功
          System.out.println("删除的数据记录数量:" + result.getDeletedCount());
          复制代码
        1.       Spring Data Query

              条件查询的核心方法

        List<Song> songs = mongoTemplate.find(query, Song.class);
        复制代码

              第一个参数是查询对象query实例,第二个参数是查询什么对象,传入class、

              查询条件往往比较复杂,需要用构建好的criteria条件对象来构建Query实例

        Query query = new Query(criteria);
        复制代码

              构建criteria对象有两种情况,

        • 单一条件

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

        • 复合条件

              根据and,or来组合

        • And

          • Criteria criteria = new Criteria();
            criteria.andOperator(criteria1, criteria2);
            复制代码
        • Or

          • Criteria criteria = new Criteria();
            criteria.orOperator(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;
        }

 

作者:NaiGeLan
链接:https://juejin.cn/post/7189072326481150012

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值