zoukankan      html  css  js  c++  java
  • 源码分析关于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);
        ......
    }
  • 相关阅读:
    二分匹配
    第k短路
    2015_10
    The 15th Zhejiang University Programming Contest
    2015_8
    hdu 1565
    istringstream 用法
    floyd 闭包传递 判断两个点是否属于同一个 强连通分量
    Sicily 1866.Gene Reprogram 一种经典的hash方法
    zoj 3130 最小费用最大流 (求 s到e 的两条总花费最少的 完全没有交点的 路径)
  • 原文地址:https://www.cnblogs.com/fengli9998/p/11457050.html
Copyright © 2011-2022 走看看