源码分析关于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); ...... }

猜你喜欢

转载自www.cnblogs.com/fengli9998/p/11457050.html