Detailed configuration and webflux webmvc

webmvc and webflux as two important modules spring framework, representing the two IO models , blocking and non-blocking.

webmvc blocking the servlet based model (generally called OIO), after a request arrives at a server will assign a separate thread to process the request, if the request contains IO operations, IO operation before the end of the thread has been waiting in a blocking state, so that the thread IO wait for the end of the operation time is wasted.

webflux reactor model is based on non-blocking (generally referred to as NiO), similarly, the request will arrive after the allocation of a server thread to process the request, if the request contains IO operations, IO operation before the end of the thread is no longer blocked in a waiting state, but to deal with other things, wait until after the end of the IO operation, and then notice (thanks to the mechanism system) thread to continue processing the request.

This thread effective use of the time consumed by IO operations.

This paper make a comparison on webmvc and webflux two frames. Interested can look at the original official documents make reference web servlet and Web reactive .

server configuration

webmvc

@Configuration
@EnableWebMvc		//使用注解@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {		//继承WebMvcConfigurer
	//配置静态资源
	@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
        registry.addResourceHandler("/file/**")
                .addResourceLocations("file:" + System.getProperty("user.dir") + File.separator + "file" + File.separator);
        registry.addResourceHandler("/swagger-ui.html**")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
	//配置拦截器
	//配置编解码
	...
}

webflux

@Configuration
@EnableWebFlux		//使用注解@EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer {		//继承WebFluxConfigurer 
	//配置静态资源
	@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
        registry.addResourceHandler("/file/**")
                .addResourceLocations("file:" + System.getProperty("user.dir") + File.separator + "file" + File.separator);
        registry.addResourceHandler("/swagger-ui.html**")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
	//配置拦截器
	//配置编解码
	...
}

Authentication Configuration

webmvc- authentication data depends on the user service needs to be defined to achieve the UserDetailsService Bean

@Configuration
@EnableWebSecurity
public class WebMvcSecurityConfig extends WebSecurityConfigurerAdapter implements 
AuthenticationEntryPoint,		//未验证回调
AuthenticationSuccessHandler,		//验证成功回调
AuthenticationFailureHandler,		//验证失败回调
LogoutSuccessHandler {		//登出成功回调

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        sendJson(response, new Response<>(HttpStatus.UNAUTHORIZED.value(), "Unauthorized"));
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        sendJson(response, new Response<>(1, "Incorrect"));
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        sendJson(response, new Response<>(0, authentication.getClass().getSimpleName()));
    }

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        sendJson(response, new Response<>(0, "Success"));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .authorizeRequests()
                .antMatchers("/swagger*/**", "/webjars/**", "/v2/api-docs")
                .permitAll()
                .and()
                .authorizeRequests()
                .antMatchers("/static/**", "/file/**")
                .permitAll()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .logout()
                .logoutUrl("/user/logout")		//虚拟路径,不是控制器定义的路径
                .logoutSuccessHandler(this)
                .permitAll()
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(this)
                .and()
                .formLogin()
                .usernameParameter("username")
                .passwordParameter("password")
                .loginProcessingUrl("/user/login")		//虚拟路径,不是控制器定义的路径
                .successForwardUrl("/user/login")		//是控制器定义的路径
                .failureHandler(this)
                .and()
                .httpBasic()
                .authenticationEntryPoint(this);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService);
    }

webflux- authentication data depends on the user service needs to be defined to achieve the Bean ReactiveUserDetailsService

@Configuration
@EnableWebFluxSecurity		//使用注解@EnableWebFluxSecurity
public class WebFluxSecurityConfig implements 
WebFilter,		//拦截器
ServerLogoutSuccessHandler,		//登出成功回调
ServerAuthenticationEntryPoint,		//验证入口
ServerAuthenticationFailureHandler,		//验证成功回调 
ServerAuthenticationSuccessHandler {		//验证失败回调
	//实现接口的方法
	@Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    	//配置webflux的context-path
        ServerHttpRequest request = exchange.getRequest();
        if (request.getURI().getPath().startsWith(contextPath)) {
            exchange = exchange.mutate().request(request.mutate().contextPath(contextPath).build()).build();
        }
        //把查询参数转移到FormData中,不然验证过滤器(ServerFormLoginAuthenticationConverter)接受不到参数
        if (exchange.getRequest().getMethod() == HttpMethod.POST && exchange.getRequest().getQueryParams().size() > 0) {
            ServerWebExchange finalExchange = exchange;
            ServerWebExchange realExchange = new Decorator(exchange) {
                @Override
                public Mono<MultiValueMap<String, String>> getFormData() {
                    return super.getFormData().map(new Function<MultiValueMap<String, String>, MultiValueMap<String, String>>() {
                        @Override
                        public MultiValueMap<String, String> apply(MultiValueMap<String, String> stringStringMultiValueMap) {
                            if (stringStringMultiValueMap.size() == 0) {
                                return finalExchange.getRequest().getQueryParams();
                            } else {
                                return stringStringMultiValueMap;
                            }
                        }
                    });
                }
            };
            return chain.filter(realExchange);
        }
        return chain.filter(exchange);
    }
    
	@Override
    public Mono<Void> onLogoutSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
        return sendJson(webFilterExchange.getExchange(), new Response<>("登出成功"));
    }

    @Override
    public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
        return sendJson(exchange, new Response<>(HttpStatus.UNAUTHORIZED.value(), "未验证"));
    }

    @Override
    public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
        return sendJson(webFilterExchange.getExchange(), new Response<>(1, "验证失败"));
    }

    @Override
    public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
        return webFilterExchange.getChain().filter(
                webFilterExchange.getExchange().mutate()
                        .request(t -> t.method(HttpMethod.POST).path("/user/login"))		//转发到自定义控制器
                        .build()
        );
    }
    
	@Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.addFilterAfter(this, SecurityWebFiltersOrder.FIRST)
                .csrf().disable()
                .authorizeExchange()
                .pathMatchers("/swagger*/**", "/webjars/**", "/v2/api-docs")		//swagger
                .permitAll()
                .and()
                .authorizeExchange()
                .pathMatchers("/static/**", "/file/**")		//静态资源
                .permitAll()
                .and()
                .authorizeExchange()
                .anyExchange()
                .authenticated()
                .and()
                .logout()		//登出
                .logoutUrl("/user/logout")
                .logoutSuccessHandler(this)
                .and()
                .exceptionHandling()		//未验证回调
                .authenticationEntryPoint(this)
                .and()
                .formLogin()
                .loginPage("/user/login")
                .authenticationFailureHandler(this)		//验证失败回调
                .authenticationSuccessHandler(this)		//验证成功回调
                .and()
                .httpBasic()
                .authenticationEntryPoint(this);		//basic验证,一般用于移动端
        return http.build();
    }
}

Session Configuration

webmvc

@Configuration
@EnableRedisHttpSession	//使用注解@EnableRedisHttpSession	
public class RedisHttpSessionConfig { //考虑到分布式系统,一般使用redis存储session

  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {
    return new LettuceConnectionFactory();
  }

}
//单点登录使用FindByIndexNameSessionRepository根据用户名查询session,删除其他session即可
Map<String, Session> map = findByIndexNameSessionRepository.findByPrincipalName(name);

webflux

@Configuration
@EnableRedisWebSession //使用注解@EnableRedisWebSession 
public class RedisWebSessionConfig { //考虑到分布式系统,一般使用redis存储session

    @Bean
    public LettuceConnectionFactory lettuceConnectionFactory() {
        return new LettuceConnectionFactory();
    }

}
//单点登录使用ReactiveRedisSessionRepository.getSessionRedisOperations().scan方法查询相同用户名的session,删除其他session即可
public Mono<Map<String, String>> findByPrincipalName(String name) {
        return reactiveSessionRepository.getSessionRedisOperations().scan(ScanOptions.scanOptions().match(ReactiveRedisSessionRepository.DEFAULT_NAMESPACE + ":sessions:*").build())
                .flatMap(new Function<String, Publisher<Tuple2<String, Map.Entry<Object, Object>>>>() {
                    @Override
                    public Publisher<Tuple2<String, Map.Entry<Object, Object>>> apply(String s) {
                        return reactiveSessionRepository.getSessionRedisOperations().opsForHash().entries(s)
                                .map(new Function<Map.Entry<Object, Object>, Tuple2<String, Map.Entry<Object, Object>>>() {
                                    @Override
                                    public Tuple2<String, Map.Entry<Object, Object>> apply(Map.Entry<Object, Object> objectObjectEntry) {
                                        return Tuples.of(s, objectObjectEntry);
                                    }
                                });
                    }
                })
                .filter(new Predicate<Tuple2<String, Map.Entry<Object, Object>>>() {
                    @Override
                    public boolean test(Tuple2<String, Map.Entry<Object, Object>> rule) {
                        Map.Entry<Object, Object> t = rule.getT2();
                        String key = "sessionAttr:" + HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
                        if (key.equals(t.getKey())) {
                            User sci = (User) ((SecurityContextImpl) t.getValue()).getAuthentication().getPrincipal();
                            return sci.getUsername().equals(name);
                        }
                        return false;
                    }
                })
                .collectMap(new Function<Tuple2<String, Map.Entry<Object, Object>>, String>() {
                    @Override
                    public String apply(Tuple2<String, Map.Entry<Object, Object>> rule) {
                        return name;
                    }
                }, new Function<Tuple2<String, Map.Entry<Object, Object>>, String>() {
                    @Override
                    public String apply(Tuple2<String, Map.Entry<Object, Object>> rule) {
                        return rule.getT1().replace(ReactiveRedisSessionRepository.DEFAULT_NAMESPACE + ":sessions:", "");
                    }
                });
    }

swagger Configuration

webmvc

//依赖包
implementation 'io.springfox:springfox-swagger2:2.9.2'
implementation 'io.springfox:springfox-swagger-ui:2.9.2'
//配置
@Configuration
@EnableSwagger2		//使用注解
public class SwaggerConfig {
    @Bean
    public Docket  docket () {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        .title("*****")
                        .description("******")
                        .version("0.0.1")
                        .build())
                .select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build();
    }
}
//参数上传
//定义参数bean
@Setter
@Getter
@ToString
@ApiModel
public class QueryBean{
    @ApiModelProperty(value = "普通参数", required = false, example = "")
    private String query;
    @ApiModelProperty(value = "文件参数", required = false, example = "")
    private MultipartFile image;		//webmvc中使用MultipartFile作为接收文件的类型
}
//定义接口
@ApiOperation("一个接口")
@PostMapping("/path")
//这里需要使用@ApiImplicitParam显示配置【文件参数】才能使swagger界面显示上传文件按钮
@ApiImplicitParams({
	@ApiImplicitParam(
		paramType = "form", //表单参数
		dataType = "__file", //最新版本使用__file表示文件,以前用的是file
		name = "image", //和QueryBean里面的【文件参数image】同名
		value = "文件")	//注释
})
public Mono<Response> bannerAddOrUpdate(QueryBean q) {

}

webflux- to little more trouble, not yet released, only snapshot version, but the test is available

//仓库
maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }
//依赖
implementation 'io.springfox:springfox-swagger2:3.0.0-SNAPSHOT'
implementation 'io.springfox:springfox-swagger-ui:3.0.0-SNAPSHOT'
implementation 'io.springfox:springfox-spring-webflux:3.0.0-SNAPSHOT'
//配置
@Configuration
@EnableSwagger2WebFlux		//使用注解
public class SwaggerConfig {
    @Bean
    public Docket docket() {
        Set<String> consumes = new HashSet<>();
        consumes.add(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
        Set<String> produces = new HashSet<>();
        produces.add(MediaType.APPLICATION_JSON_VALUE);
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        .title("*****")
                        .description("*****")
                        .version("0.0.1")
                        .build())
                .pathMapping("/context-path")  //注意webflux没有context-path配置,如果不加这句话的话,接口测试时路径没有前缀
                .consumes(consumes)
                .produces(produces)
                .select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build();
    }
}

//参数上传
//定义参数bean
@Setter
@Getter
@ToString
@ApiModel
public class QueryBean{
    @ApiModelProperty(value = "普通参数", required = false, example = "")
    private String query;
    @ApiModelProperty(value = "文件参数", required = false, example = "")
    private FilePart image;		//强调,webflux中使用FilePart作为接收文件的类型
}
//定义接口
@ApiOperation("一个接口")
@PostMapping("/path")
//这里需要使用@ApiImplicitParam显示配置【文件参数】才能使swagger界面显示上传文件按钮
@ApiImplicitParams({
	@ApiImplicitParam(
		paramType = "form", //表单参数
		dataType = "__file", //最新版本使用__file表示文件,以前用的是file
		name = "image", //和QueryBean里面的【文件参数image】同名
		value = "文件")	//注释
})
public Mono<Response> bannerAddOrUpdate(QueryBean q) {

}

These are the contrast webmvc and webflux, thank you.

Published 21 original articles · won praise 0 · Views 832

Guess you like

Origin blog.csdn.net/ssehs/article/details/103643369