话不多说 开搞。
项目接上回搭建的webflux项目。传送门:IDEA 搭建一个spring boot2 webflux项目
项目为jdk10+ spring boot2 +mongodb
springboot项目创建好之后默认的resources里面的文件名为:application.properties
方便我们使用,我们改成application.yml
spring:
data:
mongodb:
uri: mongodb://localhost:27017/webflux
当启动时,会自动在你安装的mongodb中建立一个webflux数据库,mongodb默认端口号27017。
/**
* 启动类
*/
@EnableReactiveMongoRepositories//开启流mongodb
@SpringBootApplication(scanBasePackages = {"com.example.demo"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
注意是@EnableReactiveMongoRepositories,Reactive mongodb
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
@Document(collection = "user")//指定在mongodb中的表名
@Data //lombok注解
public class User {
@Id //主键
private String id;
@NotBlank //校验框架 不能为空
private String name;
@Max(150)@Min(1) //最大值最小值
private Integer age;
}
import com.example.demo.domain.User;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends ReactiveMongoRepository {
}
还是注意reactivemongodb。
跟其他JPA差不多,可以使用如findById这类的取名直接生成方法方式也可以使用@Query来自己写查询。这里不多说了。
import com.example.demo.domain.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
/**
*user处理器
*/
@Component //交给spring管理的
public class UserHandler {
@Autowired //注入JPA层
private UserRepository repository;
public Mono findAll(ServerRequest request){
return ServerResponse.ok().contentType(MediaType.TEXT_EVENT_STREAM)
.body(repository.findAll(), User.class);
}
public Mono save(ServerRequest request){
//这里MediaType.TEXT_EVENT_STREAM设置的是返回值的格式
return ServerResponse.ok().contentType(MediaType.TEXT_EVENT_STREAM)
.body(repository.saveAll(request.bodyToMono(User.class)),User.class);
}
}
这里我们看一下ServerResponse,它里面提供了很多静态方法还有内部接口,很多操作他通过自己就可以来完成。
如果你想返回成功,那么直接调用他的静态方法,ok()
static BodyBuilder ok() {
return status(HttpStatus.OK);
}
方式一个http的状态码, HttpStatus是一个枚举,它里面提供了所有的httpcode,200,400,404,500......
OK(200, "OK"),
所以通过这种方式我们直接设置了response中的返回码为200。
其他方法也比较简单,就不多列举了,有的我也不会。。哈哈。。
但是注意一下,ok()方法的返回值是BodyBuilder ,他是ServerResponse中的一个接口,用来构建response的。
包括contentType()也是,他们都不能满足返回值要求。所以在构建最后要使用BodyBuilder接口的body、syncbody、render
方法来返回Mono
还有值得注意的是ServerRequest和ServerResponse。他们是reactive中的非阻塞的req和resp。用法和之前大家熟悉的HttpServletRequest有区别。
而且在写代码的时候,要时刻关注着不能让程序阻塞。不然就失去了响应式编程的意义。
关于为什么参数列表是ServerRequest ,返回值是Mono
至于Mono和Flux,我想有时间了写一个单独的博客。
目前简单说,Mono代表返回0或者1个元素;Flux表示返回0或者N个元素。替换原来MVC开发模式中的直接返回entity和list等。
我觉得最大的改变就是使用使用路由器替代原先的控制器。
import com.example.demo.handlers.UserHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
/**
* 路由器
*/
@Configuration
public class UserRouters {
@Bean
public RouterFunction router(UserHandler handler) {
return RouterFunctions.nest(RequestPredicates.path("/u"),
RouterFunctions
.route(RequestPredicates.GET("/"),handler::findAll)
.andRoute(RequestPredicates.POST("/").
and(RequestPredicates.accept(MediaType.APPLICATION_JSON_UTF8)), handler::save));
}
}
上面例子来源于RouterFunctions.nest的注解。略加修改。
webflux注册了一个以RouterFunction
当系统发现存在该返回值得Bean时,就会将这个Bean注册为一个路由用来处理请求。
RouterFunction是一个函数式接口,输入一个ServerRequest,返回一个Mono
RouterFunctions提供了实现RouterFunction的一些方法。所以这里我们使用RouterFunctions来进行操作。
public static RouterFunction nest(
RequestPredicate predicate, RouterFunction routerFunction) {
return new DefaultNestedRouterFunction<>(predicate, routerFunction);
}
nest方法输入一个RequestPredicate,一个RouterFunction。
RequestPredicate是去请求的地址,同样是一个函数式接口,并且也给他提供了一个工具类RequestPredicates。
RequestPredicates.path() 代表请求路径
RequestPredicates.GET() 代表请求是一个get请求
RequestPredicates.POST() 代表请求是一个POST请求
其实看一下源码,以GET为例
public static RequestPredicate GET(String pattern) {
return method(HttpMethod.GET).and(path(pattern));
}
path
public static RequestPredicate path(String pattern) {
Assert.notNull(pattern, "'pattern' must not be null");
return pathPredicates(DEFAULT_PATTERN_PARSER).apply(pattern);
}
GET内部还是调用的path方法,只不过在前面加上了get。
一般常用的put,get,post,delete,patch啥的都已经提供了,所以我们直接使用相应的请求即可。真贴心。
第二个参数还是RouterFunctions。
所以这个方法,可以视为将这个方法内的所有请求路径中一样的部分抽取出来,作为父路径。
再看一下RouterFunctions..route方法。
public static RouterFunction route(
RequestPredicate predicate, HandlerFunction handlerFunction) {
return new DefaultRouterFunction<>(predicate, handlerFunction);
}
输入一个RequestPredicate和一个HandlerFunction
再看一下HandlerFunction
@FunctionalInterface
public interface HandlerFunction {
/**
* Handle the given request.
* @param request the request to handle
* @return the response
*/
Mono handle(ServerRequest request);
}
这也是一个函数式接口,输入一个ServerRequest,输出一个Mono
这就又回到了刚才我们自己写的handler
public Mono findAll(ServerRequest request){
return ServerResponse.ok().contentType(MediaType.TEXT_EVENT_STREAM)
.body(repository.findAll(), User.class);
}
我们写的handler正好就是handlerFunction的实现。’
到这这一切算是联系上了。。我写的有点乱,凑合着看吧。
现在项目写完了。启动验证一下。
好了,就到这吧。如果有什么写的不好的地方,请指教。