提示:一定是spring boot项目,因为spring cloud function web基于spring boot
文章目录
- 前言
- 一、前置条件
- 二、工程创建(使用idea创建maven工程即可)
- 二、实现功能范围
- 四、配置简述
- 五、应用配置文件(application.yml)
- 六、函数式Bean的配置(ApplicationFunctionConfig.java)
- 七、MVC Web的函数式路由配置(RouteConfig.java)
- 八、函数自定义及业务逻辑的实现(com.mrhan.functions包下)
- 九、业务实现类(service包下)
- 十、传统的Controller类(FunctionDemoController,FunctionDemoInfoController)
- 十一、启动与接口访问(使用postman)
- 十二、一些疑问与困惑的澄清
- 总结
前言
提示:直接实现了普通spring mvc web,函数式mvc web,cloud function web(函数即接口)的使用
Spring cloud function使用即介绍参见:https://docs.spring.io/spring-cloud-function/reference/4.3/spring-cloud-function/introduction.html
一、前置条件
jdk:17 Spring 相关模块: spring boot-3.5.8 spring-cloud-starter-function-web-4.3.0(引入这一个包即可把spring boot相关的包自动引入) 其他:spring-boot-starter-test-3.5.8 (可选) 开发工具:idea 2021.2 Spring cloud function参考文档:https://docs.spring.io/spring-cloud-function/reference/4.3/ Spring cloud function web不同类型函数请求方式参考:https://docs.spring.io/spring-cloud-function/reference/4.3/spring-cloud-function/standalone-web-applications.html 基础知识要求:spring boot,jdk的函数式编程(Function,Consumer,Supplier三类函数的基本使用),lambok表达式提示:spring cloud function web中的函数bean(一般默认就是创建bean的那个方法名)即为请求路径(等同于一个controller某个方法的的请求路径)
二、工程创建(使用idea创建maven工程即可)
注:各种包及文件夹,见名知意,不详述,如图
pom.xml
二、实现功能范围
- 传统controller的定义(类上及方法上定义请求路径)及使用
- mvc函数式路由的定义及使用,请求路径及业务处理分离
- Spring cloud function web中函数式Bean(函数的Bean名称即为http请求路径)定义及使用
- 使用函数式编程(自定义或jdk自带的函数接口)的业务逻辑实现
四、配置简述
| 配置(文件)名称 | 配置内容 | 作用 |
|---|---|---|
| Application.yml | 1.应用端口,访问上下文的配置;2.cloud function请求路径,暴露的http函数(方法)名配置 | 用于启动基于spring boot的spring cloud function web应用 |
| ApplicationFunctionConfig.java | 将函数注册为Bean的配置类 | 注册函数类型的bean到容器中 |
| RouteConfig.java | mvc函数式的请求路径(路由)配置 | 以函数式的方式分离业务实现与请求路径供外部调用接口,相当于controller |
五、应用配置文件(application.yml)
spring:cloud:function:web:# 定义函数的web路径,这个路径在servlet-》context-path路径下path:/functionwebpath# 按rest风格 配置不同请求方式对应的方法 多个函数用分号隔开,组合函数使用逗号或者竖线隔开http:delete:deleteUserpost:addUserput:uppercase;updateUser;reverseString;uppercase,getUserByUserIdget:getUserByUserId;getUserName;reverseStringprofiles:active:devserver:port:8082servlet:# 定义servlet的context-path,这个式应用访问的根路径,函数式的web路径在该请求下context-path:/functionservletpath提示:注意spring.cloud.function.web.path和server.servlet.context-path两个路径的区别,spring.cloud.function.web.path指的是请求函数方法的访问路径,server.servlet.context-path为整个应用(工程)的访问路径。
举例说明(以当前配置):
- 访问upppercase函数请求路径:localhost:8080/functionservletpath/functionwebpath/uppercase
- 访问自定义的传统controller方法:localhost:8082/functionservletpath/function/getUserByUserId?userId=xxx
- 访问mvc函数式请求路径方法(类似controller):localhost:8082/functionservletpath/info
六、函数式Bean的配置(ApplicationFunctionConfig.java)
packagecom.mrhan.config;importcom.mrhan.entity.User;importorg.springframework.cache.annotation.Cacheable;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjava.util.function.Consumer;importjava.util.function.Function;importjava.util.function.Supplier;/** * @author Mr han * @date 2025/12/11 * @description 应用的函数式相关的配置,主要式将函数定义为一个个的Bean,只要 * 是对象就可以定义为一个Bean,因为函数本身也是一个Bean * */@ConfigurationpublicclassApplicationFunctionConfig{// 定义一系列Function 功能型的函数@BeanpublicFunction<String,String>reverseString(){returnvalue->newStringBuilder(value).reverse().toString();}/** * * @return 返回一个将字符串转换成大写的函数 * @date 2025/12/11 * @description 将函数定义为一个bean(本身函数也是一个类,只是比较特别而已) * */@BeanpublicFunction<String,String>uppercase(){returnvalue->value.toUpperCase();}/** * * @return * @date 2025/12/13 * @description 按用户id返回用户信息 * */@BeanpublicFunction<String,User>getUserByUserId(){returnuserId->{Useruser=newUser();user.setUserId(userId);user.setName("han");user.setAge(18);returnuser;};}/** * * @return * @date 2025/12/13 * @description 添加用户发送post请求,携带json格式的数据 * */@BeanpublicFunction<User,Boolean>addUser(){returnuser->{System.out.println("添加用户:"+user.toString());returntrue;};}// 定义一些消费型的函数(Consumer)/** * * @return * @date 2025/12/13 * @description 传入用户信息并进行更新 * */@BeanpublicConsumer<User>updateUser(){returnuser->{System.out.println("更新用户信息:"+user);};}@BeanpublicConsumer<String>deleteUser(){returnuserId->{System.out.println("删除用户:"+userId);};}// 定义一些提供型(Supplier)的函数/** * * @return * @date 2025/12/13 * @description 返回一个用户名 * */@BeanpublicSupplier<String>getUserName(){return()->"han";}}七、MVC Web的函数式路由配置(RouteConfig.java)
packagecom.mrhan.config;importcom.mrhan.service.FunctiondemoInfoHandler;importjakarta.servlet.http.HttpServletRequest;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.http.MediaType;importorg.springframework.web.servlet.function.RequestPredicate;importorg.springframework.web.servlet.function.RouterFunction;importorg.springframework.web.servlet.function.ServerResponse;importstaticorg.springframework.web.servlet.function.RequestPredicates.accept;importstaticorg.springframework.web.servlet.function.RouterFunctions.route;/** * @author Mr han * @date 2025/12/16 * @description mvc函数式路由配置替代实际controller类的处理方法 */@Configuration(proxyBeanMethods=false)publicclassRouteConfig{privatestaticfinalLoggerlog=LoggerFactory.getLogger(RouteConfig.class);privatestaticfinalRequestPredicateACCEPT_TEXT_HTML=accept(MediaType.TEXT_HTML);/** * * @return * @date 2025/12/16 * @description 定义mvc的函数的请求,此请求将覆盖自定义的同路径Controller类方法,即意味着 * 定义了同请求路径的controller类方法将失效 * (此类失效 @see {@link com.mrhan.controller.FunctionDemoInfoController#info()} {@link com.mrhan.controller.FunctionDemoInfoController#gatewayInfo(HttpServletRequest)} ()}) * * */@BeanpublicRouterFunction<ServerResponse>customRoutes(FunctiondemoInfoHandlerfunctiondemoHandler){returnroute().GET("/info",ACCEPT_TEXT_HTML,functiondemoHandler::info).GET("/gatewayInfo",ACCEPT_TEXT_HTML,functiondemoHandler::gatewayInfo)// 添加before过滤器(不是web中的filter过滤器,只是相当于一个请求过来时的处理,比如打印请求日志,安全处理等).before(request->{Stringpath=request.requestPath().value();log.error("=========开始进入请求:{}=========",path);returnrequest;})// 添加请求之后的函数处理器.after((request,response)->{log.error("==========请求处理完毕:{},响应的状态码:",request.requestPath().value(),response.statusCode());returnresponse;}).build();}}提示:如果此处定义的路由路径和controller类定义的路径一致,那么请求只会进入到路由路径对应方法中执行业务逻辑即controller类方法将被覆盖而失效。
八、函数自定义及业务逻辑的实现(com.mrhan.functions包下)
动态函数工厂DynamicFunctionFactory.java:说白了就是定义了一些按传入的参数名字返回不同的函数对象方法的一个类
packagecom.mrhan.functions;importcom.mrhan.entity.User;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importjava.util.function.Consumer;importjava.util.function.Function;importjava.util.function.Supplier;@ComponentpublicclassDynamicFunctionFactory{@AutowiredprivateFunctionFactoryfunctionFactory;publicFunctioncreateFunctionByName(Stringname){switch(name){case"addPrefix":returnfunctionFactory.addPrefix("mrhan");case"addSuffix":returnfunctionFactory.addSuffix("hello");case"addUser":returnuser->{System.out.println("添加用户:"+user.toString());returntrue;};default:returns->String.valueOf(s).toUpperCase();}}publicConsumercreateConsumerByName(Stringname){switch(name){case"deleteUser":returnuser->{System.out.println("删除用户:"+user.toString());};case"setUser":returnuser->{System.out.println("设置用户信息:"+user.toString());};default:returns->System.out.println(s.toString());}}publicSuppliercreateSupplierByName(Stringname){switch(name){case"user":return()->newUser("add user");default:return()->"hello world";}}}函数工厂(FunctionFactory.java):说白了就是定义了返回函数对象的方法的一个类
packagecom.mrhan.functions;importorg.springframework.stereotype.Component;importjava.util.function.Function;@ComponentpublicclassFunctionFactory{publicFunctionaddPrefix(Stringprefix){returnstr->prefix+str;}publicFunctionaddSuffix(Stringsuffix){returnstr->str+suffix;}}接受多参数的函数工厂(MultArgsFunctionFactory.java):定义了接受多个参数的函数式接口(function和consumer型)及返回此类函数的方法
packagecom.mrhan.functions;importcom.mrhan.entity.User;importorg.springframework.context.annotation.Bean;importorg.springframework.stereotype.Component;importjava.util.function.BiFunction;/** * @author Mr han * @date 2025/12/14 * @description 自定义的多参数函数 */@ComponentpublicclassMultArgsFunctionFactory{/** * * @return * @date 2025/12/14 * @description 拼接两个字符串 * */publicBiFunction<String,String,String>concat(){return(str1,str2)->{System.out.println("两个参数的函数的使用,拼接两个字符并进行返回");returnstr1+str2;};}/** * * @return * @date 2025/12/14 * @description 定义接受三个参数的函数bean,构建一个user对象 * */publicThreeArgsFunction<String,String,Integer,User>constructUserWithFunction(){return(username,userId,age)->{System.out.println("三个参数的函数的使用,构建一个user对象");Useruser=newUser();user.setUserId(userId);user.setName(username);user.setAge(age);returnuser;};}/** * * @return * @date 2025/12/14 * @description 传递三个参数构建user对象并更新用户信息 * */publicThreeArgsConsumer<String,String,Integer>updateUserWithConsumer(){return(username,userId,age)->{System.out.println("三个参数的函数的使用,构建一个user对象并更新");Useruser=newUser();user.setUserId(userId);user.setName(username);user.setAge(age);System.out.println("更细用户信息:"+user.toString());};}/** * @author Mr han * @date 2025/12/14 * @description 定义一个接受三个参数的函数 */@FunctionalInterfacepublicinterfaceThreeArgsFunction<T1,T2,T3,R>{Rapply(T1t1,T2t2,T3t3);}/** * @author Mr han * @date 2025/12/14 * @description 定义三个参数五返回值函数式接口 */@FunctionalInterfacepublicinterfaceThreeArgsConsumer<T1,T2,T3>{voidaccept(T1t1,T2t2,T3t3);}}九、业务实现类(service包下)
BusinessServiceWithUseFunction.java:涵盖了在业务中如何使用函数的实例
packagecom.mrhan.service;importcom.mrhan.entity.Order;importcom.mrhan.entity.User;importcom.mrhan.functions.FunctionFactory;importcom.mrhan.functions.MultArgsFunctionFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.cloud.function.context.FunctionCatalog;importorg.springframework.stereotype.Service;importjava.util.Map;importjava.util.function.Function;@ServicepublicclassBusinessServiceWithUseFunction{// 直接使用函数bean@AutowiredprivateFunctiongetUserByUserId;// 通过函数工厂使用bean@AutowiredprivateFunctionFactoryfunctionFactory;// 使用functioncatalog查找所有函数并使用@AutowiredprivateFunctionCatalogfunctionCatalog;// 注册函数式流水线bean@Autowired@Qualifier("orderProcessPipline")privateFunctionorderProcessPipline;// 多参数函数工厂@AutowiredprivateMultArgsFunctionFactorymultArgsFunctionFactory;publicUsergetUserByUserId(StringuserId){return(User)getUserByUserId.apply(userId);}/** * * @return * @date 2025/12/13 * @description 函数工厂方法及andThen的组合使用 * */publicUsergetUserByUserIdWithAddPrefixAndSuffix(StringuserId){FunctionaddPrefix=functionFactory.addPrefix("prefix-");FunctionaddSuffix=functionFactory.addSuffix("-suffix");return(User)addPrefix.andThen(addSuffix).andThen(getUserByUserId).apply(userId);}/** * * @return * @date 2025/12/13 * @description 通过functioncatalog获取多个函数并使用compose将他们组合在一起 * */publicUsergetUserByUppercase(StringuserId){Functionuppercase=functionCatalog.lookup("uppercase");FunctiongetUserByUserId=functionCatalog.lookup("getUserByUserId");// 将转换成大写的userId作为通过userId查找用户信息的函数的变量return(User)getUserByUserId.compose(uppercase).apply(userId);}/** * * @return * @date 2025/12/13 * @description 使用订单函数做订单的流水线处理 * */publicvoiddealOrder(Orderorder){orderProcessPipline.apply(order);}/** * * @return * @date 2025/12/14 * @description 传递三个参数构建函数并使用函数构建返回user对象 * */publicUserconstructUserWithFunction(Stringname,StringuserId,Integerage){returnmultArgsFunctionFactory.constructUserWithFunction().apply(name,userId,age);}/** * * @return * @date 2025/12/14 * @description 传递三个参数并使用自定义函数consumer更新user * */publicvoidupdateUserWithConsumer(Stringname,StringuserId,Integerage){multArgsFunctionFactory.updateUserWithConsumer().accept(name,userId,age);}}FunctiondemoInfoHandler.java:给mvc函数式路由提供的业务处理类
packagecom.mrhan.service;importorg.springframework.stereotype.Component;importorg.springframework.stereotype.Service;importorg.springframework.web.servlet.function.ServerRequest;importorg.springframework.web.servlet.function.ServerResponse;importjava.nio.file.attribute.AttributeView;importjava.util.HashMap;importjava.util.Map;/** * @author Mr han * @date 2025/12/16 * @description 使用mvc函数式处理请求的处理类 */@ComponentpublicclassFunctiondemoInfoHandler{publicServerResponseinfo(ServerRequestrequest){returnServerResponse.ok().body("functiondemo service is ok!");}publicServerResponsegatewayInfo(ServerRequestrequest){Map<String,String>resultmap=newHashMap<>();request.params().forEach((key,value)->resultmap.put(key,value+""));request.headers().asHttpHeaders().forEach((key,value)->resultmap.put(key,value+""));returnServerResponse.ok().body(resultmap);}}十、传统的Controller类(FunctionDemoController,FunctionDemoInfoController)
FunctionDemoController.java:传统的Controller类
packagecom.mrhan.controller;importcom.mrhan.entity.Order;importcom.mrhan.entity.User;importcom.mrhan.service.BusinessServiceWithUseFunction;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/function")publicclassFunctionDemoController{privatestaticfinalLoggerlog=LoggerFactory.getLogger(FunctionDemoController.class);@AutowiredprivateBusinessServiceWithUseFunctionservice;@RequestMapping("/getUserByUserId")publicUsergetUserByUserId(@RequestParam("userId")StringuserId){log.error("=========直接直接函数定义的bean的例子=========");returnservice.getUserByUserId(userId);}@RequestMapping("/getUserByUserIdWithAddPrefixAndSuffix")publicUsergetUserByUserIdWithAddPrefixAndSuffix(@RequestParam("userId")StringuserId){log.error("=========函数工厂方法及andThen的组合使用=========");returnservice.getUserByUserIdWithAddPrefixAndSuffix(userId);}@RequestMapping("/getUserByUppercase")publicUsergetUserByUppercase(@RequestParam("userId")StringuserId){log.error("=========通过functioncatalog获取多个函数并使用compose将他们组合在一起=========");returnservice.getUserByUppercase(userId);}@RequestMapping("/dealOrder")publicvoiddealOrder(){log.error("==========执行流水线式函数处理订单流程===========");Orderorder=newOrder();order.setAmount(200D);order.setDiscount(0.1D);order.setTax(0.2D);order.setVIP(true);service.dealOrder(order);}@RequestMapping("/constructUserWithFunction")publicUserconstructUserWithFunction(@RequestParam("name")Stringname,@RequestParam("userId")StringuserId,@RequestParam("age")Integerage){log.error("==========使用自定义的传递多个参数的功能型函数处理构建User=====");returnservice.constructUserWithFunction(name,userId,age);}@RequestMapping("/updateUserWithConsumer")publicvoidupdateUserWithConsumer(@RequestParam("name")Stringname,@RequestParam("userId")StringuserId,@RequestParam("age")Integerage){log.error("=========使用传递多个参数的consumer函数更新用户信息=========");service.updateUserWithConsumer(name,userId,age);}}FunctionDemoInfoController.java: 此类的请求路径已被mvc函数式路由配置覆盖,请求将不会到达该类的方法中
packagecom.mrhan.controller;importcom.mrhan.service.FunctiondemoInfoHandler;importjakarta.servlet.http.HttpServletRequest;importjakarta.servlet.http.HttpServletResponse;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjava.util.HashMap;importjava.util.Map;/** * @author Mr han * @date 2025/12/16 * @description 此类的同路径方法将,失效,因为已经在mvc函数式路由中已定义路径处理方法 * @see {@link com.mrhan.config.RouteConfig#customRoutes(FunctiondemoInfoHandler)} */@RestController@RequestMapping("")publicclassFunctionDemoInfoController{/** * * @return * @date 2025/12/16 * @description 此方法无效,已经被mvc函数式路由配置覆盖 * */@GetMapping("/info")publicStringinfo(){return"function demo is ok!";}/** * * @return * @date 2025/12/16 * @description 此方法无效,已经被mvc函数式路由配置覆盖 * */@GetMapping("/gatewayInfo")publicMap<String,String>gatewayInfo(HttpServletRequestrequest){Map<String,String>resultmap=newHashMap<>();request.getParameterNames().asIterator().forEachRemaining(parameterName->{resultmap.put(parameterName,request.getParameter(parameterName)+"");});request.getHeaderNames().asIterator().forEachRemaining(headerName->{resultmap.put(headerName,request.getHeader(headerName)+"");});returnresultmap;}}十一、启动与接口访问(使用postman)
启动:直接按普通spring boot项目找到xxxApplicaton.java的main方法启动即可,不详述,提示:访问时特地选了最后的请求路径都为getUserByUserId的路径发起访问,注意观察传统controller方法和函数式方法的区别。
对定义的传统controller接口的访问(getUserByUserId
对函数(getUserByUserId)的访问(函数即请求方法,函数体即请求方法的业务处理逻辑,函数(Bean)名即请求路径)
对通过mvc函数式路由配置的方法的访问(等效于controller)
提示:可以观察到不会访问到同路径下的controller方法中,只会到路径对应的函数式路由方法中
十二、一些疑问与困惑的澄清
- 定义了函数并注册为bean如何访问?
解析:不用定义任何的controller或配置函数式的mvc路由,直接以函数Bean名为请求路径访问,例如,访问本案例的getUserByUserId函数路径为:http:.//localhost:8082/functionservlettpath/functionwebpath/getUserByUserId/userxxjf23 - 将函数定义为Bean后怎么从spring容器中取该函数Bean并使用?
解析:使用@Autowired依赖注入org.springframework.cloud.function.context.FunctionCatalog,通过lookup方法获取函数Bean,参见文档中BusinessServiceWithUseFunction.java获取函数Bean及对函数Bean的使用。jdk基础的Funciton,Consumer,Supplier三类函数式接口的使用也就是获取到的Bean的使用。 - 定义了函数后可以怎么用?
解析:有三种使用方式,如下。
方式一:函数分类后,直接参照jdk基础的Funciton,Consumer,Supplier三类函数式接口的使用方法,传入或不传入参数调用即可(类似于匿名类方法的调用)
方式二:在spring functon环境中注册为函数Bean,像普通的对象Bean一样被依赖注入使用到业务处理中
方式三:在spring function web环境中注册为Bean的同时暴露该方法供请求调用提示:方式一、方式二见本文BusinessServiceWithUseFunction.java,方式三见本文applicaton.yml及ApplicationFunctionConfig.java - 是否可以在传统的controller或业务类中使用函数(不是函数Bean)?
解析:可以,就当调用匿名类方法一样的使用,见BusinessServiceWithUseFunction.java - 使用了spring cloud function web模块有哪些暴露方法给外部发送请求访问的方式?
解析:有三种方式:定义传统的controller类方法,定义函数为Bean,使用mvc函数式路由定义路径及对应的处理类 - 函数式编程和面向对象编程有啥区别?怎么理解?
解析:函数式编程本质上是面向对象编程,函数式编程中的函数实际上是一个简化的实现了函数式接口的匿名类对象,该函数编写时仅仅有简略的重写的方法定义格式(接口要求的N个传入参数或者无参数),而在方法体中实现无返回值或有返回值。进一步理解,则可知Function,Consumer,Supplier三类函数式接口,就是一个接口,固定了一个你必须实现的方法(Function.apply,Consumer.accept,Supplier.get)然后你美其名说使用这些进行函数式编程,实际上就是调用你实现了它们的匿名类对象的重写的方法(Function.apply,Consumer.accept,Supplier.get),只不过这个方法的传入参数类型,返回参数类型可以泛化,可以延申拓展为任意多参数任意类型。总结:函数式编程=创建接口的匿名实现类对象并执行重写的方法,以函数(Function,Consumer,Supplier)为传入参数=传入参数是你需要创建的接口(Function,Consumer,Supplier)的匿名实现类对象,同理可得传递、返回参数为函数场景中涉及的函数就是匿名实现类对象。lambok语法中的一串传入的就是一个个的匿名实现类对象,你自己实现了它要调用的方法。无论lambok多么复杂你只要关注传入了什么参数(类型),是否需要返回,返回的是什么参数(类型),在方法体中写逻辑最后加上返回或无返回结束函数,而最终结果就是你创建了一个(Function,Consumer,Supplier)接口的匿名实现类对象作为参数给需要这个对象的方法。
总结
拿来即用的干货,不白跑,不浪费你宝贵时间,关注我不迷路!始终在路上,不停歇!