SpringBoot开发日记(一)——快速创建HelloWorld!

使用Maven创建

File-》new-》Project,选择maven,然后next

然后输入组织名称、模块名称、项目版本号等,然后next

 

选择项目位置,然后点击finish按钮完成创建

 

遇到目录不存在时当我们点击OK后,系统会自动帮我们创建 

创建完成后会询问你是否导入Maven,我们选择Enable Auto-Import(开启自动导入maven依赖) 

 

配置maven :File-》settings

 添加maven依赖,打开项目目录下的pom文件导入如下基础依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>
    </dependencies>

编写启动类,输标右键java目录,New-》Java Class,输入如下内容,点击OK

然后对启动类进行编辑如下

package cn.com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

 

 创建配置文件,在resources目录下新建application.properties,并在其中配置服务端口号为8888

 

 编写HelloWorldController

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
        return "hello spring boot!";
    }
}

 运行,在启动类上鼠标右键,如图点击运行

访问 

使用SpringInitializr创建

File-》new-》Project,选择Spring Initializr,然后next

然后填入各项信息

应为我们是web项目,然后按图示进行选择(我这里用的是比较新的2.1.7版)

然后选择一个自己存放的路径,点击finish即可

然后新建一个HelloController

@RestController
public class HelloController {

    @RequestMapping(value="/hello",method = RequestMethod.GET)
    public String hello(){
        return "Hello SpringBoot!";
    }

}

最后目录结构如图

然后你就可以点击IDEA的运行按钮直接运行啦!打开浏览器输入http://:localhost:8080/hello即可访问啦!

 

2.SpringBoot基础配置

使用application.yml配置文件格式如图 ,yml格式书写会比properties格式更加方便一些

 

 Profile占位符:开发者在项目发布之前,一般需要频繁地在开发环境、测试环境以及生产环境之间进行切换,这个时候大量的配置需要频繁更改,SpringBoot提供了解决方案,即约定的不同环境下配置文件名称规则为application-{profile}.properties,profile占位符表示当前环境的名称

 

 

 

 

3.SpringBoot整合视图层技术

整合Thymeleaf实现前后端不分离

在目前的企业级应用开发中,前后端分离是趋势,但是视图层技术还占有一席之地。Spring Boot对视图层技术提供了很好的支持,官方推荐使用的模板引擎是Thymeleaf。Thymeleaf支持HTML原型,既可以让前端工程师在浏览器中直接打开查看样式,也可以让后端工程师结合真实数据查看显示效果。同时,Spring Boot提供了Thymeleaf自动化配置解决方案,因此在SpringBoot中使用Thymeleaf非常方便

引入maven依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>

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

配置thymeleaf 

server:
  port: 8080


spring:
  thymeleaf:
    encoding: UTF-8
    prefix: classpath:/templates/
    suffix: .jsp
    cache: false


创建Controller类 

package com.taiji.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Controller
public class HelloController {

    @GetMapping("hello")
    public String hello(){
        return "hello";
    }
}

在resource目录下新建templates文件夹并在其下创建hello.html文件

在resource目录下新建static文件夹并导入js文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<style>
    span{
        color: #f00;
    }
</style>
<script type="text/javascript" src="/js/jquery/jquery.js"></script>
<body>
<a href="www.baidu.com">百度123</a>
<button id="btn">123</button>
</body>
<script type="text/javascript">
    $("#btn").click(function () {
        alert("123");
    })
</script>
</html>

 

 

4.Web开发

1.JSON处理器

当引入了spring-boot-starter-web依赖后,这个依赖中默认加入了jackson-databind作为JSON处理器,此时不需要添加额外的JSON处理器就能返回一段JSON了

package cn.com.example.entity;

import com.fasterxml.jackson.annotation.JsonFormat;

import java.util.Date;

public class Book {
    private String name;
    private String author;
    private Float price;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date publicationDate;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Float getPrice() {
        return price;
    }

    public void setPrice(Float price) {
        this.price = price;
    }

    public Date getPublicationDate() {
        return publicationDate;
    }

    public void setPublicationDate(Date publicationDate) {
        this.publicationDate = publicationDate;
    }
}
package cn.com.example.controller;

import cn.com.example.entity.Book;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
public class BookController {

    @GetMapping("/book")
    public Book findBooks(){
        Book book=new Book();
        book.setName("test");
        book.setAuthor("me");
        book.setPrice(100.5f);
        book.setPublicationDate(new Date());
        return book;
    }
}

常见的JSON处理器除了jackson-databind之外,还有Gson和fastjson

2.访问静态资源

我们在resource目录下新建一个static相关目录来保存静态文件,然后我们在image目录下放几张图片

在配置文件下加入,然后重启服务即可查看到图片

spring.resources.static-locations=classpath:static/image/

3.文件上传

4.@ControllerAdvice注解:主要用来处理全局数据(全局异常处理、添加全局数据、请求参数预处理)

5.自定义错误页面

6.CORS(Cross-Origin Resource Sharing)是由W3C制定的一种跨域资源共享技术标准,其目的就是为了解决前端的跨域请求

同域:协议+域名+端口号均相同

跨域:协议+域名+端口号一个或多个不相同

什么是跨域请求:

例如:一个域名为http:/test.cn的网站,发起一个http://test.cn/books/getBookInfo的Ajax请求(同域请求),但如果还是这个网站发起一个http://test2.cn/pay/purchase的Ajax请求(http://test2.cn:80与http://test.cn:80不同,这个请求即为跨域请求)

浏览器的同源策略:

出于安全考虑,浏览器限制从JS脚本发起的跨域HTTP请求,浏览器一旦检测到请求的域名不一致后,会阻塞请求结果(即请求可以发出去,但响应结果被阻塞了)(现在所有支持 JavaScript 的浏览器都会使用这个策略)

JSONP:Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据

 

当浏览器发起请求时,请求头中携带了如下信息:

假如服务端支持CORS,则服务端给出的响应信息如下:

响应头中有一个Access-Control-Allow-Origin字段,用来记录可以访问该资源的域。当浏览器收到这样的响应头信息之后,提取出Access-Control-Allow-Origin字段中的值,发现该值包含当前页面所在的域,就知道这个跨域是被允许的,因此就不再对前端的跨域请求进行限制。这就是GET请求的整个跨域流程,在这个过程中,前端请求的代码不需要修改,主要是后端进行处理

@CrossOrigin中的value表示支持的域,这里表示来自http://localhost:8081域的请求是支持跨域的

maxAge表示探测请求的有效期

allowedHeaders表示允许的请求头,*表示所有的请求头都被允许

 

也可以不在每个方法上添加@CrossOrigin注解,而是采用一种全局配置

全局配置需要自定义类实现WebMvcConfigurer接口,然后实现接口中的addCorsMappings方法

在addCorsMappings方法中,addMapping表示对哪种格式的请求路径进行跨域处理;allowedHeaders表示允许的请求头,默认允许所有的请求头信息;allowedMethods表示允许的请求方法,默认是GET、POST和HEAD;*表示支持所有的请求方法;maxAge表示探测请求的有效期;allowedOrigins表示支持的域

测试:

我们新建一个SpringBoot工程,端口指定为8081,新建一个html页面用ajax去请求8080端的接口

可以看到数据被请求到了 

7.配置类与XML配置:把xml文件与配置类关联,使得可以直接引入配置类

8.注册拦截器:

9.启动系统任务

有一些特殊的任务需要在系统启动时执行,例如配置文件加载、数据库初始化等操作。如果没有使用Spring Boot,这些问题可以在Listener中解决。Spring Boot对此提供了两种解决方案:CommandLineRunner和ApplicationRunner。

10.整合Servlet、Filter和Listener

11.路径映射

12.AOP

package cn.com.example.component;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class LogAspect {
    @Pointcut("execution(* cn.com.example.service.*.*(..))")
    public void pc1() {
    }
    @Before(value = "pc1()")
    public void before(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println(name + "方法开始执行...");
    }
    @After(value = "pc1()")
    public void after(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println(name + "方法执行结束...");
    }
    @AfterReturning(value = "pc1()", returning = "result")
    public void afterReturning(JoinPoint jp, Object result) {
        String name = jp.getSignature().getName();
        System.out.println(name + "方法返回值为:" + result);
    }
    @AfterThrowing(value = "pc1()",throwing = "e")
    public void afterThrowing(JoinPoint jp,Exception e) {
        String name = jp.getSignature().getName();
        System.out.println(name+"方法抛异常了,异常是:"+e.getMessage());
    }
    @Around("pc1()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        return pjp.proceed();
    }
}
package cn.com.example.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public String getUserById(Integer id){
        System.out.println("get..");
        return "user";
    }

    public void deleteUserById(Integer id){
        System.out.println("delete..");
    }
}
package cn.com.example.controller;

import cn.com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    UserService userService;

    @GetMapping("/getUserById")
    public String getUserById(Integer id){
      return userService.getUserById(id);
    }

    @GetMapping("/deleteUserById")
    public void deleteUserById(Integer id){
        userService.deleteUserById(id);
    }



}

 

5.整合持久层技术

整合JdbcTemplate

整合MyBatis

整合Spring Data JPA(按照图片讲一下就好了)

JPA(Java Persistence API)和Spring Data是两个范畴的概念。作为一名Java EE工程师,基本都有听说过Hibernate框架。Hibernate是一个ORM框架,而JPA则是一种ORM规范,JPA和Hibernate的关系就像JDBC与JDBC驱动的关系,即JPA制定了ORM规范,而Hibernate是这些规范的实现(事实上,是先有Hibernate后有JPA,JPA规范的起草者也是Hibernate的作者),因此从功能上来说,JPA相当于Hibernate的一个子集。Spring Data是Spring的一个子项目,致力于简化数据库访问,通过规范的方法名称来分析开发者的意图,进而减少数据库访问层的代码量。Spring Data不仅支持关系型数据库,也支持非关系型数据库。Spring Data JPA可以有效简化关系型数据库访问代码。

创建数据库(不需要创建表 )

CREATE DATABASE 'jpa' DEFAULT CHARACTER SET utf-8;

添加MySQL和Spring Data JPA的依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>2.3.1.RELEASE</version>
        </dependency>

数据库配置:在application.properties中添加

#连接数据库
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/jpa?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.max-idle=10  //最大空闲连接数
spring.datasource.max-wait=10000 //最大阻塞等待时间
spring.datasource.min-idle=5 //最小空闲连接数
spring.datasource.initial-size=5 

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

写Controller层 


@RestController
public class UserController {

    @Autowired
    private UserRepository userRepo;

    @GetMapping(value = "/user")
    public List<User> userList(){
        return userRepo.findAll();
    }

    @GetMapping(value = "/findByLoginName")
    public List<User> findByLoginName(){
        return userRepo.findByLoginName();
    }

}

写Dao层

自定义UserRepository 继承自JpaRepository。JpaRepository中提供了一些基本的数据操作方法,有基本的增删改查、分页查询、排序查询等

也可以使用字段组合来查询

还可以自己定义查询条件 


..
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User,Integer> {

    @Query(value = "select * from infos_user where login_name=?1 limit 1",nativeQuery = true)
    public User findByLoginName(String loginName);

}

 

写entity:

@Entity注解表示该类是一个实体类,在项目启动时会根据该类自动生成一张表,表的名称即@Entity注解中name的值,如果不配置name,默认表名为类名

@Id注解表示该属性是一个主键,@GeneratedValue注解表示主键自动生成


import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue
    private Integer id;
    private String age;
    private String name;

    public Integer getId() {
        return id;
    }

    public User() {
    }

    public User(Integer id, String age, String name) {
        this.id=id;
        this.age = age;
        this.name = name;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

 

6.整合NoSql

NoSQL是指非关系型数据库,非关系型数据库和关系型数据库两者存在许多显著的不同点,其中最重要的是NoSQL不使用SQL作为查询语言。其数据存储可以不需要固定的表格模式,一般都有水平可扩展性的特征

• Key/Value键值存储。这种数据存储通常都是无数据结构的,一般被当作字符串或者二进制数据,但是数据加载速度快,典型的使用场景是处理高并发或者用于日志系统等,这一类的数据库有Redis、Tokyo Cabinet等。

• 列存储数据库。列存储数据库功能相对局限,但是查找速度快,容易进行分布式扩展,一般用于分布式文件系统中,这一类的数据库有HBase、Cassandra等。

• 文档型数据库。和Key/Value键值存储类似,文档型数据库也没有严格的数据格式,这既是缺点也是优势,因为不需要预先创建表结构,数据格式更加灵活,一般可用在Web应用中,这一类数据库有MongoDB、CouchDB等。

• 图形数据库。图形数据库专注于构建关系图谱,例如社交网络,推荐系统等,这一类的数据库有Neo4J、DEX等

整合Redis

单个Redis、Redis集群、

添加依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>        

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.3.1.RELEASE</version>
        </dependency>

配置redis

@RestController
public class BookController {

    @Autowired
    RedisTemplate redisTemplate;//操作对象

    @Autowired
    StringRedisTemplate stringRedisTemplate;//操作字符串

    @GetMapping("/test1")
    public void test1(){
        ValueOperations<String,String> ops1=stringRedisTemplate.opsForValue();
        ops1.set("name","三国演义");
        String name=ops1.get("name");
        System.out.println(name);
        ValueOperations ops2=redisTemplate.opsForValue();
        Book b1=new Book();
        b1.setId(1);
        b1.setName("红楼梦");
        b1.setAuthor("曹雪芹");
        ops2.set("b1",b1);
        Book book=(Book) ops2.get("b1");
        System.out.println(book);
    }
}

Session共享:

正常情况下,HttpSession是通过Servlet容器创建并进行管理的,创建成功之后都是保存在内存中。如果开发者需要对项目进行横向扩展搭建集群,那么可以利用一些硬件或者软件工具来做负载均衡,此时,来自同一用户的HTTP请求就有可能被分发到不同的实例上去,如何保证各个实例之间Session的同步就成为一个必须解决的问题。Spring Boot提供了自动化的Session共享配置,它结合Redis可以非常方便地解决这个问题。使用Redis解决Session共享问题的原理非常简单,就是把原本存储在不同服务器上的Session拿出来放在一个独立的服务器上

案例:当一个客户端发送一个请求(无session),通过nginx将第一次请求分发给服务器1,服务器判断无session,就让那个客户进行登录操作,并得到响应,此时客户端会存储一个来自服务器1响应的session,并存储在客户端。当客户端发送第二次请求的时候,此时本次请求已经携带了session(跳过登录),nginx却将请求分发给服务器2,因为服务器2中没有session,所以无法与客户端session进行对应。所以程序会出现异常或是报错,无法正常响应。

 

redis实现分布式服务Session共享的原理:(引用 https://www.cnblogs.com/zhuxiaopijingjing/p/12550700.html)

当客户端第一次发送请求后,nginx将请求分发给服务器1 ,然后将服务器1 产生的session 放入redis中,这样的话 客户端、服务器1 和redis中都会有一个相同的session,当客户端发送第二次请求的时候,nginx将请求分发给服务器2 (已知服务器2 中无session),因为客户端自己携带了一个session,那么服务器2 就可以拿着客户端带来的session中的session ID去redis中获取session,只要拿到这个session,就能执行之后的操作

 

整合MongoDB

 

7.构建RESTful服务(PASS)

在REST中,资源是由URI来指定的,对资源的增删改查操作可以通过HTTP协议提供的GET、POST、PUT、DELETE等方法实现,使用REST可以更高效地利用缓存来提高响应速度,同时REST中的通信会话状态由客户端来维护,这可以让不同的服务器处理一系列请求中的不同请求,进而提高服务器的扩展性。在前后端分离项目中,一个设计良好的Web软件架构必然要满足REST风格。

         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>2.3.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
            <version>2.3.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>

添加依赖

 

8.开发工具与单元测试

devtool:热部署工具,当开发者将spring-boot-devtools引入项目后,只要classpath路径下的文件发生了变化,项目就会自动重启,这极大地提高了项目的开发速度

9.SpringBoot缓存

Spring 3.1中开始对缓存提供支持,核心思路是对方法的缓存,当开发者调用一个方法时,将方法的参数和返回值作为key/value缓存起来,当再次调用该方法时,如果缓存中有数据,就直接从缓存中获取,否则再去执行该方法。但是,Spring中并未提供缓存的实现,而是提供了一套缓存API,开发者可以自由选择缓存的实现,


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.3.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>

 开启缓存机制

 

@Service
@CacheConfig(cacheNames = "book_cache")
public class BookDao {
    @Autowired
    MyKeyGenerator myKeyGenerator;
    @Cacheable(keyGenerator = "myKeyGenerator")
    public Book getBookById(Integer id) {
        System.out.println("getBookById");
        Book book = new Book();
        book.setId(id);
        book.setName("三国演义");
        book.setAuthor("罗贯中");
        return book;
    }
    @CachePut(key = "#book.id")
    public Book updateBookById(Book book) {
        System.out.println("updateBookById");
        book.setName("三国演义2");
        return book;
    }
    @CacheEvict(key = "#id")
    public void deleteBookById(Integer id) {
        System.out.println("deleteBookById");
    }
}

 在BookDao上添加@CacheConfig注解指明使用的缓存的名字,这个配置可选,若不使用@CacheConfig注解,则直接在@Cacheable注解中指明缓存名字

在getBookById方法上添加@Cacheable注解表示对该方法进行缓存,默认情况下,缓存的key是方法的参数,缓存的value是方法的返回值。当开发者在其他类中调用该方法时,首先会根据调用参数查看缓存中是否有相关数据,若有,则直接使用缓存数据,该方法不会执行,否则执行该方法,执行成功后将返回值缓存起来,但若是在当前类中调用该方法,则缓存不会生效

如果开发者不想使用默认的key,也可以像第13行和第19行一样自定义key,第13行表示缓存的key为参数book对象中id的值,第19行表示缓存的key为参数id

@CachePut注解一般用于数据更新方法上,与@Cacheable注解不同,添加了@CachePut注解的方法每次在执行时都不去检查缓存中是否有数据,而是直接执行方法,然后将方法的执行结果缓存起来,如果该key对应的数据已经被缓存起来了,就会覆盖之前的数据,这样可以避免再次加载数据时获取到脏数据。

@CacheEvict注解一般用于删除方法上,表示移除一个key对应的缓存。@CacheEvict注解有两个特殊的属性:allEntries和beforeInvocation,其中allEntries表示是否将所有的缓存数据都移除,默认为false,beforeInvocation表示是否在方法执行之前移除缓存中的数据,默认为false,即在方法执行之后移除缓存中的数据

创建测试类: 

package org.sang.cache;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class CacheApplicationTests {
    @Autowired
    BookDao bookDao;
    @Test
    public void contextLoads() {
        bookDao.getBookById(1);
        bookDao.getBookById(1);
        bookDao.deleteBookById(1);
        Book b3 = bookDao.getBookById(1);
        System.out.println("b3:"+b3);
        Book b = new Book();
        b.setName("三国演义");
        b.setAuthor("罗贯中");
        b.setId(1);
        bookDao.updateBookById(b);
        Book b4 = bookDao.getBookById(1);
        System.out.println("b4:"+b4);
    }
}

一开始执行了两个查询,但是查询方法只打印了一次,因为第二次使用了缓存。接下来执行了删除方法,删除方法执行完之后再次执行查询,查询方法又被执行了,因为在删除方法中缓存已经被删除了。再接下来执行更新方法,更新方法中不仅更新数据,也更新了缓存,所以在最后的查询方法中,查询方法日志没打印,说明该方法没执行,而是使用了缓存中的数据,而缓存中的数据已经被更新了

 

 

cache-names:配置缓存名称,Redis中的key都有一个前缀,默认前缀就是“缓存名::”

redis.time:配置缓存有效期,即Redis中key的过期时间。

开启缓存,在项目入口类中开启缓存

 

10.安全管理

Shiro是一个轻量级的安全管理框架,提供了认证、授权、会话管理、密码管理、缓存管理等功能,Spring Security是一个相对复杂的安全管理框架,功能比Shiro更加强大,权限控制细粒度更高,对OAuth 2的支持也更友好,又因为Spring Security源自Spring家族,因此可以和Spring框架无缝整合,特别是Spring Boot中提供的自动化配置方案,可以让Spring Security的使用更加便捷

SpringSecurity:

只要在pom中引入

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>

则随机一个接口都会直接跳转到登录页面,登录页面是由SpringSecurity提供的

默认的用户名是user,默认的登录密码则在每次启动项目时随机生成,查看项目启动日志

从项目启动日志中可以看到默认的登录密码,登录成功后,用户就可以访问“/hello”接口了。

Oauth2

OAuth是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如头像、照片、视频等),而在这个过程中无须将用户名和密码提供给第三方应用。实现这一功能是通过提供一个令牌(token),而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站在特定的时段内访问特定的资源。这样,OAuth让用户可以授权第三方网站灵活地访问存储在另外一些资源服务器的特定信息,而非所有内容。例如,用户想通过QQ登录知乎,这时知乎就是一个第三方应用,知乎要访问用户的一些基本信息就需要得到用户的授权,如果用户把自己的QQ用户名和密码告诉知乎,那么知乎就能访问用户的所有数据,并且只有用户修改密码才能收回授权,这种授权方式安全隐患很大,如果使用OAuth,就能很好地解决这一问题

采用令牌的方式可以让用户灵活地对第三方应用授权或者收回权限。OAuth 2是OAuth协议的下一版本,但不向下兼容OAuth 1.0。OAuth 2关注客户端开发者的简易性,同时为Web应用、桌面应用、移动设备、起居室设备提供专门的认证流程。传统的Web开发登录认证一般都是基于Session的,但是在前后端分离的架构中继续使用Session会有许多不便,因为移动端(Android、iOS、微信小程序等)要么不支持Cookie(微信小程序),要么使用非常不便,对于这些问题,使用OAuth 2认证都能解决

 

OAuth 2中几个基本的角色。

• 资源所有者:资源所有者即用户,具有头像、照片、视频等资源。

• 客户端:客户端即第三方应用,例如上文提到的知乎。

• 授权服务器:授权服务器用来验证用户提供的信息是否正确,并返回一个令牌给第三方应用。

• 资源服务器:资源服务器是提供给用户资源的服务器,例如头像、照片、视频等。

 

OAuth 2授权流程

步骤01 客户端(第三方应用)向用户请求授权。

步骤02 用户单击客户端所呈现的服务授权页面上的同意授权按钮后,服务端返回一个授权许可凭证给客户端。

步骤03 客户端拿着授权许可凭证去授权服务器申请令牌。

步骤04 授权服务器验证信息无误后,发放令牌给客户端。

步骤05 客户端拿着令牌去资源服务器访问资源。

步骤06 资源服务器验证令牌无误后开放资源

 

11.整合WebSocket

为什么需要WebSocket在HTTP协议中,所有的请求都是由客户端发起的,由服务端进行响应,服务端无法向客户端推送消息,但是在一些需要即时通信的应用中,又不可避免地需要服务端向客户端推送消息

WebSocket是一种在单个TCP连接上进行全双工通信的协议,已被W3C定为标准。使用WebSocket可以使得客户端和服务器之间的数据交换变得更加简单,它允许服务端主动向客户端推送数据。在WebSocket协议中,浏览器和服务器只需要完成一次握手,两者之间就可以直接创建持久性的连接,并进行双向数据传输

WebSocket使用了HTTP/1.1的协议升级特性,一个WebSocket请求首先使用非正常的HTTP请求以特定的模式访问一个URL,这个URL有两种模式,分别是ws和wss,对应HTTP协议中的HTTP和HTTPS,在请求头中有一个Connection:Upgrade字段,表示客户端想要对协议进行升级,另外还有一个Upgrade:websocket字段,表示客户端想要将请求协议升级为WebSocket协议。这两个字段共同告诉服务器要将连接升级为WebSocket这样一种全双工协议,如果服务端同意协议升级,那么在握手完成之后,文本消息或者其他二进制消息就可以同时在两个方向上进行发送,而不需要关闭和重建连接。此时的客户端和服务端关系是对等的,它们可以互相向对方主动发送消息。和传统的解决方案相比,WebSocket主要有如下特点

WebSocket使用时需要先创建连接,这使得WebSocket成为一种有状态的协议,在之后的通信过程中可以省略部分状态信息(例如身份认证等)。• WebSocket连接在端口80(ws)或者443(wss)上创建,与HTTP使用的端口相同,这样,基本上所有的防火墙都不会阻止WebSocket连接。• WebSocket使用HTTP协议进行握手,因此它可以自然而然地集成到网络浏览器和HTTP服务器中,而不需要额外的成本。• 心跳消息(ping和pong)将被反复的发送,进而保持WebSocket连接一直处于活跃状态。• 使用该协议,当消息启动或者到达的时候,服务端和客户端都可以知道。• WebSocket连接关闭时将发送一个特殊的关闭消息。• WebSocket支持跨域,可以避免Ajax的限制。• HTTP规范要求浏览器将并发连接数限制为每个主机名两个连接,但是当我们使用WebSocket的时候,当握手完成之后,该限制就不存在了,因为此时的连接已经不再是HTTP连接了。• WebSocket协议支持扩展,用户可以扩展协议,实现部分自定义的子协议。• 更好的二进制支持以及更好的压缩效果

 

WebSocket既然具有这么多优势,使用场景当然也是非常广泛的,例如:• 在线股票网站。• 即时聊天。• 多人在线游戏。• 应用集群通信。• 系统性能实时监控。

12.消息服务

消息队列(Message Queue)是一种进程间或者线程间的异步通信方式,使用消息队列,消息生产者在产生消息后,会将消息保存在消息队列中,直到消息消费者来取走它,即消息的发送者和接收者不需要同时与消息队列交互。使用消息队列可以有效实现服务的解耦,并提高系统的可靠性以及可扩展性。目前,开源的消息队列服务非常多,如Apache ActiveMQ、RabbitMQ等,这些产品也就是常说的消息中间件

 

JMS(Java Message Service)即Java消息服务,它通过统一JAVA API层面的标准,使得多个客户端可以通过JMS进行交互,大部分消息中间件提供商都对JMS提供支持。JMS和ActiveMQ的关系就象JDBC和JDBC驱动的关系。JMS包括两种消息模型:点对点和发布者/订阅者,同时JMS仅支持Java平台

AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是一个线路层的协议规范,而不是API规范(例如JMS)。由于AMQP是一个线路层协议规范,因此它天然就是跨平台的,就像SMTP、HTTP等协议一样,只要开发者按照规范的格式发送数据,任何平台都可以通过AMQP进行消息交互。像目前流行的StormMQ、RabbitMQ等都实现了AMQP

RabbitMQ是一个实现了AMQP的开源消息中间件,使用高性能的Erlang编写。RabbitMQ具有可靠性、支持多种协议、高可用、支持消息集群以及多语言客户端等特点,在分布式系统中存储转发消息,具有不错的性能表现

在RabbitMQ中,所有的消息生产者提交的消息都会交由Exchange进行再分配,Exchange会根据不同的策略将消息分发到不同的Queue中。RabbitMQ中一共提供了4种不同的Exchange策略,分别是Direct、Fanout、Topic以及Header,这4种不同的策略中,前3种的使用频率较高,第4种的使用频率较低,下面分别对这4种不同的ExchangeType予以介绍

DirectExchange的路由策略是将消息队列绑定到一个DirectExchange上,当一条消息到达DirectExchange时会被转发到与该条消息routing key相同的Queue上,例如消息队列名为“hello-queue”,则routingkey为“hello-queue”的消息会被该消息队列接收

@Configuration
public class RabbitDirectConfig {
    public final static String DIRECTNAME = "sang-direct";
    @Bean
    Queue queue() {
        return new Queue("hello-queue");
    }
    @Bean
    DirectExchange directExchange() {
        return new DirectExchange(DIRECTNAME, true, false);
    }
    @Bean
    Binding binding() {
        return BindingBuilder.bind(queue())
                .to(directExchange()).with("direct");
    }
}

首先提供一个消息队列Queue,然后创建一个DirectExchange对象,三个参数分别是名字、重启后是否依然有效以及长期未用时是否删除 

创建一个Binding对象,将Exchange和Queue绑定在一起

@Component
public class DirectReceiver {
    @RabbitListener(queues = "hello-queue")
    public void handler1(String msg) {
        System.out.println("DirectReceiver:" + msg);
    }
}

 @RabbitListener注解指定一个方法是一个消息消费方法,方法参数就是所接收到的消息。然后在单元测试类中注入一个RabbitTemplate对象来进行消息发送

@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqApplicationTests {
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Test
    public void headerTest() {
        rabbitTemplate.send("hello-queue","hello direct");
    }
}

确认RabbitMQ已经启动,然后启动Spring Boot项目,启动成功后,运行该单元测试方法,在Spring Boot控制台打印日志 

 

FanoutExchange的数据交换策略是把所有到达FanoutExchange的消息转发给所有与它绑定的Queue,在这种策略中,routingkey将不起任何作用

package org.sang.rabbitmq;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitFanoutConfig {
    public final static String FANOUTNAME = "sang-fanout";
    @Bean
    FanoutExchange fanoutExchange() {
        return new FanoutExchange(FANOUTNAME, true, false);
    }
    @Bean
    Queue queueOne() {
        return new Queue("queue-one");
    }
    @Bean
    Queue queueTwo() {
        return new Queue("queue-two");
    }
    @Bean
    Binding bindingOne() {
        return BindingBuilder.bind(queueOne()).to(fanoutExchange());
    }
    @Bean
    Binding bindingTwo() {
        return BindingBuilder.bind(queueTwo()).to(fanoutExchange());
    }
}
package org.sang.rabbitmq;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListeners;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Component
public class FanoutReceiver {
    @RabbitListener(queues = "queue-one")
    public void handler1(String message) {
        System.out.println("FanoutReceiver:handler1:" + message);
    }
    @RabbitListener(queues = "queue-two")
    public void handler2(String message) {
        System.out.println("FanoutReceiver:handler2:" + message);
    }
}

 

TopicExchange是比较复杂也比较灵活的一种路由策略,在TopicExchange中,Queue通过routingkey绑定到TopicExchange上,当消息到达TopicExchange后,TopicExchange根据消息的routingkey将消息路由到一个或者多个Queue上

@Configuration
public class RabbitTopicConfig {
    public final static String TOPICNAME = "sang-topic";
    @Bean
    TopicExchange topicExchange() {
        return new TopicExchange(TOPICNAME, true, false);
    }
    @Bean
    Queue xiaomi() {
        return new Queue("xiaomi");
    }
    @Bean
    Queue huawei() {
        return new Queue("huawei");
    }
    @Bean
    Queue phone() {
        return new Queue("phone");
    }
    @Bean
    Binding xiaomiBinding() {
        return BindingBuilder.bind(xiaomi()).to(topicExchange())
                .with("xiaomi.#");
    }
    @Bean
    Binding huaweiBinding() {
        return BindingBuilder.bind(huawei()).to(topicExchange())
                .with("huawei.#");
    }
    @Bean
    Binding phoneBinding() {
        return BindingBuilder.bind(phone()).to(topicExchange())
                .with("#.phone.#");
    }
}

第一个Binding中的“xiaomi.#”表示消息的routingkey凡是以“xiaomi”开头的,都将被路由到名称为“xiaomi”的Queue上 

@Component
public class TopicReceiver {
    @RabbitListener(queues = "phone")
    public void handler1(String message) {
        System.out.println("PhoneReceiver:"+ message);
    }
    @RabbitListener(queues = "xiaomi")
    public void handler2(String message) {
        System.out.println("XiaoMiReceiver:"+message);
    }
    @RabbitListener(queues = "huawei")
    public void handler3(String message) {
        System.out.println("HuaWeiReceiver:"+message);
    }
}

 

顺序不固定 

HeadersExchange是一种使用较少的路由策略,HeadersExchange会根据消息的Header将消息路由到不同的Queue上,这种策略也和routingkey无关

13.企业开发

定时任务:

Quartz:spring-boot-starter-quartz

Quartz是一个功能丰富的开源作业调度库,它由Java写成,可以集成在任何Java应用程序中,包括Java SE和Java EE等。使用Quartz可以创建简单或者复杂的执行计划,它支持数据库、集群、插件以及邮件,并且支持cron表达式,具有极高的灵活性。Spring Boot中集成Quartz和Spring中集成Quartz比较类似,主要提供三个Bean:JobDetail、Trigger以及SchedulerFactory

Job可以是一个普通的JavaBean,如果是普通的JavaBean,那么可以先添加@Component注解将之注册到Spring容器中。Job也可以继承抽象类QuartzJobBean,若继承自QuartzJobBean,则需要实现该类中的executeInternal方法,该方法在任务被调用时使用 

@Component
public class MyFirstJob {
    public void sayHello() {
        System.out.println("MyFirstJob:sayHello:"+new Date());
    }
}

 

public class MySecondJob extends QuartzJobBean {
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    @Override
    protected void executeInternal(JobExecutionContext context){
        System.out.println("hello:"+name+":"+new Date());
    }
}

创建QuartzConfig对JobDetail和Trigger进行配置 

@Configuration
public class QuartzConfig {
    @Bean
    MethodInvokingJobDetailFactoryBean jobDetail1() {
        MethodInvokingJobDetailFactoryBean bean =
                new MethodInvokingJobDetailFactoryBean();
        bean.setTargetBeanName("myFirstJob");
        bean.setTargetMethod("sayHello");
        return bean;
    }
    @Bean
    JobDetailFactoryBean jobDetail2() {
        JobDetailFactoryBean bean = new JobDetailFactoryBean();
        bean.setJobClass(MySecondJob.class);
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("name","sang");
        bean.setJobDataMap(jobDataMap);
        bean.setDurability(true);
        return bean;
    }
    @Bean
    SimpleTriggerFactoryBean simpleTrigger() {
        SimpleTriggerFactoryBean bean =
                new SimpleTriggerFactoryBean();
        bean.setJobDetail(jobDetail1().getObject());
        bean.setRepeatCount(3);
        bean.setStartDelay(1000);
        bean.setRepeatInterval(2000);
        return bean;
    }
    @Bean
    CronTriggerFactoryBean cronTrigger() {
        CronTriggerFactoryBean bean =
                new CronTriggerFactoryBean();
        bean.setJobDetail(jobDetail2().getObject());
        bean.setCronExpression("* * * * * ?");
        return bean;
    }
    @Bean
    SchedulerFactoryBean schedulerFactory() {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        SimpleTrigger simpleTrigger = simpleTrigger().getObject();
        CronTrigger cronTrigger = cronTrigger().getObject();
        bean.setTriggers(simpleTrigger,cronTrigger);
        return bean;
    }
}

 JobDetail的配置有两种方式:第一种方式通过MethodInvokingJobDetailFactoryBean类配置JobDetail,只需要指定Job的实例名和要调用的方法即可,注册这种方式无法在创建JobDetail时传递参数;第二种方式是通过JobDetailFactoryBean来实现的,这种方式只需要指定JobClass即可,然后可以通过JobDataMap传递参数到Job中,Job中只需要提供属性名,并且提供一个相应的set方法即可接收到参数

Trigger有多种不同实现,这里展示两种常用的Trigger:SimpleTrigger和CronTrigger,这两种Trigger分别使用SimpleTriggerFactoryBean和CronTriggerFactoryBean进行创建。在SimpleTriggerFactoryBean对象中,首先设置JobDetail,然后通过setRepeatCount配置任务循环次数,setStartDelay配置任务启动延迟时间,setRepeatInterval配置任务的时间间隔。在CronTriggerFactoryBean对象中,则主要配置JobDetail和Cron表达式

最后通过SchedulerFactoryBean创建SchedulerFactory,然后配置Trigger即可

 

14.应用监控

当一个Spring Boot项目运行时,开发者需要对Spring Boot项目进行实时监控,获取项目的运行情况,在项目出错时能够实现自动报警等。Spring Boot提供了actuator来帮助开发者获取应用程序的实时运行数据。开发者可以选择使用HTTP端点或JMX来管理和监控应用程序,获取应用程序的运行数据,包括健康状况、应用信息、内存使用情况等

开启端点:spring-boot-starter-actuator

Spring Boot默认包含的端点 

如果开发者不想暴露这么多端点,那么可以关闭默认的配置,然后手动指定需要开启哪些端点,如下配置表示关闭所有端点,只开启info端点:

 

端点的默认暴露情况 

 在Web应用中,默认只有health和info两个端点暴露,即当开发者在Spring Boot项目中加入spring-boot-starter-actuator依赖并启动Spring Boot项目后,默认只有这两个端口可访问

对于已经展示出来的接口,开发者可以直接发送相应的请求查看相关信息,例如请求health端点

端点保护:

启动Spring Boot项目,再去访问health端点,需要登录后才可以访问

监控信息可视化:

 

监控端点,返回JSON数据,这样查看起来非常不方便。Spring Boot中提供了监控信息管理端,用来实现监控信息的可视化,这样可以方便开发者快速查看系统运行状况,而不用去一个一个地调用接口(spring-boot-admin-starter-server)

在项目启动类上添加@EnableAdminServer注解,表示启动AdminServer

配置完成后,启动Spring Boot项目,在浏览器中输入http://localhost:8080/index.html

接下来开发Client。Client实际上就是一个一个的服务,Client将被注册到AdminServer上,然后AdminServer获取Client的运行数据并展示出来

然后在application.properties中添加以下两行配置

spring.boot.admin.client.url表示配置AdminServer地址。

配置完成后,启动Client项目,此时在AdminServer上就可以看到Client的运行数据

 

 

15.项目构建和部署

打Jar包

打War包

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值