1.Social包在SpringBoot2.x移除问题
spring-boot-autoconfigure1.5x版本中支持facebook,领英和推特
官方文档:https://docs.spring.io/spring-boot/docs/1.5.18.RELEASE/api/
spring-boot-autoconfigure2.x中版本找不到了
官方文档:https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/api/
问题:遇到SocialAutoConfigurerAdapter
,SocialProperties
和SocialWebAutoConfigurerAdapter
类不存在
解决方法:
不想引入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> connectInterceptors;
private final List> disconnectInterceptors;
private final List> signInInterceptors;
public SocialAutoConfigurationAdapter(ObjectProvider>> connectInterceptorsProvider, ObjectProvider>> disconnectInterceptorsProvider, ObjectProvider>> 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包
SpringBoot2.x源码中Jdbc包
遇到的问题:DataSourceBuilder在SpringBoot2.x不存在
解决方法:
引入spring-boot-starter-jdbc
依赖
org.springframework.boot
spring-boot-starter-jdbc
源码对比
SpringBoot1.5
中DataSourceBuilder
源码
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 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.x
的spring-boot-starter-jdbc
依赖中DataSourceBuilder
源码
public final class DataSourceBuilder {
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 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 DataSourceBuilder type(Class 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 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.x
的spring-boot-starter-data-jpa
依赖中的pom.xml
中spring-data-jpa
是2.x.x
org.springframework.data
spring-data-jpa
2.1.3.RELEASE
CrudRepository源码
@NoRepositoryBean
public interface CrudRepository extends Repository {
S save(S var1);
Iterable saveAll(Iterable var1);
Optional findById(ID var1);
boolean existsById(ID var1);
Iterable findAll();
Iterable findAllById(Iterable var1);
long count();
void deleteById(ID var1);
void delete(T var1);
void deleteAll(Iterable extends T> var1);
void deleteAll();
}
SpringBoot1.5
的spring-boot-starter-data-jpa
依赖中的pom.xml
中spring-data-jpa
是1.x.x
org.springframework.data
spring-data-jpa
1.11.17.RELEASE
CrudRepository源码
@NoRepositoryBean
public interface CrudRepository extends Repository {
S save(S var1);
Iterable save(Iterable var1);
T findOne(ID var1);
boolean exists(ID var1);
Iterable findAll();
Iterable findAll(Iterable var1);
long count();
void delete(ID var1);
void delete(T var1);
void delete(Iterable extends T> var1);
void deleteAll();
}
区别:返回值由T变为Optional,
Optional类是Java8新特性类库:
官方介绍:https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
Optional
源码
public final class Optional {
private static final Optional> EMPTY = new Optional<>();
private final T value;
private Optional() {
this.value = null;
}
public static Optional empty() {
@SuppressWarnings("unchecked")
Optional t = (Optional) EMPTY;
return t;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static Optional of(T value) {
return new Optional<>(value);
}
public static Optional 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 filter(Predicate super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
public Optional map(Function super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
public Optional flatMap(Function super T, Optional> 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 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 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 orders) {
return new CompoundOrder(orders);
}
public static Terms.Order compound(Terms.Order... orders) {
return compound(Arrays.asList(orders));
}
protected abstract Comparator 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 orders) {
return new CompoundOrder(orders);
}
public static BucketOrder compound(BucketOrder... orders) {
return compound(Arrays.asList(orders));
}
public abstract Comparator 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> variableMapPrototypes = new ConcurrentHashMap();
private final ApplicationContext applicationContext;
public SpringWebContext(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, Locale locale, Map variables, ApplicationContext appctx) {
super(request, response, servletContext, locale, addSpringSpecificVariables(variables, appctx));
this.applicationContext = appctx;
}
private static Map addSpringSpecificVariables(Map variables, ApplicationContext appctx) {
HashMap 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 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中经常用到Security
的Md5PasswordEncoder
进行密码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
包中
SpringBoot 2.x的org.springframework.boot.autoconfigure.web
包中
其中的ErrorAttributes
,ErrorController
,DefaultErrorAttributes
在SpringBoot 2.x的时候都转到org.springframework.boot.web.servlet.error
包中
ErrorAttributes
接口源码对比
SpringBoot 1.5
public interface ErrorAttributes {
Map getErrorAttributes(RequestAttributes var1, boolean var2);
Throwable getError(RequestAttributes var1);
}
SpringBoot2.x
public interface ErrorAttributes {
Map 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 getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map 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 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 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 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 errorAttributes, Throwable error) {
StringWriter stackTrace = new StringWriter();
error.printStackTrace(new PrintWriter(stackTrace));
stackTrace.flush();
errorAttributes.put("trace", stackTrace.toString());
}
private void addPath(Map 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 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 getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map 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 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 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 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 errorAttributes, Throwable error) {
StringWriter stackTrace = new StringWriter();
error.printStackTrace(new PrintWriter(stackTrace));
stackTrace.flush();
errorAttributes.put("trace", stackTrace.toString());
}
private void addPath(Map 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 getAttribute(RequestAttributes requestAttributes, String name) {
return requestAttributes.getAttribute(name, 0);
}
}
相关例子
相关代码
public Object errorApiHandler(HttpServletRequest request,boolean includeStackTrace) {
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
Map 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 attr = this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
......
}