源码分析关于SpringBoot2.x版本与1.5版本之间的问题(持续更新)

1.Social包在SpringBoot2.x移除问题

spring-boot-autoconfigure1.5x版本中支持facebook,领英和推特
官方文档: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

问题:遇到SocialAutoConfigurerAdapterSocialPropertiesSocialWebAutoConfigurerAdapter类不存在

解决方法:

不想引入1.5版本的springboot的话只能自己按照源码重写(复制粘贴)
官方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之间的区别

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.关于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();
}

区别:返回值由T变为Optional<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(可以给默认值)

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的内置方法index()返回值由FieldIndex变为boolean

2.FieldIndex枚举

源码对比
SpringBoot1.5

public enum FieldIndex {
    not_analyzed,
    analyzed,
    no;

    private FieldIndex() {
    }
}

not_analyzed:整个字段存储为关键词,常用于汉字短语、邮箱等复杂的字符串;
analyzed:通过默认的standard分析器进行分析,详细的分析规则参考这里
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();
    }
}
可以发现内部类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方法不存在

为了优化访问速度,应对高并发,把页面信息全部获取出来存到redis缓存中,需要用thymeleafViewResolver.getTemplateEngine().process("goodslist.html",ctx);实现

ctx参数在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参数在SpringBoot2.x时用的是WebContext,官方已经把大部分的功能移到了IWebContext接口下,用于区分边界。

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;
    }
}

其实区别就是在构造方法,SpringBoot2.x中剔除了ApplicationContext过多的依赖,现在thymeleaf渲染不再过多依赖spring容器

解决方法:

SpringWebContext换成WebContext,构造参数中删除ApplicationContext对象

6.Security中Md5PasswordEncoder废弃处理

SpringBoot1.5中经常用到SecurityMd5PasswordEncoder进行密码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;
    }
}

继承关系如下

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()是对原始密码进行加密,采用hash+salt方式,在方法中应用系统得提供盐值(salt)。
isPasswordValid()是用来验证密码是否正确的,得提供三个参数,加密后的密码、原始密码以及盐值(salt)。缺点就是每次加密和解密都得提供盐值,加密后的密码是固定的,而且接口实现复杂,存在多继承和实现。

SpringBoot2.x删除了MD5,因为它不再足够安全,应该使用Bcrypt

BCryptPasswordEncoder方法采用SHA-256 +随机盐+密钥对密码进行加密。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;
        }
    }
}

继承关系如下

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)是对方法加密,入参只有原始密码,而且每次获取的加密后的密码不一样
matches(CharSequence rawPassword, String encodedPassword) 前一个参数为前端传来的值,后一个为数据库中需要对比的值(已加密存入数据库的密码)。是用来验证密码和加密后密码是否一致的,如果一致则返回true。优点盐值不用用户提供,每次随机生成,多重加密——迭代SHA-256算法+密钥+随机盐来对密码加密,大大增加密码破解难度,而且接口实现简单,不存在多继承。

7.Spring Boot异常处理相关类缺失问题

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

其中的ErrorAttributesErrorControllerDefaultErrorAttributes在SpringBoot 2.x的时候都转到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);
    }
}

相关例子

相关代码
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

也就是类型转换异常,ServletRequestAttributes 不能强转为WebRequest

解决方法

代码修改为

public Object errorApiHandler(HttpServletRequest request,boolean includeStackTrace) {
    WebRequest webRequest=new ServletWebRequest(request);
    Map<String, Object> attr = this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    ......
}

推荐阅读更多精彩内容