Spring Security原理篇(三) HttpSecurity

1.初始化HttpSecurity对象

从前面的文章中,我们已经提到在WebSecurityConfigurerAdapter的初始化方法init()中,通过getHttp()方法获取到了HttpSecurity的对象,我们再来看一下init()这个方法的源代码

    /**
     * @param web
     * @throws Exception
     */
    public void init(final WebSecurity web) throws Exception {
        final HttpSecurity http = getHttp();
        web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
            public void run() {
                FilterSecurityInterceptor securityInterceptor = http
                        .getSharedObject(FilterSecurityInterceptor.class);
                web.securityInterceptor(securityInterceptor);
            }
        });
    }

这个方法的具体作用已经在上一篇文章中说过,先构建HttpSecurity对象,然后通过WebSecurity对象的addSecurityFilterChainBuilder()方法添加到securityFilterChainBuilders的List中,最后用来组件过滤器链。

1.1 WebSecurityConfigurerAdaptergetHttp()方法

  • 首先我们还是引入一下这个方法的源代码
protected final HttpSecurity getHttp() throws Exception {
        //如果已经存在HttpSecurity 对象,则返回
        if (http != null) {
            return http;
        }
        //这里主要还是关于异常的一些处理,这里我们最后的文章统一再说,先给自己留个坑,先猜测一下吧
        DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
                .postProcess(new DefaultAuthenticationEventPublisher());
    localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
           //构建AuthenticationManager对象,这个对象管理认证,后面我们再说
        AuthenticationManager authenticationManager = authenticationManager();
        authenticationBuilder.parentAuthenticationManager(authenticationManager);

            //创建共享对象
        Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
       
        //构建HttpSecurity 需要用到authenticationBuilder,sharedObjects
        http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
                sharedObjects);

        //允许默认配置的时候
        if (!disableDefaults) {
            // @formatter:off
            http
                .csrf().and()
                .addFilter(new WebAsyncManagerIntegrationFilter())
                .exceptionHandling().and()
                .headers().and()
                .sessionManagement().and()
                .securityContext().and()
                .requestCache().and()
                .anonymous().and()
                .servletApi().and()
                .apply(new DefaultLoginPageConfigurer<>()).and()
                .logout();
            // @formatter:on
            ClassLoader classLoader = this.context.getClassLoader();
            List<AbstractHttpConfigurer> defaultHttpConfigurers =
                    SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

            for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
                http.apply(configurer);
            }
        }

        //??configure??
        configure(http);
        return http;
    }
  • 我们可以从上面的代码中可以知道,因为HttpSecurity构造函数需要AuthenticationManagerBuildersharedObjects 对象,上面的代码先创建AuthenticationManagerBuilder的对象,然后填充了共享对象的map,然后调用HttpSecueity的构造函数构造出来一个HttpSecurity的对象,然后configure(http),这个方法最后再讲,关于AuthenticationManagerBuilder我们到用户认证的时候再去专门讲,而sharedObjects我们也需要专门篇幅来将讲解,这些看似细节又复杂的东西,怕混乱到影响这篇文章重点需要讲解的HttpSecurity

1.2 HttpSecurity 类的大体理解

1.2.1 HttpSecurity的类图

HttpSecurity类图
  • 在说Filter的时候我们说到过WebSecuritybuild()方法的时候有很具体的说到AbastractSecurityBuilderAbstractConfiguredSecurityBuilder这两个类中build()方法执行的过程我们看一下HttpSecurity类的定义
public final class HttpSecurity extends
        AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
        implements SecurityBuilder<DefaultSecurityFilterChain>,
        HttpSecurityBuilder<HttpSecurity> 

我们可以知道调用HttpSecuritybuild()方法的时候返回的就是DefaultSecurityFilterChain的对象,当然具体的还要看HttpSecurityperformBuild()方法

  • HttpSecurityBuilder接口

1.2.2HttpSecurity的属性

     //从变量名就可以看到是请求匹配过滤的配置信息    
    private final RequestMatcherConfigurer requestMatcherConfigurer;

    //过滤器列表?
    private List<Filter> filters = new ArrayList<>();

    //匹配任何请求的匹配器
    private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;

    //过滤器比较.....啥啥啥不知道
    private FilterComparator comparator = new FilterComparator();
  • 带着疑问,我们还是要看先了解一下这些属性的值是怎么来的,先挑软柿子捏吧
  • 1.requestMatcher可以完整的看一下 AnyRequestMatcher的定义
public final class AnyRequestMatcher implements RequestMatcher {
    public static final RequestMatcher INSTANCE = new AnyRequestMatcher();

    public boolean matches(HttpServletRequest request) {
        //直接return true?那就是说有请求都匹配
        return true;
    }

    @Override
    @SuppressWarnings("deprecation")
    public boolean equals(Object obj) {
        return obj instanceof AnyRequestMatcher
                || obj instanceof org.springframework.security.web.util.matcher.AnyRequestMatcher;
    }

    @Override
    public int hashCode() {
        return 1;
    }

    @Override
    public String toString() {
        return "any request";
    }

    private AnyRequestMatcher() {
    }
}

这个类继承的接口只有matches方法,方法的注释上写的很明白,如果匹配规则就返回true,否则返回false,AnyRequestMatcher永远返回true,说明匹配任何请求。如果需要查看接口定义,可以自行查看类RequestMatcher的源代码,因为简单,节省空间。

*2.comparatorFilterComparator的对象。然而Filter比较器类的定义也是比较简单的,此处还是不引入源代码了,因为后面我们还要讲解,里面很重要的定义了一些我们不知不觉用着的东西。FilterComparator实现Comparator接口,作为比较器,我们只看一下他的一个方法就可以了

public int compare(Filter lhs, Filter rhs) {
    Integer left = getOrder(lhs.getClass());
    Integer right = getOrder(rhs.getClass());
    return left - right;
}

这个getOrder就是Filter上定义Order的数字,还有一种形式就是addFilter这个的。反正就是获取到在过滤器列表中的顺序,不过当然不是连续的顺序

  • 3.filters的list 只是通过addFilter()方法添加进来,放一下源代码,不解释了
    public HttpSecurity addFilter(Filter filter) {
        Class<? extends Filter> filterClass = filter.getClass();
        if (!comparator.isRegistered(filterClass)) {
            throw new IllegalArgumentException(
                    "The Filter class "
                            + filterClass.getName()
                            + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
        }
        this.filters.add(filter);
        return this;
    }

1.2.3 HttpSecurity的部分方法

  • 1.构造方法
public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor,
            AuthenticationManagerBuilder authenticationBuilder,
            Map<Class<? extends Object>, Object> sharedObjects) {
        super(objectPostProcessor);
        Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
        setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
        for (Map.Entry<Class<? extends Object>, Object> entry : sharedObjects
                .entrySet()) {
            setSharedObject((Class<Object>) entry.getKey(), entry.getValue());
        }
        ApplicationContext context = (ApplicationContext) sharedObjects
                .get(ApplicationContext.class);
        this.requestMatcherConfigurer = new RequestMatcherConfigurer(context);
    }

除了设置了共享对象之外,唯一值得一提的就是ApplicationContext 作为共享对象传递进来了,哈哈,毕竟spring security再牛逼,再spring面前还是得装装小媳妇的。至于requestMatcherConfigurer还是要在稍后的篇幅重点讲一下的,毕竟太重要了,不论对于我们使用还是要理解这个过程,都不可获取。

  • performBuild()方法,上面已经提到了,主要是为了构建过滤器链,还是看一下源代码吧
protected DefaultSecurityFilterChain performBuild() throws Exception {
        Collections.sort(filters, comparator);
        return new DefaultSecurityFilterChain(requestMatcher, filters);
}
  • 对过滤器进行排序,然后返回了创建的DefaultSecurityFilterChain对象

1.2.4 HttpSecurity实现HttpSecurityBuilder的方法

  • authenticationProvider()方法
    这个方法不准备列举源代码了,就是提供一个设置AuthenticationProvider的方法,至于为什么要说一下,就是为了支出HttpSecurity可以设置AuthenticationProvider至于说AuthenticationProvider有什么用,以后具体说,但是这里还是想先大概说一下,他是具体的用户验证的方式,比如用户名密码形式,邮箱密码形式,短信验证码形式的登录等等吧。
  • userDetailsService()方法
    设置userDetailsService()后面也会说到,主要有一个方法根据username去获取用户信息。然后根据获取到的用户比对密码是否正确的。后面再说吧,先简单提一下
  • addFilterAfter()方法
public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
    comparator.registerAfter(filter.getClass(), afterFilter);
    return addFilter(filter);
}
  • 先注册到过滤器比较器里面,因为要排序

  • 然以后添加到过滤器列表中

  • addFilterBefore方法,参考addFilterAfter()方法吧,差不多

  • addFilter方法 自动排序的过滤器,但是从注释上我们必须认识到,添加的过滤器必须继承自一下的某一个过滤器

* <ul>
     * <li>{@link ChannelProcessingFilter}</li>
     * <li>{@link ConcurrentSessionFilter}</li>
     * <li>{@link SecurityContextPersistenceFilter}</li>
     * <li>{@link LogoutFilter}</li>
     * <li>{@link X509AuthenticationFilter}</li>
     * <li>{@link AbstractPreAuthenticatedProcessingFilter}</li>
     * <li><a href="{@docRoot}/org/springframework/security/cas/web/CasAuthenticationFilter.html">CasAuthenticationFilter</a></li>
     * <li>{@link UsernamePasswordAuthenticationFilter}</li>
     * <li>{@link ConcurrentSessionFilter}</li>
     * <li>{@link OpenIDAuthenticationFilter}</li>
     * <li>{@link org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter}</li>
     * <li>{@link org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter}</li>
     * <li>{@link ConcurrentSessionFilter}</li>
     * <li>{@link DigestAuthenticationFilter}</li>
     * <li>{@link org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter}</li>
     * <li>{@link BasicAuthenticationFilter}</li>
     * <li>{@link RequestCacheAwareFilter}</li>
     * <li>{@link SecurityContextHolderAwareRequestFilter}</li>
     * <li>{@link JaasApiIntegrationFilter}</li>
     * <li>{@link RememberMeAuthenticationFilter}</li>
     * <li>{@link AnonymousAuthenticationFilter}</li>
     * <li>{@link SessionManagementFilter}</li>
     * <li>{@link ExceptionTranslationFilter}</li>
     * <li>{@link FilterSecurityInterceptor}</li>
     * <li>{@link SwitchUserFilter}</li>
     * </ul>

1.2.4 HttpSecurity配置的部分方法

这部分还是参考一下源代码吧,太多太多了,其实在我们使用的时候也可以参考,因为这些方法的注释上都给出了具体的例子,下面简单的看一下吧,就比如formLogin()这个方法

    /**
     *
     * 指定支持基于表单的身份验证. If
     * 若果没有指定loginPage()这个配置,那么将使用默认的登录页面
     *
     * <h2>Example Configurations</h2>
     * 默认的登录的URL为 /login
    
     * <pre>
     * &#064;Configuration
     * &#064;EnableWebSecurity
     * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
     *
     *  &#064;Override
     *  protected void configure(HttpSecurity http) throws Exception {
     *      http.authorizeRequests().antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and().formLogin();
     *  }
     *
     *  &#064;Override
     *  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     *      auth.inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;);
     *  }
     * }
     * </pre>
     *
     * The configuration below demonstrates customizing the defaults.
     *
     * <pre>
     * &#064;Configuration
     * &#064;EnableWebSecurity
     * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
     *
     *  &#064;Override
     *  protected void configure(HttpSecurity http) throws Exception {
     *      http.authorizeRequests().antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and().formLogin()
     *              .usernameParameter(&quot;username&quot;) // default is username
     *              .passwordParameter(&quot;password&quot;) // default is password
     *              .loginPage(&quot;/authentication/login&quot;) // default is /login with an HTTP get
     *              .failureUrl(&quot;/authentication/login?failed&quot;) // default is /login?error
     *              .loginProcessingUrl(&quot;/authentication/login/process&quot;); // default is /login
     *                                                                      // with an HTTP
     *                                                                      // post
     *  }
     *
     *  &#064;Override
     *  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     *      auth.inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;);
     *  }
     * }
     * </pre>
     *
     * @see FormLoginConfigurer#loginPage(String)
     *
     * @return the {@link FormLoginConfigurer} for further customizations
     * @throws Exception
     */
    public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
        return getOrApply(new FormLoginConfigurer<>());
    }

2 从配置到Filter

我们还是需要看一下HttpSecurity的配置最后是怎样影响到我们的过滤器执行的
我们还是用两个简单的例子来看一下工作原理

2.1 formLogin()的原理

2.1.1 formLogin配置的例子

我们也不需要自己手写一个例子出来,然后说一大堆,我们直接可以拿到方法上面注释的例子来看一下就可以了,只是简单的替换掉了转义的字符和一点点英文的注释

 下面的配置演示了自定义默认值。
@Configuration
@EnableWebSecurity
 public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {  
    http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
                .usernameParameter("username") //默认的用户名的参数 username
                .passwordParameter("password") // 默认的密码的参数 password
                .loginPage("/authentication/login") // 默认请求地址 /login with an HTTP get
                .failureUrl("/authentication/login?failed") //默认失败地址 /login?error
                .loginProcessingUrl("/authentication/login/process"); // default is /login                                                                      // with an HTTP
                                                                        // post
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
    }
 }

上面的例子做了一下几件事情

  • 在内存中创建了一个用户,用户名user,密码password,拥有角色USER
  • 配置了任何请求的用户都必须拥有USER角色
  • 配置了登录请求的地址为/authentication/login,失败的地址为/authentication/login?failed,用户名提交的参数为username,密码提交的参数为password,虽然用户名和密码的参数默认也是这样,但是我们也知道这里可以自定义

2.1.2 formLogin背后的实现原理

  • 我们看一下在HttpSecurity中formLogin()的方法
//formlogin()方法
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
    //T1这里的FormLoginConfigurer和getOrApply方法我们都不知道是啥玩意儿
    return getOrApply(new FormLoginConfigurer<>());
}

//getOrApply()方法
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
            C configurer) throws Exception {
        //T2 这里的意思是去查看是否有存在了
        C existingConfig = (C) getConfigurer(configurer.getClass());
        if (existingConfig != null) {
            return existingConfig;
        }
           //T3 不存在的情况下就去调用apply()这个方法
        return apply(configurer);
    }

//apply()方法
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
            throws Exception {
         //T4
        configurer.addObjectPostProcessor(objectPostProcessor);
        //T5    
        configurer.setBuilder((B) this);
        //T6    
        add(configurer);
        return configurer;
    }
  • T1 这里的FormLoginConfigurer后面下面说
  • T2 这里的C泛型指的就是FormLoginConfigurergetConfigurer()方法的源代码自行查看,这个方法大概的意图就是LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers在这个map中如果存在这个FormLoginConfigurer的配置就返回,否则返回null
  • T4 这里添加一个后置处理器,先不说
  • T5 设置this到builder里面,后面会有用
  • T6 将FormLoginConfigurer添加到LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers的map中

2.1.3 FormLoginConfigurer

  • 类图


    FormLoginConfigurer类图
  • 我们看一下构造方法

public FormLoginConfigurer() {
        super(new UsernamePasswordAuthenticationFilter(), null);
        usernameParameter("username");
        passwordParameter("password");
    }

调用父类构造方法的时候创建了UsernamePasswordAuthenticationFilter,然后复制给了一个叫做authFilter的变量,所以我们知道,其实在创建FormLoginConfigurer的时候,他自己就已经有了一个叫做UsernamePasswordAuthenticationFilter

  • FormLoginConfigurerFilter转换的configure()方法
@Override
    public void configure(B http) throws Exception {
        PortMapper portMapper = http.getSharedObject(PortMapper.class);
        if (portMapper != null) {
            authenticationEntryPoint.setPortMapper(portMapper);
        }

        RequestCache requestCache = http.getSharedObject(RequestCache.class);
        if (requestCache != null) {
            this.defaultSuccessHandler.setRequestCache(requestCache);
        }

        authFilter.setAuthenticationManager(http
                .getSharedObject(AuthenticationManager.class));
        authFilter.setAuthenticationSuccessHandler(successHandler);
        authFilter.setAuthenticationFailureHandler(failureHandler);
        if (authenticationDetailsSource != null) {
            authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
        }
        SessionAuthenticationStrategy sessionAuthenticationStrategy = http
                .getSharedObject(SessionAuthenticationStrategy.class);
        if (sessionAuthenticationStrategy != null) {
            authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
        }
        RememberMeServices rememberMeServices = http
                .getSharedObject(RememberMeServices.class);
        if (rememberMeServices != null) {
            authFilter.setRememberMeServices(rememberMeServices);
        }
        F filter = postProcess(authFilter);
        http.addFilter(filter);
    }

这段代码大部分的都是在设置一些Filter执行需要的属性。这个和具体的这个配置到底是完成什么样的功能有关系,然后我们最需要关心的只有下面这行代码

http.addFilter(filter);

也就是说最后往HttpSecurity的List<Filter> filters列表中添加了一个Filter对象

  • 触发FormLoginConfigurer的configure()方法调用
    HttpSecurity调用build()方法的时候回调用dobuild()方法,然后configure()方法



    这个configure方法回调用HttpSecurity的所有的configure方法,然后转换成过滤器添加到filters方法中

private void configure() throws Exception {
        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

        for (SecurityConfigurer<O, B> configurer : configurers) {
            configurer.configure((B) this);
        }
    }

至于说这个configurers属性我们前面说过每一个配置后面调用apply()方法都会添加到这个列表中,这里不再赘述

2.2 直接添加过滤器

上面添加过滤器是通过对HttpSecurity方法的调用实现配置,最后添加过滤器,然而直接添加过滤器就更加简单,下面只通过一个简单的例子来说一下

2.2.1 addFilterAfter方法

直接看一下源代码就行,因为实在太简单了

public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
    comparator.registerAfter(filter.getClass(), afterFilter);
    return addFilter(filter);
}
public HttpSecurity addFilter(Filter filter) {
        Class<? extends Filter> filterClass = filter.getClass();
        if (!comparator.isRegistered(filterClass)) {
            throw new IllegalArgumentException(
                    "The Filter class "
                            + filterClass.getName()
                            + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
        }
        this.filters.add(filter);
        return this;
    }

我们可以看到只是先确定过滤器的顺序,然后看一下过滤器是否注册了,然后就会添加到我们的filters这个变量。

3 总结

杂乱无章的几乎介绍了这个类的所有的代码,我们总结一下这个类吧
*HttpSecurity最终可以得到一个DefaultSecurityFilterChain通过的是build()方法

  • HttpSecurity维护了一个过滤器的列表,这个过滤器的列表最终放入了DefaultSecurityFilterChain这个过滤器链中
  • HttpSecurity最终提供了很多的配置,然而所有的配置也都是为了处理维护我们的过滤器列表
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,600评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,748评论 6 342
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,313评论 1 92
  • feisky云计算、虚拟化与Linux技术笔记posts - 1014, comments - 298, trac...
    不排版阅读 3,815评论 0 5
  • -你是不是总坚持不下去一件事? -你是不是觉得自己总是半途而废? -你是不经常反复的去执行一个习惯结果还是不了了之...
    B型血兔子007阅读 343评论 0 0