前端程序员忙里偷闲入门一波SpringBoot
-
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";
}
}
执行代码成功
-
Spring 依赖注入
-
Spring注解(Annotation)
-
注解的定义
注解通过 @interface 关键字进行定义。
@interface就是声明当前的Java类型是Annotation,固定语法就是这样写就好
public @interface TestAnnotation { }
它的形式跟接口很类似,不过前面多了一个 @ 符号。上面的代码就创建了一个名字为 TestAnnotaion 的注解。
-
注解的应用
创建一个类 Test,然后在类定义的地方加上 @TestAnnotation 就可以用 TestAnnotation 注解这个类了。可以简单理解为将 TestAnnotation 这张标签贴到 Test 这个类上面。
@TestAnnotation public class Test { }
要想注解能够正常工作,还需要介绍一下一个新的概念那就是元注解。
-
元注解
元标签有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。
-
@Retention
Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的取值如下:
- RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
- RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
我们可以这样的方式来加深理解,@Retention 去给一张标签解释的时候,它指定了这张标签张贴的时间。@Retention 相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期。
@Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation { }
-
@Documented
顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。
一般情况都会添加这个注解
-
@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; } }
-
@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 这个注解。
可以这样理解:
老子非常有钱,所以人们给他贴了一张标签叫做富豪。
老子的儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。
老子的孙子长大了,自然也是富豪。
这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签
-
@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{ }
-
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;
-
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检查到代码中有注解,就自动完成特定功能,减少代码量、降低系统复杂度。
-
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);
-
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启动后会自动执行
-
Spring MVC
-
Spring Controller
基本上所有的网页加载都是这样的一个过程。在Spring Boot方案里,一个网页请求到了服务器后,首先我们进入的是Java Web服务器,然后进入到Spring Boot应用,最后匹配到某一个SpringController(这其实也是一个Spring Bean),然后路由到具体某一个Bean的方法,执行完后返回结果,输出到客户端来。
SpringController三大技术点
- Bean的配置:Controller注解运用
- 网络资源的加载:加载网页
- 网址路由的配置:RequestMapping注解运用
-
Controller注解运用
import org.springframework.stereotype.Controller;
@Controller
public class HelloControl {
}
-
加载网页
在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";
}
}
-
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";
}
}
-
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注解即可
-
-
SpringBoot入门
-
-
-
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 { ... ... }
-
-
Spring Boot Logger
-
-
因为在Spring这种比较复杂的系统中,System.out.println()打印的内容会输出到什么地方,是不确定的。所以在企业级的项目中,都用日志系统来记录信息。
-
日志系统的两大优势:
-
- 日志系统可以灵活的配置日志的细节,例如输出格式,通常日志系统可以灵活的配置日志的细节,例如输出格式,通常日志的类名等信息,这样能很方便的观察日志分析问题。
- 日志系统可以轻松的控制日志是否输出。例如淘宝这样超大型的网站,在开发阶段需要打印出调试信息,但是发布到正式环境就不能打印了,因为每天几十几百亿的访问量,大量调试信息到导致磁盘撑爆。这时候就需要控制日志的输出,而不是修改所有的代码。
-
步骤
-
-
配置 修改Spring Boot系统的标准配置文件:application.properties(在项目的src/main/resources/目录下),增加日志级别配置:
- `
-
logging.level.root=info
-
-
表示所有日志都为info级别
-
我们也可以为不同的包定义不同的级别,例如
-
logging.level.fm.douban.app=info
-
表面在fm.douban.app及其子包的所有类都输出info级别的日志
-
日志优先级如下
-
-
级别的作用:
-
logging.level.root=warn意味着只输出warn级别及其以上的日志(warn,error)不输出低级别日志
-
- 编码
-
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()与日志级别一一对应
-
-
-
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; }
-
Spring Session
-
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.com
是youkeda.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; }
-
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; }
-
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<>()); } }
-
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)里。
-
Spring MongoDB
-
配置环境
添加依赖库、
<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端口
-
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());
-
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; }
-
-
转载自:https://juejin.cn/post/7189072326481150012