源碼分析關(guān)于SpringBoot2.x版本與1.5版本之間的問題(持續(xù)更新)

1.Social包在SpringBoot2.x移除問題

spring-boot-autoconfigure1.5x版本中支持facebook墩崩,領(lǐng)英和推特
官方文檔:https://docs.spring.io/spring-boot/docs/1.5.18.RELEASE/api/

image.png

spring-boot-autoconfigure2.x中版本找不到了
官方文檔:https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/api/

image.png

問題:遇到SocialAutoConfigurerAdapter叉瘩,SocialPropertiesSocialWebAutoConfigurerAdapter類不存在

解決方法:

不想引入1.5版本的springboot的話只能自己按照源碼重寫(復(fù)制粘貼)
官方Github也是這樣寫的:https://github.com/spring-projects/spring-social
SocialAutoConfigurerAdapter源碼

public abstract class SocialAutoConfigurerAdapter extends SocialConfigurerAdapter {
    public SocialAutoConfigurerAdapter() {
    }
    public void addConnectionFactories(ConnectionFactoryConfigurer configurer, Environment environment) {
        configurer.addConnectionFactory(this.createConnectionFactory());
    }
    protected abstract ConnectionFactory<?> createConnectionFactory();
}

SocialProperties源碼

public abstract class SocialProperties {
    private String appId;
    private String appSecret;
    public SocialProperties() {
    }
    public String getAppId() {
        return this.appId;
    }
    public void setAppId(String appId) {
        this.appId = appId;
    }
    public String getAppSecret() {
        return this.appSecret;
    }
    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
    }
}

SocialWebAutoConfiguration源碼

@Configuration
@ConditionalOnClass({ConnectController.class, SocialConfigurerAdapter.class})
@ConditionalOnBean({ConnectionFactoryLocator.class, UsersConnectionRepository.class})
@AutoConfigureBefore({ThymeleafAutoConfiguration.class})
@AutoConfigureAfter({WebMvcAutoConfiguration.class})
public class SocialWebAutoConfiguration {
    public SocialWebAutoConfiguration() {
    }

    private static class SecurityContextUserIdSource implements UserIdSource {
        private SecurityContextUserIdSource() {
        }

        public String getUserId() {
            SecurityContext context = SecurityContextHolder.getContext();
            Authentication authentication = context.getAuthentication();
            Assert.state(authentication != null, "Unable to get a ConnectionRepository: no user signed in");
            return authentication.getName();
        }
    }

    @Configuration
    @ConditionalOnClass({SpringResourceResourceResolver.class})
    protected static class SpringSocialThymeleafConfig {
        protected SpringSocialThymeleafConfig() {
        }

        @Bean
        @ConditionalOnMissingBean
        public SpringSocialDialect springSocialDialect() {
            return new SpringSocialDialect();
        }
    }

    @Configuration
    @EnableSocial
    @ConditionalOnWebApplication
    @ConditionalOnClass({SecurityContextHolder.class})
    protected static class AuthenticationUserIdSourceConfig extends SocialConfigurerAdapter {
        protected AuthenticationUserIdSourceConfig() {
        }

        public UserIdSource getUserIdSource() {
            return new SocialWebAutoConfiguration.SecurityContextUserIdSource();
        }
    }

    @Configuration
    @EnableSocial
    @ConditionalOnWebApplication
    @ConditionalOnMissingClass({"org.springframework.security.core.context.SecurityContextHolder"})
    protected static class AnonymousUserIdSourceConfig extends SocialConfigurerAdapter {
        protected AnonymousUserIdSourceConfig() {
        }

        public UserIdSource getUserIdSource() {
            return new UserIdSource() {
                public String getUserId() {
                    return "anonymous";
                }
            };
        }
    }

    @Configuration
    @EnableSocial
    @ConditionalOnWebApplication
    protected static class SocialAutoConfigurationAdapter extends SocialConfigurerAdapter {
        private final List<ConnectInterceptor<?>> connectInterceptors;
        private final List<DisconnectInterceptor<?>> disconnectInterceptors;
        private final List<ProviderSignInInterceptor<?>> signInInterceptors;

        public SocialAutoConfigurationAdapter(ObjectProvider<List<ConnectInterceptor<?>>> connectInterceptorsProvider, ObjectProvider<List<DisconnectInterceptor<?>>> disconnectInterceptorsProvider, ObjectProvider<List<ProviderSignInInterceptor<?>>> signInInterceptorsProvider) {
            this.connectInterceptors = (List)connectInterceptorsProvider.getIfAvailable();
            this.disconnectInterceptors = (List)disconnectInterceptorsProvider.getIfAvailable();
            this.signInInterceptors = (List)signInInterceptorsProvider.getIfAvailable();
        }

        @Bean
        @ConditionalOnMissingBean({ConnectController.class})
        public ConnectController connectController(ConnectionFactoryLocator factoryLocator, ConnectionRepository repository) {
            ConnectController controller = new ConnectController(factoryLocator, repository);
            if (!CollectionUtils.isEmpty(this.connectInterceptors)) {
                controller.setConnectInterceptors(this.connectInterceptors);
            }

            if (!CollectionUtils.isEmpty(this.disconnectInterceptors)) {
                controller.setDisconnectInterceptors(this.disconnectInterceptors);
            }

            return controller;
        }

        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            prefix = "spring.social",
            name = {"auto-connection-views"}
        )
        public BeanNameViewResolver beanNameViewResolver() {
            BeanNameViewResolver viewResolver = new BeanNameViewResolver();
            viewResolver.setOrder(-2147483648);
            return viewResolver;
        }

        @Bean
        @ConditionalOnBean({SignInAdapter.class})
        @ConditionalOnMissingBean
        public ProviderSignInController signInController(ConnectionFactoryLocator factoryLocator, UsersConnectionRepository usersRepository, SignInAdapter signInAdapter) {
            ProviderSignInController controller = new ProviderSignInController(factoryLocator, usersRepository, signInAdapter);
            if (!CollectionUtils.isEmpty(this.signInInterceptors)) {
                controller.setSignInInterceptors(this.signInInterceptors);
            }

            return controller;
        }
    }
}

2.Jdbc包在SpringBoot1.5和2.x之間的區(qū)別

SpringBoot1.5源碼中Jdbc包

image.png

SpringBoot2.x源碼中Jdbc包

image.png

遇到的問題:DataSourceBuilder在SpringBoot2.x不存在

解決方法:

引入spring-boot-starter-jdbc依賴

<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

源碼對比

SpringBoot1.5DataSourceBuilder 源碼

public class DataSourceBuilder {
    private static final String[] DATA_SOURCE_TYPE_NAMES = new String[]{"org.apache.tomcat.jdbc.pool.DataSource", "com.zaxxer.hikari.HikariDataSource", "org.apache.commons.dbcp.BasicDataSource", "org.apache.commons.dbcp2.BasicDataSource"};
    private Class<? extends DataSource> type;
    private ClassLoader classLoader;
    private Map<String, String> properties = new HashMap();

    public static DataSourceBuilder create() {
        return new DataSourceBuilder((ClassLoader)null);
    }

    public static DataSourceBuilder create(ClassLoader classLoader) {
        return new DataSourceBuilder(classLoader);
    }

    public DataSourceBuilder(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    public DataSource build() {
        Class<? extends DataSource> type = this.getType();
        DataSource result = (DataSource)BeanUtils.instantiate(type);
        this.maybeGetDriverClassName();
        this.bind(result);
        return result;
    }

    private void maybeGetDriverClassName() {
        if (!this.properties.containsKey("driverClassName") && this.properties.containsKey("url")) {
            String url = (String)this.properties.get("url");
            String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
            this.properties.put("driverClassName", driverClass);
        }

    }

    private void bind(DataSource result) {
        MutablePropertyValues properties = new MutablePropertyValues(this.properties);
        (new RelaxedDataBinder(result)).withAlias("url", new String[]{"jdbcUrl"}).withAlias("username", new String[]{"user"}).bind(properties);
    }

    public DataSourceBuilder type(Class<? extends DataSource> type) {
        this.type = type;
        return this;
    }

    public DataSourceBuilder url(String url) {
        this.properties.put("url", url);
        return this;
    }

    public DataSourceBuilder driverClassName(String driverClassName) {
        this.properties.put("driverClassName", driverClassName);
        return this;
    }

    public DataSourceBuilder username(String username) {
        this.properties.put("username", username);
        return this;
    }

    public DataSourceBuilder password(String password) {
        this.properties.put("password", password);
        return this;
    }

    public Class<? extends DataSource> findType() {
        if (this.type != null) {
            return this.type;
        } else {
            String[] var1 = DATA_SOURCE_TYPE_NAMES;
            int var2 = var1.length;
            int var3 = 0;

            while(var3 < var2) {
                String name = var1[var3];

                try {
                    return ClassUtils.forName(name, this.classLoader);
                } catch (Exception var6) {
                    ++var3;
                }
            }

            return null;
        }
    }

    private Class<? extends DataSource> getType() {
        Class<? extends DataSource> type = this.findType();
        if (type != null) {
            return type;
        } else {
            throw new IllegalStateException("No supported DataSource type found");
        }
    }
}

SpringBoot2.xspring-boot-starter-jdbc依賴中DataSourceBuilder源碼

public final class DataSourceBuilder<T extends DataSource> {
    private static final String[] DATA_SOURCE_TYPE_NAMES = new String[]{"com.zaxxer.hikari.HikariDataSource", "org.apache.tomcat.jdbc.pool.DataSource", "org.apache.commons.dbcp2.BasicDataSource"};
    private Class<? extends DataSource> type;
    private ClassLoader classLoader;
    private Map<String, String> properties = new HashMap();

    public static DataSourceBuilder<?> create() {
        return new DataSourceBuilder((ClassLoader)null);
    }

    public static DataSourceBuilder<?> create(ClassLoader classLoader) {
        return new DataSourceBuilder(classLoader);
    }

    private DataSourceBuilder(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    public T build() {
        Class<? extends DataSource> type = this.getType();
        DataSource result = (DataSource)BeanUtils.instantiateClass(type);
        this.maybeGetDriverClassName();
        this.bind(result);
        return result;
    }

    private void maybeGetDriverClassName() {
        if (!this.properties.containsKey("driverClassName") && this.properties.containsKey("url")) {
            String url = (String)this.properties.get("url");
            String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
            this.properties.put("driverClassName", driverClass);
        }

    }

    private void bind(DataSource result) {
        ConfigurationPropertySource source = new MapConfigurationPropertySource(this.properties);
        ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
        aliases.addAliases("url", new String[]{"jdbc-url"});
        aliases.addAliases("username", new String[]{"user"});
        Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
        binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
    }

    public <D extends DataSource> DataSourceBuilder<D> type(Class<D> type) {
        this.type = type;
        return this;
    }

    public DataSourceBuilder<T> url(String url) {
        this.properties.put("url", url);
        return this;
    }

    public DataSourceBuilder<T> driverClassName(String driverClassName) {
        this.properties.put("driverClassName", driverClassName);
        return this;
    }

    public DataSourceBuilder<T> username(String username) {
        this.properties.put("username", username);
        return this;
    }

    public DataSourceBuilder<T> password(String password) {
        this.properties.put("password", password);
        return this;
    }

    public static Class<? extends DataSource> findType(ClassLoader classLoader) {
        String[] var1 = DATA_SOURCE_TYPE_NAMES;
        int var2 = var1.length;
        int var3 = 0;

        while(var3 < var2) {
            String name = var1[var3];

            try {
                return ClassUtils.forName(name, classLoader);
            } catch (Exception var6) {
                ++var3;
            }
        }

        return null;
    }

    private Class<? extends DataSource> getType() {
        Class<? extends DataSource> type = this.type != null ? this.type : findType(this.classLoader);
        if (type != null) {
            return type;
        } else {
            throw new IllegalStateException("No supported DataSource type found");
        }
    }
}

3.關(guān)于SpringDataJpa中findOne()方法報錯問題

報錯信息Inferred type 'S' for type parameter 'S' is not within its bound;should extends xxxxxx

解決方法:

1.用回SpringBoot1.5
2.findOne()改為findById().orElse(null)

源碼對比

SpringBoot2.xspring-boot-starter-data-jpa依賴中的pom.xmlspring-data-jpa2.x.x

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.1.3.RELEASE</version>

CrudRepository源碼

@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
    <S extends T> S save(S var1);
    <S extends T> Iterable<S> saveAll(Iterable<S> var1);
    Optional<T> findById(ID var1);
    boolean existsById(ID var1);
    Iterable<T> findAll();
    Iterable<T> findAllById(Iterable<ID> var1);
    long count();
    void deleteById(ID var1);
    void delete(T var1);
    void deleteAll(Iterable<? extends T> var1);
    void deleteAll();
}

SpringBoot1.5spring-boot-starter-data-jpa依賴中的pom.xmlspring-data-jpa1.x.x

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.11.17.RELEASE</version>

CrudRepository源碼

@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
    <S extends T> S save(S var1);
    <S extends T> Iterable<S> save(Iterable<S> var1);
    T findOne(ID var1);
    boolean exists(ID var1);
    Iterable<T> findAll();
    Iterable<T> findAll(Iterable<ID> var1);
    long count();
    void delete(ID var1);
    void delete(T var1);
    void delete(Iterable<? extends T> var1);
    void deleteAll();
}

區(qū)別:返回值由T變?yōu)镺ptional<T>乐疆,

Optional類是Java8新特性類庫:
官方介紹:https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html

Optional<T>源碼

public final class Optional<T> {
    private static final Optional<?> EMPTY = new Optional<>();
    private final T value;

    private Optional() {
        this.value = null;
    }

    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }

    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }

    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    public boolean isPresent() {
        return value != null;
    }

    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }

    public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

    public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

    public T orElse(T other) {
        return value != null ? value : other;
    }

    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }

    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (!(obj instanceof Optional)) {
            return false;
        }

        Optional<?> other = (Optional<?>) obj;
        return Objects.equals(value, other.value);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(value);
    }

    @Override
    public String toString() {
        return value != null
            ? String.format("Optional[%s]", value)
            : "Optional.empty";
    }
}

get()可以獲取到值车海,但是直接這樣寫的話如果值不存在就要拋異常。所以要先做判斷恭理,值存在再get()殉挽,或者就是寫在try-catch
orElse(null)存在就會直接返回值,如果不存在會返回別的值裳擎,這里不存在返回的是null(可以給默認(rèn)值)

4.Elasticsearch與springboot集成的問題

1.注釋@Field的變化

源碼對比
SpringBoot1.5

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited
public @interface Field {
    FieldType type() default FieldType.Auto;
    FieldIndex index() default FieldIndex.analyzed;
    DateFormat format() default DateFormat.none;
    String pattern() default "";
    boolean store() default false;
    String searchAnalyzer() default "";
    String analyzer() default "";
    String[] ignoreFields() default {};
    boolean includeInParent() default false;
}

SpringBoot2.x

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited
public @interface Field {
    FieldType type() default FieldType.Auto;
    boolean index() default true;
    DateFormat format() default DateFormat.none;
    String pattern() default "";
    boolean store() default false;
    boolean fielddata() default false;
    String searchAnalyzer() default "";
    String analyzer() default "";
    String normalizer() default "";
    String[] ignoreFields() default {};
    boolean includeInParent() default false;
    String[] copyTo() default {};
}

注解@Field的內(nèi)置方法index()返回值由FieldIndex變?yōu)?code>boolean

2.FieldIndex枚舉

源碼對比
SpringBoot1.5

public enum FieldIndex {
    not_analyzed,
    analyzed,
    no;

    private FieldIndex() {
    }
}

not_analyzed:整個字段存儲為關(guān)鍵詞涎永,常用于漢字短語、郵箱等復(fù)雜的字符串鹿响;
analyzed:通過默認(rèn)的standard分析器進(jìn)行分析羡微,詳細(xì)的分析規(guī)則參考這里
no:無法通過檢索查詢到該字段;

SpringBoot2.x

public enum FieldType {
    Text,
    Integer,
    Long,
    Date,
    Float,
    Double,
    Boolean,
    Object,
    Auto,
    Nested,
    Ip,
    Attachment,
    Keyword;

    private FieldType() {
    }
}

3.在Elasticsearch與springboot集成中變化較大的還有Terms接口

源碼對比
SpringBoot1.5

public interface Terms extends MultiBucketsAggregation {
    List<Terms.Bucket> getBuckets();
    Terms.Bucket getBucketByKey(String var1);
    long getDocCountError();
    long getSumOfOtherDocCounts();

    public abstract static class Order implements ToXContent {
        public Order() {
        }

        public static Terms.Order count(boolean asc) {
            return asc ? InternalOrder.COUNT_ASC : InternalOrder.COUNT_DESC;
        }

        public static Terms.Order term(boolean asc) {
            return asc ? InternalOrder.TERM_ASC : InternalOrder.TERM_DESC;
        }

        public static Terms.Order aggregation(String path, boolean asc) {
            return new Aggregation(path, asc);
        }

        public static Terms.Order aggregation(String aggregationName, String metricName, boolean asc) {
            return new Aggregation(aggregationName + "." + metricName, asc);
        }

        public static Terms.Order compound(List<Terms.Order> orders) {
            return new CompoundOrder(orders);
        }

        public static Terms.Order compound(Terms.Order... orders) {
            return compound(Arrays.asList(orders));
        }

        protected abstract Comparator<Terms.Bucket> comparator(Aggregator var1);

        abstract byte id();
    }

    public abstract static class Bucket extends InternalBucket {
        public Bucket() {
        }

        public abstract Number getKeyAsNumber();

        abstract int compareTerm(Terms.Bucket var1);

        public abstract long getDocCountError();
    }

    public static enum ValueType {
        STRING(org.elasticsearch.search.aggregations.support.ValueType.STRING),
        LONG(org.elasticsearch.search.aggregations.support.ValueType.LONG),
        DOUBLE(org.elasticsearch.search.aggregations.support.ValueType.DOUBLE);

        final org.elasticsearch.search.aggregations.support.ValueType scriptValueType;

        private ValueType(org.elasticsearch.search.aggregations.support.ValueType scriptValueType) {
            this.scriptValueType = scriptValueType;
        }

        static Terms.ValueType resolveType(String type) {
            if ("string".equals(type)) {
                return STRING;
            } else if (!"double".equals(type) && !"float".equals(type)) {
                return !"long".equals(type) && !"integer".equals(type) && !"short".equals(type) && !"byte".equals(type) ? null : LONG;
            } else {
                return DOUBLE;
            }
        }
    }
}

SpringBoot2.x

public interface Terms extends MultiBucketsAggregation {
    List<? extends Terms.Bucket> getBuckets();

    Terms.Bucket getBucketByKey(String var1);

    long getDocCountError();

    long getSumOfOtherDocCounts();

    public interface Bucket extends org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket {
        Number getKeyAsNumber();

        long getDocCountError();
    }
}
可以發(fā)現(xiàn)內(nèi)部類Order并沒有在Terms中惶我,而是變成了抽象類BucketOrder
public abstract class BucketOrder implements ToXContentObject, Writeable {
    public BucketOrder() {
    }

    public static BucketOrder count(boolean asc) {
        return asc ? InternalOrder.COUNT_ASC : InternalOrder.COUNT_DESC;
    }

    public static BucketOrder key(boolean asc) {
        return asc ? InternalOrder.KEY_ASC : InternalOrder.KEY_DESC;
    }

    public static BucketOrder aggregation(String path, boolean asc) {
        return new Aggregation(path, asc);
    }

    public static BucketOrder aggregation(String path, String metricName, boolean asc) {
        return new Aggregation(path + "." + metricName, asc);
    }

    public static BucketOrder compound(List<BucketOrder> orders) {
        return new CompoundOrder(orders);
    }

    public static BucketOrder compound(BucketOrder... orders) {
        return compound(Arrays.asList(orders));
    }

    public abstract Comparator<Bucket> comparator(Aggregator var1);

    abstract byte id();

    public abstract int hashCode();

    public abstract boolean equals(Object var1);

    public void writeTo(StreamOutput out) throws IOException {
        Streams.writeOrder(this, out);
    }

    public String toString() {
        return Strings.toString(this);
    }
}

在聚合查詢中SpringBoot1.5用Terms.Order.count()是沒問題的妈倔,在SpringBoot2.x中需要改成BucketOrder.count()

5.與Thymeleaf集成時SpringWebContext方法不存在

為了優(yōu)化訪問速度,應(yīng)對高并發(fā)绸贡,把頁面信息全部獲取出來存到redis緩存中盯蝴,需要用thymeleafViewResolver.getTemplateEngine().process("goodslist.html",ctx);實現(xiàn)

ctx參數(shù)在SpringBoot1.5中使用的是SpringWebContext

SpringWebContext源碼

public class SpringWebContext extends WebContext {
    public static final String BEANS_VARIABLE_NAME = "beans";
    private static final ConcurrentHashMap<ApplicationContext, HashMap<String, Object>> variableMapPrototypes = new ConcurrentHashMap();
    private final ApplicationContext applicationContext;

    public SpringWebContext(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, Locale locale, Map<String, ?> variables, ApplicationContext appctx) {
        super(request, response, servletContext, locale, addSpringSpecificVariables(variables, appctx));
        this.applicationContext = appctx;
    }

    private static Map<String, Object> addSpringSpecificVariables(Map<String, ?> variables, ApplicationContext appctx) {
        HashMap<String, Object> variableMapPrototype = (HashMap)variableMapPrototypes.get(appctx);
        if (variableMapPrototype == null) {
            variableMapPrototype = new HashMap(20, 1.0F);
            Beans beans = new Beans(appctx);
            variableMapPrototype.put("beans", beans);
            variableMapPrototypes.put(appctx, variableMapPrototype);
        }
        Map newVariables;
        synchronized(variableMapPrototype) {
            newVariables = (Map)variableMapPrototype.clone();
        }
        if (variables != null) {
            newVariables.putAll(variables);
        }
        return newVariables;
    }

    public ApplicationContext getApplicationContext() {
        return this.applicationContext;
    }
}
ctx參數(shù)在SpringBoot2.x時用的是WebContext,官方已經(jīng)把大部分的功能移到了IWebContext接口下,用于區(qū)分邊界恃轩。

WebContext源碼

public final class WebContext extends AbstractContext implements IWebContext {
    private final HttpServletRequest request;
    private final HttpServletResponse response;
    private final ServletContext servletContext;

    public WebContext(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {
        this.request = request;
        this.response = response;
        this.servletContext = servletContext;
    }

    public WebContext(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, Locale locale) {
        super(locale);
        this.request = request;
        this.response = response;
        this.servletContext = servletContext;
    }

    public WebContext(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, Locale locale, Map<String, Object> variables) {
        super(locale, variables);
        this.request = request;
        this.response = response;
        this.servletContext = servletContext;
    }

    public HttpServletRequest getRequest() {
        return this.request;
    }

    public HttpSession getSession() {
        return this.request.getSession(false);
    }

    public HttpServletResponse getResponse() {
        return this.response;
    }

    public ServletContext getServletContext() {
        return this.servletContext;
    }
}

其實區(qū)別就是在構(gòu)造方法结洼,SpringBoot2.x中剔除了ApplicationContext過多的依賴,現(xiàn)在thymeleaf渲染不再過多依賴spring容器

解決方法:

SpringWebContext換成WebContext叉跛,構(gòu)造參數(shù)中刪除ApplicationContext對象

6.Security中Md5PasswordEncoder廢棄處理

SpringBoot1.5中經(jīng)常用到SecurityMd5PasswordEncoder進(jìn)行密碼MD5加密和驗證

Md5PasswordEncoder源碼

public class Md5PasswordEncoder extends MessageDigestPasswordEncoder {
    public Md5PasswordEncoder() {
        super("MD5");
    }
}

源碼很簡單松忍,主要用到父類的方法
繼承于MessageDigestPasswordEncoder

public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder {
    private final String algorithm;
    private int iterations;

    public MessageDigestPasswordEncoder(String algorithm) {
        this(algorithm, false);
    }

    public MessageDigestPasswordEncoder(String algorithm, boolean encodeHashAsBase64) throws IllegalArgumentException {
        this.iterations = 1;
        this.algorithm = algorithm;
        this.setEncodeHashAsBase64(encodeHashAsBase64);
        this.getMessageDigest();
    }

    public String encodePassword(String rawPass, Object salt) {
        String saltedPass = this.mergePasswordAndSalt(rawPass, salt, false);
        MessageDigest messageDigest = this.getMessageDigest();
        byte[] digest = messageDigest.digest(Utf8.encode(saltedPass));

        for(int i = 1; i < this.iterations; ++i) {
            digest = messageDigest.digest(digest);
        }

        return this.getEncodeHashAsBase64() ? Utf8.decode(Base64.encode(digest)) : new String(Hex.encode(digest));
    }

    protected final MessageDigest getMessageDigest() throws IllegalArgumentException {
        try {
            return MessageDigest.getInstance(this.algorithm);
        } catch (NoSuchAlgorithmException var2) {
            throw new IllegalArgumentException("No such algorithm [" + this.algorithm + "]");
        }
    }

    public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
        String pass1 = "" + encPass;
        String pass2 = this.encodePassword(rawPass, salt);
        return PasswordEncoderUtils.equals(pass1, pass2);
    }

    public String getAlgorithm() {
        return this.algorithm;
    }

    public void setIterations(int iterations) {
        Assert.isTrue(iterations > 0, "Iterations value must be greater than zero");
        this.iterations = iterations;
    }
}

繼承關(guān)系如下

public class Md5PasswordEncoder extends MessageDigestPasswordEncoder
public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder
public abstract class BaseDigestPasswordEncoder extends BasePasswordEncoder
public abstract class BasePasswordEncoder implements PasswordEncoder

PasswordEncoder接口

public interface PasswordEncoder {
    String encodePassword(String var1, Object var2);
    boolean isPasswordValid(String var1, String var2, Object var3);
}

encodePassword()是對原始密碼進(jìn)行加密,采用hash+salt方式筷厘,在方法中應(yīng)用系統(tǒng)得提供鹽值(salt)鸣峭。
isPasswordValid()是用來驗證密碼是否正確的宏所,得提供三個參數(shù),加密后的密碼摊溶、原始密碼以及鹽值(salt)爬骤。缺點就是每次加密和解密都得提供鹽值,加密后的密碼是固定的莫换,而且接口實現(xiàn)復(fù)雜霞玄,存在多繼承和實現(xiàn)。

SpringBoot2.x刪除了MD5拉岁,因為它不再足夠安全,應(yīng)該使用Bcrypt

BCryptPasswordEncoder方法采用SHA-256 +隨機鹽+密鑰對密碼進(jìn)行加密坷剧。SHA系列是Hash算法,不是加密算法喊暖,使用加密算法意味著可以解密(這個與編碼/解碼一樣)惫企,但是采用Hash處理,其過程是不可逆的
BCryptPasswordEncoder源碼

public class BCryptPasswordEncoder implements PasswordEncoder {
    private Pattern BCRYPT_PATTERN;
    private final Log logger;
    private final int strength;
    private final SecureRandom random;

    public BCryptPasswordEncoder() {
        this(-1);
    }

    public BCryptPasswordEncoder(int strength) {
        this(strength, (SecureRandom)null);
    }

    public BCryptPasswordEncoder(int strength, SecureRandom random) {
        this.BCRYPT_PATTERN = Pattern.compile("\\A\\$2a?\\$\\d\\d\\$[./0-9A-Za-z]{53}");
        this.logger = LogFactory.getLog(this.getClass());
        if (strength == -1 || strength >= 4 && strength <= 31) {
            this.strength = strength;
            this.random = random;
        } else {
            throw new IllegalArgumentException("Bad strength");
        }
    }

    public String encode(CharSequence rawPassword) {
        String salt;
        if (this.strength > 0) {
            if (this.random != null) {
                salt = BCrypt.gensalt(this.strength, this.random);
            } else {
                salt = BCrypt.gensalt(this.strength);
            }
        } else {
            salt = BCrypt.gensalt();
        }

        return BCrypt.hashpw(rawPassword.toString(), salt);
    }

    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if (encodedPassword != null && encodedPassword.length() != 0) {
            if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
                this.logger.warn("Encoded password does not look like BCrypt");
                return false;
            } else {
                return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
            }
        } else {
            this.logger.warn("Empty encoded password");
            return false;
        }
    }
}

繼承關(guān)系如下

public class BCryptPasswordEncoder implements PasswordEncoder

PasswordEncoder接口源碼也有改變

public interface PasswordEncoder {
    String encode(CharSequence var1);
    boolean matches(CharSequence var1, String var2);
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

encode(CharSequence rawPassword)是對方法加密陵叽,入?yún)⒅挥性济艽a狞尔,而且每次獲取的加密后的密碼不一樣
matches(CharSequence rawPassword, String encodedPassword) 前一個參數(shù)為前端傳來的值,后一個為數(shù)據(jù)庫中需要對比的值(已加密存入數(shù)據(jù)庫的密碼)巩掺。是用來驗證密碼和加密后密碼是否一致的偏序,如果一致則返回true。優(yōu)點鹽值不用用戶提供锌半,每次隨機生成禽车,多重加密——迭代SHA-256算法+密鑰+隨機鹽來對密碼加密,大大增加密碼破解難度刊殉,而且接口實現(xiàn)簡單,不存在多繼承州胳。

7.Spring Boot異常處理相關(guān)類缺失問題

SpringBoot 1.5的org.springframework.boot.autoconfigure.web包中
image.png
SpringBoot 2.x的org.springframework.boot.autoconfigure.web包中
image.png

其中的ErrorAttributes记焊,ErrorControllerDefaultErrorAttributes在SpringBoot 2.x的時候都轉(zhuǎn)到org.springframework.boot.web.servlet.error包中

ErrorAttributes接口源碼對比
SpringBoot 1.5
public interface ErrorAttributes {
    Map<String, Object> getErrorAttributes(RequestAttributes var1, boolean var2);
    Throwable getError(RequestAttributes var1);
}
SpringBoot2.x
public interface ErrorAttributes {
    Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace);
    Throwable getError(WebRequest webRequest);
}
ErrorController接口源碼對比
SpringBoot 1.5
public interface ErrorController {
    String getErrorPath();
}
SpringBoot2.x
@FunctionalInterface
public interface ErrorController {
    String getErrorPath();
}
DefaultErrorAttributes類源碼對比
SpringBoot 1.5
@Order(-2147483648)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
    private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName() + ".ERROR";

    public DefaultErrorAttributes() {
    }

    public int getOrder() {
        return -2147483648;
    }

    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        this.storeErrorAttributes(request, ex);
        return null;
    }

    private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
        request.setAttribute(ERROR_ATTRIBUTE, ex);
    }

    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        this.addStatus(errorAttributes, requestAttributes);
        this.addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
        this.addPath(errorAttributes, requestAttributes);
        return errorAttributes;
    }

    private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
        Integer status = (Integer)this.getAttribute(requestAttributes, "javax.servlet.error.status_code");
        if (status == null) {
            errorAttributes.put("status", 999);
            errorAttributes.put("error", "None");
        } else {
            errorAttributes.put("status", status);

            try {
                errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
            } catch (Exception var5) {
                errorAttributes.put("error", "Http Status " + status);
            }

        }
    }

    private void addErrorDetails(Map<String, Object> errorAttributes, RequestAttributes requestAttributes, boolean includeStackTrace) {
        Throwable error = this.getError(requestAttributes);
        if (error != null) {
            while(true) {
                if (!(error instanceof ServletException) || error.getCause() == null) {
                    errorAttributes.put("exception", error.getClass().getName());
                    this.addErrorMessage(errorAttributes, error);
                    if (includeStackTrace) {
                        this.addStackTrace(errorAttributes, error);
                    }
                    break;
                }

                error = ((ServletException)error).getCause();
            }
        }

        Object message = this.getAttribute(requestAttributes, "javax.servlet.error.message");
        if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null) && !(error instanceof BindingResult)) {
            errorAttributes.put("message", StringUtils.isEmpty(message) ? "No message available" : message);
        }

    }

    private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) {
        BindingResult result = this.extractBindingResult(error);
        if (result == null) {
            errorAttributes.put("message", error.getMessage());
        } else {
            if (result.getErrorCount() > 0) {
                errorAttributes.put("errors", result.getAllErrors());
                errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. Error count: " + result.getErrorCount());
            } else {
                errorAttributes.put("message", "No errors");
            }

        }
    }

    private BindingResult extractBindingResult(Throwable error) {
        if (error instanceof BindingResult) {
            return (BindingResult)error;
        } else {
            return error instanceof MethodArgumentNotValidException ? ((MethodArgumentNotValidException)error).getBindingResult() : null;
        }
    }

    private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
        StringWriter stackTrace = new StringWriter();
        error.printStackTrace(new PrintWriter(stackTrace));
        stackTrace.flush();
        errorAttributes.put("trace", stackTrace.toString());
    }

    private void addPath(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
        String path = (String)this.getAttribute(requestAttributes, "javax.servlet.error.request_uri");
        if (path != null) {
            errorAttributes.put("path", path);
        }

    }

    public Throwable getError(RequestAttributes requestAttributes) {
        Throwable exception = (Throwable)this.getAttribute(requestAttributes, ERROR_ATTRIBUTE);
        if (exception == null) {
            exception = (Throwable)this.getAttribute(requestAttributes, "javax.servlet.error.exception");
        }

        return exception;
    }

    private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
        return requestAttributes.getAttribute(name, 0);
    }
}
SpringBoot2.x
@Order(-2147483648)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
    private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName() + ".ERROR";
    private final boolean includeException;

    public DefaultErrorAttributes() {
        this(false);
    }

    public DefaultErrorAttributes(boolean includeException) {
        this.includeException = includeException;
    }

    public int getOrder() {
        return -2147483648;
    }

    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        this.storeErrorAttributes(request, ex);
        return null;
    }

    private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
        request.setAttribute(ERROR_ATTRIBUTE, ex);
    }

    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        this.addStatus(errorAttributes, webRequest);
        this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        this.addPath(errorAttributes, webRequest);
        return errorAttributes;
    }

    private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
        Integer status = (Integer)this.getAttribute(requestAttributes, "javax.servlet.error.status_code");
        if (status == null) {
            errorAttributes.put("status", 999);
            errorAttributes.put("error", "None");
        } else {
            errorAttributes.put("status", status);

            try {
                errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
            } catch (Exception var5) {
                errorAttributes.put("error", "Http Status " + status);
            }

        }
    }

    private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace) {
        Throwable error = this.getError(webRequest);
        if (error != null) {
            while(true) {
                if (!(error instanceof ServletException) || error.getCause() == null) {
                    if (this.includeException) {
                        errorAttributes.put("exception", error.getClass().getName());
                    }

                    this.addErrorMessage(errorAttributes, error);
                    if (includeStackTrace) {
                        this.addStackTrace(errorAttributes, error);
                    }
                    break;
                }

                error = ((ServletException)error).getCause();
            }
        }

        Object message = this.getAttribute(webRequest, "javax.servlet.error.message");
        if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null) && !(error instanceof BindingResult)) {
            errorAttributes.put("message", StringUtils.isEmpty(message) ? "No message available" : message);
        }

    }

    private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) {
        BindingResult result = this.extractBindingResult(error);
        if (result == null) {
            errorAttributes.put("message", error.getMessage());
        } else {
            if (result.hasErrors()) {
                errorAttributes.put("errors", result.getAllErrors());
                errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. Error count: " + result.getErrorCount());
            } else {
                errorAttributes.put("message", "No errors");
            }

        }
    }

    private BindingResult extractBindingResult(Throwable error) {
        if (error instanceof BindingResult) {
            return (BindingResult)error;
        } else {
            return error instanceof MethodArgumentNotValidException ? ((MethodArgumentNotValidException)error).getBindingResult() : null;
        }
    }

    private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
        StringWriter stackTrace = new StringWriter();
        error.printStackTrace(new PrintWriter(stackTrace));
        stackTrace.flush();
        errorAttributes.put("trace", stackTrace.toString());
    }

    private void addPath(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
        String path = (String)this.getAttribute(requestAttributes, "javax.servlet.error.request_uri");
        if (path != null) {
            errorAttributes.put("path", path);
        }

    }

    public Throwable getError(WebRequest webRequest) {
        Throwable exception = (Throwable)this.getAttribute(webRequest, ERROR_ATTRIBUTE);
        if (exception == null) {
            exception = (Throwable)this.getAttribute(webRequest, "javax.servlet.error.exception");
        }

        return exception;
    }

    private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
        return requestAttributes.getAttribute(name, 0);
    }
}

相關(guān)例子

相關(guān)代碼
public Object errorApiHandler(HttpServletRequest request,boolean includeStackTrace) {
    RequestAttributes requestAttributes = new ServletRequestAttributes(request);
    Map<String, Object> attr = this.errorAttributes.getErrorAttributes((WebRequest)requestAttributes,includeStackTrace);
    ......
}
這樣寫雖然不會報語法錯誤栓撞,但是在SpringBoot2.x運行時會報
Caused by: java.lang.ClassCastException: org.springframework.web.context.request.ServletRequestAttributes 
cannot be cast to org.springframework.web.context.request.WebRequest

也就是類型轉(zhuǎn)換異常遍膜,ServletRequestAttributes 不能強轉(zhuǎn)為WebRequest

解決方法

代碼修改為

public Object errorApiHandler(HttpServletRequest request,boolean includeStackTrace) {
    WebRequest webRequest=new ServletWebRequest(request);
    Map<String, Object> attr = this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    ......
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瓤湘,隨后出現(xiàn)的幾起案子瓢颅,更是在濱河造成了極大的恐慌,老刑警劉巖弛说,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挽懦,死亡現(xiàn)場離奇詭異,居然都是意外死亡木人,警方通過查閱死者的電腦和手機信柿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門冀偶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人渔嚷,你說我怎么就攤上這事进鸠。” “怎么了形病?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵客年,是天一觀的道長。 經(jīng)常有香客問我漠吻,道長搀罢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任侥猩,我火速辦了婚禮榔至,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘欺劳。我一直安慰自己唧取,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布划提。 她就那樣靜靜地躺著枫弟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鹏往。 梳的紋絲不亂的頭發(fā)上淡诗,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音伊履,去河邊找鬼韩容。 笑死,一個胖子當(dāng)著我的面吹牛唐瀑,可吹牛的內(nèi)容都是我干的群凶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼哄辣,長吁一口氣:“原來是場噩夢啊……” “哼请梢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起力穗,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤毅弧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后当窗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體够坐,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了咆霜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邓馒。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蛾坯,靈堂內(nèi)的尸體忽然破棺而出光酣,到底是詐尸還是另有隱情,我是刑警寧澤脉课,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布救军,位于F島的核電站,受9級特大地震影響倘零,放射性物質(zhì)發(fā)生泄漏唱遭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一呈驶、第九天 我趴在偏房一處隱蔽的房頂上張望拷泽。 院中可真熱鬧,春花似錦袖瞻、人聲如沸司致。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脂矫。三九已至,卻和暖如春霉晕,著一層夾襖步出監(jiān)牢的瞬間庭再,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工牺堰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拄轻,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓萌焰,卻偏偏與公主長得像哺眯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子扒俯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容