Spring5框架新功能(通用日誌、函數式表達式、webflux、響應式編程)

Spring5 框架新功能

​ 整個 Spring5 框架的代碼基於 Java8,運行時兼容 JDK9,許多不建議使用的類和方法在代碼庫中刪除了。

1、Spring 5.0 框架自帶了通用的日誌封裝

​ (1)Spring5 已經移除 Log4jConfigListener,官方建議使用 Log4j2

​ (2)Spring5 框架整合 Log4j2

​ 第一步 引入 jar 包

​ 第二步 創建 log4j2.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!--日誌級別以及優先級排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration 後面的 status 用於設置 log4j2 自身內部的信息輸出,可以不設置,當設置成 trace 時,可以看到 log4j2 內部各種詳細輸出-->
<configuration status="INFO">
 <!--先定義所有的 appender-->
 <appenders>
 <!--輸出日誌信息到控制檯-->
 <console name="Console" target="SYSTEM_OUT">
 <!--控制日誌輸出的格式-->
 <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
 </console>
 </appenders>
    
 <!--然後定義 logger,只有定義 logger 並引入的 appender,appender 纔會生效!-->
 <!--root:用於指定項目的根日誌,如果沒有單獨指定 Logger,則會使用 root 作爲默認的日誌輸出-->
 <loggers>
 <root level="info">
 <appender-ref ref="Console"/>
 </root>
 </loggers>
</configuration>

2、Spring5 框架核心容器支持@Nullable 註解

​ @Nullable 註解可以使用在方法、屬性、參數上面,表示方法返回可以爲空,屬性值可以爲空,參數值可以爲空

3、Spring5 核心容器支持函數式風格 GenericApplicationContext

//函數式風格創建對象,交給 spring 進行管理
@Test
public void testGenericApplicationContext() {
 //1 創建 GenericApplicationContext 對象
 GenericApplicationContext context = new GenericApplicationContext();
 //2 調用 context 的方法對象註冊
 context.refresh();
 context.registerBean("user1",User.class,() -> new User());
 //3 獲取在 spring 註冊的對象
 // User user = (User)context.getBean("com.atguigu.spring5.test.User");
 User user = (User)context.getBean("user1");
 System.out.println(user);
}

4、Spring5 框架新功能——Webflux(類似於springMVC)

​ a)Webflux是 Spring5 添加新的模塊,用於 web 開發的,功能和 SpringMVC 類似的,Webflux 使用 當前一種比較流行的響應式編程出現的框架。

​ b)使用傳統 web 框架,比如 SpringMVC,這些基於 Servlet 容器,Webflux 是一種異步非阻塞的框架,異步非阻塞的框架在 Servlet3.1 以後才支持,核心是基於 Reactor 的相關 API 實現的。

解釋什麼是異步非阻塞 :

(1)異步和同步 (針對調用者

同步: 調用者發送請求,如果等着對方迴應之後纔去做其他事情就是同步

異步: 調用者發送請求,如果發送請求之後不等着對方迴應就去做其他事情就是異步 √

(2)非阻塞和阻塞 (針對被調用者

阻塞: 被調用者受到請求之後,做完請求任務之後纔給出反饋就是阻塞

非阻塞: 被調用者受到請求之後,受到請求之後馬上給出反饋然後再去做事情就是非阻塞 √

​ c)Webflux 特點

​ (1)第一 非阻塞式:在有限資源下,提高系統吞吐量和伸縮性,以 Reactor 爲基礎實現響應式編程

​ (2)第二 函數式編程:Spring5 框架基於 java8,Webflux 使用 Java8 函數式編程方式實現路由請求

​ d)SpringMVC和Webflux之間區別

​ 相同:兩個框架都可以使用註解方式,都運行在 Tomcat 等容器中

​ 不同:SpringMVC 採用命令式編程,Webflux 採用異步響應式編程

5、響應式編程(Java 實現

​ 概念:什麼是響應式編程 響應式編程是一種面向數據流和變化傳播的編程範式。這意味着可以在編程語言中很方便 地表達靜態或動態的數據流,而相關的計算模型會自動將變化的值通過數據流進行傳播。

​ 例子:電子表格程序就是響應式編程的一個例子。單元格可以包含字面值或類似"=B1+C1"的公 式,而包含公式的單元格的值會依據其他單元格的值的變化而變化

​ a)Java8 及其之前版本是提供的觀察者模式兩個類 Observer 和 Observable實現

public class ObserverDemo extends Observable {
 public static void main(String[] args) {
 ObserverDemo observer = new ObserverDemo();
 //添加觀察者
 observer.addObserver((o,arg)->{
	 System.out.println("發生變化");
 });
 observer.addObserver((o,arg)->{
 	 System.out.println("手動被觀察者通知,準備改變");
 });
  //這裏必須進行兩個方法調用纔可以響應!!
 observer.setChanged(); //數據變化
 observer.notifyObservers(); //通知
 }
}

6、響應式編程(Reactor 實現

​ (1)響應式編程操作中,Reactor 是滿足 Reactive 規範框架

​ (2)Reactor 有兩個核心類,Mono 和 Flux,這兩個類實現接口 Publisher,提供豐富操作符。Flux 對象實現發佈者,返回 N 個元素;Mono 實現發佈者,返回 0 或者 1 個元素

​ (3)Flux 和 Mono 都是數據流的發佈者,使用 Flux 和 Mono 都可以發出三種數據信號: 元素值,錯誤信號,完成信號,錯誤信號和完成信號都代表終止信號,終止信號用於告訴 訂閱者數據流結束了,錯誤信號終止數據流同時把錯誤信息傳遞給訂閱者

(4)代碼演示 Flux (返回多個元素)和 Mono(返回0或1個元素

<!--第一步 引入依賴-->
<dependency>
 <groupId>io.projectreactor</groupId>
 <artifactId>reactor-core</artifactId>
 <version>3.1.5.RELEASE</version>
</dependency>
//第二步 編程代碼
public static void main(String[] args) {
 //just 方法直接聲明
 Flux.just(1,2,3,4);
 Mono.just(1);
 //其他的方法
 Integer[] array = {1,2,3,4};
 Flux.fromArray(array);

 List<Integer> list = Arrays.asList(array);
 Flux.fromIterable(list);
 Stream<Integer> stream = list.stream();
 Flux.fromStream(stream);
}

​ (5)三種信號特點

​ ① 錯誤信號和完成信號都是終止信號,不能共存的

​ ② 如果沒有發送任何元素值,而是直接發送錯誤或者完成信號,表示是空數據流

​ ③ 如果沒有錯誤信號,沒有完成信號,表示是無限數據流

​ (6)調用 just 或者其他方法只是聲明數據流,數據流並沒有發出,只有進行訂閱之後纔會觸發數據流,不訂閱什麼都不會發生的!!

 //just方法聲明後,進行訂閱
        Flux.just(1,2,3,4).subscribe(System.out::print);
        Mono.just(1).subscribe(System.out::print);

(7)操作符

​ 概念: 對數據流進行一道道操作,成爲操作符,比如工廠流水線

​ a) map 元素映射爲新元素

​ b) flatMap 元素映射爲流;把每個元素轉換流,把轉換之後多個流合併大的流

7、SpringWebflux 執行流程和核心 API

​ a)SpringWebflux 基於 Reactor,默認使用容器是 Netty,Netty 是高性能的 NIO 框架,異步非阻塞的框架

​ b)SpringWebflux 核心控制器 DispatchHandler,實現接口 WebHandler 接口

​ c)SpringWebflux 裏面 DispatcherHandler負責請求的處理 ; HandlerMapping請求查詢到處理的方法 ;HandlerAdapter真正負責請求處理 ; HandlerResultHandler響應結果處理

​ d)SpringWebflux 實現函數式編程,兩個接口:RouterFunction(路由處理) 和 HandlerFunction(處理函數)

8、SpringWebflux(基於註解編程模型)

​ a)SpringWebflux 實現方式有兩種:註解編程模型函數式編程模型

​ b)使用註解編程模型方式,和之前 SpringMVC 使用相似的,只需要把相關依賴配置到項目中, SpringBoot 自動配置相關運行容器,默認情況下使用 Netty 服務器

第一步 創建 SpringBoot 工程,引入 Webflux 依賴

第二步 配置啓動端口號

第三步 創建包和相關類

​ 實體類

//實體類
public class User {
    private String name;
    private String gender;
    private Integer age;

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

    public void setName(String name) {
        this.name = name;
    }
    //其他set和get略去...
}

​ 創建接口定義操作的方法

//用戶操作接口
public interface UserService {
    //根據id查詢用戶
    Mono<User> getUserById(int id);//Mono返回單個或零個元素

    //查詢所有用戶
    Flux<User> getAllUser();//Flux返回多個元素

    //添加用戶
    Mono<Void> saveUserInfo(Mono<User> user);
}

​ 接口實現類

@Repository
public class UserServiceImpl implements UserService {

    //創建map集合存儲數據
    private final Map<Integer,User> users = new HashMap<>();

    public UserServiceImpl() {
        this.users.put(1,new User("lucy","nan",20));
        this.users.put(2,new User("mary","nv",30));
        this.users.put(3,new User("jack","nv",50));
    }

    //根據id查詢
    @Override
    public Mono<User> getUserById(int id) {
        return Mono.justOrEmpty(this.users.get(id));
    }

    //查詢多個用戶
    @Override
    public Flux<User> getAllUser() {
        return Flux.fromIterable(this.users.values());
    }

    //添加用戶
    @Override
    public Mono<Void> saveUserInfo(Mono<User> userMono) {
        return userMono.doOnNext(person -> {
            //向map集合裏面放值
            int id = users.size()+1;
            users.put(id,person);
        }).thenEmpty(Mono.empty());
    }
}

​ 創建 controller

@RestController
public class UserController {
 //注入 service
 @Autowired
 private UserService userService;
 //id 查詢
 @GetMapping("/user/{id}")
 public Mono<User> geetUserId(@PathVariable int id) {
 return userService.getUserById(id);
 }
 //查詢所有
 @GetMapping("/user")
 public Flux<User> getUsers() {
 return userService.getAllUser();
 }
 //添加
 @PostMapping("/saveuser")
 public Mono<Void> saveUser(@RequestBody User user) {
 Mono<User> userMono = Mono.just(user);
 return userService.saveUserInfo(userMono);
 }
}

​ c)SpringMVC 方式實現,同步阻塞的方式,基於 SpringMVC+Servlet+Tomcat

​ SpringWebflux 方式實現,異步非阻塞 方式,基於 SpringWebflux+Reactor+Netty

9、SpringWebflux(基於函數式編程模型)

​ (1)在使用函數式編程模型操作時候,需要自己初始化服務器

​ (2)基於函數式編程模型時候,有兩個核心接口:RouterFunction(實現路由功能,請求轉發 給對應的 handler)和 HandlerFunction(處理請求生成響應的函數)。核心任務定義兩個函數 式接口的實現並且啓動需要的服務器。

​ ( 3 ) SpringWebflux 請 求 和 響 應 不 再 是 ServletRequest 和 ServletResponse ,而是 ServerRequest 和 ServerResponse

步驟:

​ 第一步 把註解編程模型工程複製一份 ,保留 entity 和 service 內容

​ 第二步 創建 Handler(具體實現方法)

public class UserHandler {
 private final UserService userService;
 public UserHandler(UserService userService) {
 this.userService = userService;
 }
 //根據 id 查詢
 public Mono<ServerResponse> getUserById(ServerRequest request) {
     //獲取 id 值
     int userId = Integer.valueOf(request.pathVariable("id"));
     //空值處理
     Mono<ServerResponse> notFound = ServerResponse.notFound().build();
     //調用 service 方法得到數據
     Mono<User> userMono = this.userService.getUserById(userId);
     //把 userMono 進行轉換返回
     //使用 Reactor 操作符 flatMap
     return userMono.flatMap(person ->
            ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(fromObject(person)))
             .switchIfEmpty(notFound);
     }

 //查詢所有
 public Mono<ServerResponse> getAllUsers() {
     //調用 service 得到結果
     Flux<User> users = this.userService.getAllUser();
     return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
         	 .body(users,User.class);
     }
    
 //添加
 public Mono<ServerResponse> saveUser(ServerRequest request) {
        //得到 user 對象
        Mono<User> userMono = request.bodyToMono(User.class);
        return
            ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
    }
}

​ 第三步 初始化服務器,編寫 Router

//1 創建 Router 路由
public RouterFunction<ServerResponse> routingFunction() {
 //創建 hanler 對象
 UserService userService = new UserServiceImpl();
 UserHandler handler = new UserHandler(userService);
 //設置路由
 return RouterFunctions.route(
GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
 .andRoute(GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
}

//2 創建服務器完成適配
public void createReactorServer() {
 //路由和 handler 適配
 RouterFunction<ServerResponse> route = routingFunction();
 HttpHandler httpHandler = toHttpHandler(route);
 ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
 //創建服務器
 HttpServer httpServer = HttpServer.create();
 httpServer.handle(adapter).bindNow();//立即生效
}

//最終調用
public static void main(String[] args) throws Exception{
 Server server = new Server();
 server.createReactorServer();
 System.out.println("enter to exit");
 System.in.read();
}


//使用 WebClient 調用
public class Client {
 public static void main(String[] args) {
 //調用服務器地址
 WebClient webClient = WebClient.create("http://127.0.0.1:5794");
 //根據 id 查詢
 String id = "1";
 User userresult = webClient.get().uri("/users/{id}", id)
 				.accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();
 System.out.println(userresult.getName());
 //查詢所有
 Flux<User> results = webClient.get().uri("/users")
 .accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
     
 results.map(stu -> stu.getName())
 .buffer().doOnNext(System.out::println).blockFirst();
 }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章