在 HTTP 协议的请求头中,有一个很重要的属性:Content-Type,它的作用是:
作为请求:告诉服务器,客户端发送的数据类型。
作为响应:告诉客户端,服务器响应的数据类型。
客户端或者服务器拿到数据后,通过该数据类型,就可以正确的解析数据。
由于 GET 请求不存在请求体部分,它的参数都是拼接在 URL 尾部,浏览器把数据转换成一个字符串(key1=value1&key2=value2…),然后把这个字符串追加到 URL 后面,用 ? 分割,因此 GET 请求的请求头不需要设置 Content-Type 字段。
所以下面我们所讲的都是针对 POST 请求而言。
Content-Type 属性的值有很多种,下面列举几种最常见的:
这是最常见的数据类型,前端表单默认使用的就是这种类型。
比如有这么一个登录表单:
<form action="login.do" enctype="application/x-www-form-urlencoded" method="post">
<input type="text" name="username" />
<input type="password" name="password" />
<button type="submit">登录</button>
</form>
注意 “enctype” 属性,该属性规定在发送到服务器之前,浏览器应该如何对表单数据进行编码。默认是 “application/x-www-form-urlencoded”。
当我们点击 “登录” 按钮时,浏览器会将所有内容进行编码,拼接成 “key=value” 的格式,也就是键值对的格式。多个键值对之间用 “&” 分割。
当我们提交时,在 Chrome 浏览器中,可以看到请求体的数据格式是这样子的:
可以看到浏览器帮我们拼接成了键值对的格式,key 就是输入框的 name,而 value 就是我们输入的内容。
当服务器接收到数据后,根据 Content-Type 属性获取到数据类型,就能正确的解析数据。
接下来是后台代码:
@RequestMapping(value = "/login.do", method = RequestMethod.POST)
public String login(@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password) {
return "index.jsp";
}
当 Spring MVC 收到客户端发送来的数据时,根据 Content-Type 声明的数据类型,通过使用 HandlerAdapter 配置的 HttpMessageConverters 来解析请求体中的数据,然后绑定到相应的 bean 上,bean 的类型、个数和顺序跟我们方法声明的参数是一样的。这样后台就接收到了数据。
这也是一种很常见的数据类型,这种类型的数据是序列化后的 JSON 字符串。
比如有这么一个登录表单:
<form>
<input type="text" name="username" />
<input type="password" name="password" />
<button type="button">登录</button>
</form>
后台代码如下:
@RequestMapping(value = "/login.do", method = RequestMethod.POST)
@ResponseBody
public String login(@RequestBody User user) {
return "login success.";
}
处理 application/json 编码的数据时,必须使用 @RequestBody 注解,Spring MVC 通过使用 HandlerAdapter 配置的 HttpMessageConverters 来解析请求体中的数据,然后绑定到 @RequestBody 注解的对象上。
AJAX 代码如下:
var username = $("input[name='username']").val();
var password = $("input[name='password']").val();
var data = {
username: username,
password: password
}
$.ajax({
type: "post",
url: "/login.do",
contentType: "application/json",
data: JSON.stringify(data),
success: function(data){
},
error: function(data){
}
});
JQuery 的 AJAX 默认传递的是 application/x-www-form-urlencoded 类型的数据,如果想传递 JSON 类型,则需指定 contentType: application/json。
由于 application/json 类型的数据是序列化后的 JSON 字符串,所以我们也必须手动把 JSON 对象序列化成字符串,我们可通过 JSON.stringify(data) 方法进行序列化。
通过 Chrome 浏览器可以看到请求体的数据格式是这样子的:
在最初的 http 协议中,没有上传文件方面的功能。后来为了支持文件上传,提高二进制文件的传输效率,新增了 multipart/form-data 数据类型。
比如有这么一个登录表单:
<form action="login.do" enctype="multipart/form-data" method="post">
<input type="text" name="username" />
<input type="password" name="password" />
<input type="file" name="avatar">
<button type="submit">登录</button>
</form>
表单的 enctype 属性必须指定为 multipart/form-data,否则不能上传文件。
后台代码如下:
@RequestMapping(value = "/login.do", method = RequestMethod.POST)
public String login(String username, String password, MultipartFile avatar) {
return "index.jsp";
}
MultipartFile 这个类是用来接受前台传过来的文件。
重点是 multipart/form-data 数据类型的请求体,这种数据类型的请求体跟前面两个完全不一样。
通过 Chrome 浏览器我们可以看到请求体的数据格式,请看下图:
首先先看 Content-Type 属性,它的值是:multipart/form-data; boundary=—-WebKitFormBoundaryxJ5HRAtPAwUo1RsG
multipart/form-data 是我们在表单里指定的数据类型,这个是必须的。
那么这个 boundary=—-WebKitFormBoundaryxJ5HRAtPAwUo1RsG 又是什么呢?
boundary 是边界符,作用是用来分割多个表单项和文件。它是浏览器自动帮我们加上的,边界符的值不是固定的,可以随便取,边界符也是必须的。
通过上图,我们来分析一下请求体的数据结构。
首先先看一个公式:
分割符 = "--" + 边界符
结束符 = "--" + 边界符 + "--"
根据上面的公式,我们可以得出分割符和结束符的值:
分割符:------WebKitFormBoundaryxJ5HRAtPAwUo1RsG
结束符:------WebKitFormBoundaryxJ5HRAtPAwUo1RsG--
先分析第一个表单项:
------WebKitFormBoundary8vr5fnZ5TkqDW6kZ
Content-Disposition: form-data; name="username"
zhangsan
第一行是分割符,但是仅仅有分割符是不够的,这样的数据格式还是不对,服务器还是无法解析数据。通过查资料得知,分割符必须以回车符(\r)+换行符(\n)结尾。
也就是说,第一行的内容其实是:分割符 + 回车符 + 换行符。
------WebKitFormBoundaryxJ5HRAtPAwUo1RsG\r\n
接下来看第二行,第二行的作用是告诉服务器对应字段(表单)的相关信息,第二行也必须以 “回车符 + 换行符” 结尾。所以第二行的内容其实是:
Content-Disposition: form-data; name="username"\r\n
接下来看第三行,第三行虽然没有显示任何内容,但第三行的内容其实是 “回车符 + 换行符”,只是不显示而已。
也就是说第三行的内容其实是:
\r\n
接下来看第四行,第四行是我们输入的表单值了,同样必须以 “回车符 + 换行符” 结尾。
也就是说第四行的内容其实是:
zhangsan\r\n
综上所述,一个完整的文本格式的数据结构应该是这样的:
------WebKitFormBoundary8vr5fnZ5TkqDW6kZ\r\nContent-Disposition: form-data; name="username"\r\n\r\nzhangsan\r\n
接下来再分析一下文件的内容:
------WebKitFormBoundary8vr5fnZ5TkqDW6kZ
Content-Disposition: form-data; name="avatar"; filename=""
Content-Type: application/octet-stream
文件只比文本多了一行 “Content-Type: application/octet-stream” 内容,但其实两者的数据结构其实是一样的:
------WebKitFormBoundary8vr5fnZ5TkqDW6kZ\r\nContent-Disposition: form-data; name="avatar"; filename=""\r\nContent-Type: application/octet-stream\r\n\r\n(文件的二进制数据)\r\n
最后一行是结束符,结束符也必须以 “回车符 + 换行符” 结尾。
所以最后一行的内容其实是:
------WebKitFormBoundary8vr5fnZ5TkqDW6kZ--\r\n
综上所述,请求体的完整数据结构如下图:
学过 JavaScript 的应该都知道,在 js 中的数值型 number 类型中有几个特殊的数,一个正无穷大、一个负无穷大、一个不是一个数 NaN。
后来无意中发现 java 中也有这三个数,不过这三个数是浮点数,不是整数。只有在浮点数中这这三个数才成立。看图分析:
正无穷 Infinity
负无穷 -Infinity
不是一个数 NaN
无穷乘以 0,结果为 NaN。
System.out.println(Float.POSITIVE_INFINITY * 0); // NaN
System.out.println(Float.NEGATIVE_INFINITY * 0); // NaN
无穷除以 0,结果还是无穷。
System.out.println(Float.POSITIVE_INFINITY / 0); // Infinity
System.out.println(Float.NEGATIVE_INFINITY / 0); // -Infinity
无穷做任何运算(除了乘以 0),结果还是无穷。
System.out.println(Float.POSITIVE_INFINITY + 10000); // Infinity
System.out.println(Float.POSITIVE_INFINITY - 10000); // Infinity
System.out.println(Float.POSITIVE_INFINITY * 10000); // Infinity
System.out.println(Float.POSITIVE_INFINITY / 10000); // Infinity
System.out.println(Float.NEGATIVE_INFINITY + 10000); // -Infinity
System.out.println(Float.NEGATIVE_INFINITY - 10000); // -Infinity
System.out.println(Float.NEGATIVE_INFINITY * 10000); // -Infinity
System.out.println(Float.NEGATIVE_INFINITY / 10000); // -Infinity
判断一个浮点数是否为无穷,可用 isInfinite() 方法。
System.out.println(Float.isInfinite(Float.POSITIVE_INFINITY)); // true
System.out.println(Float.isInfinite(Float.NEGATIVE_INFINITY)); // true
判断一个浮点数是否为 NaN,可用 isNaN() 方法。
System.out.println(Float.isNaN(Float.NaN)); // true
System.out.println(Double.isNaN(Double.NaN)); // true
NaN 表示非数字,它与任何值都不相等,甚至不等于它自己。
System.out.println(Float.NaN == Float.NaN); // false
System.out.println(Double.NaN == Double.NaN); // false
参考资料:
https://www.cnblogs.com/zhizhixiaoxia/p/10756317.html
https://www.cnblogs.com/zhisuoyu/archive/2016/03/24/5314541.html
认证流程如下图所示:
根据流程图实现的伪代码:
1.认证拦截器
/**
* 用于处理来自表单提交的认证拦截器,Spring Security 会默认提供
* <p> 父类已经实现了 doFilter() 方法,我们只需要实现 attemptAuthentication() 方法,
* 进行身份认证就可以了
*
*/
class MyUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
protected MyUsernamePasswordAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
// 执行实际的身份认证
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
// 客户端提交的用户名
String username = request.getParameter("username");
// 客户端提交的密码
String password = request.getParameter("password");
// 封装成一个未认证的 Authentication 对象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// 调用认证管理器进行认证,返回一个已经认证的 Authentication 对象
return this.getAuthenticationManager().authenticate(authRequest);
}
}
2.认证管理器
/**
* 认证管理器,Spring Security 会默认提供
*/
class MyAuthenticationManager implements AuthenticationManager {
// 认证提供者(可以有多个)
private List<AuthenticationProvider> providers = Collections.emptyList();
public MyAuthenticationManager(List<AuthenticationProvider> providers) {
this.providers = providers;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
Authentication result = null;
for (AuthenticationProvider provider : providers) {
if (!provider.supports(toTest)) {
continue;
}
// 调用认证提供者进行认证,如果 result 不为 null ,说明认证通过
result = provider.authenticate(authentication);
if (result != null) {
break;
}
}
if (result == null) {
throw new ProviderNotFoundException("ProviderManager.providerNotFound");
}
return result;
}
}
3.认证提供者
/**
* 认证提供者,对用户的信息进行认证,并返回一个完整的 Authentication 对象,Spring Security 会默认提供
*
* <p>认证提供者需要实现 AuthenticationProvider 接口,该接口定义了一个认证的方法 authenticate()。
* <p>由于 Spring Security 提供了一个抽象类 AbstractUserDetailsAuthenticationProvider,
* 并且它已经实现了 authenticate() 方法,所以我们不需要自己实现,直接继承这个抽象类即可。
*
*/
class MyAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
// 认证的 Service
private UserDetailsService userDetailsService;
// 密码认证处理器
private PasswordEncoder passwordEncoder;
public MyAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
this.userDetailsService = userDetailsService;
this.passwordEncoder = passwordEncoder;
}
/**
* 这个方法用于添加额外的检查功能,我们不需要添加,所以空着即可
*/
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
}
/**
* 这个方法很重要,用于认证用户提供的信息是否正确,
* 并且返回一个 UserDetails 对象,父类的 authenticate() 方法会用到这个对象
*/
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// 调用认证服务接口,加载 UserDetails 对象
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails == null) {
throw new UsernameNotFoundException(username);
}
// 判断用户名和密码是否正确,如果正确直接返回
if (userDetails.getUsername().equals(authentication.getPrincipal().toString())
&& passwordEncoder.isPasswordValid(userDetails.getPassword(), authentication.getCredentials().toString(), null)) {
return userDetails;
}
throw new BadCredentialsException("username: " + username + ", credentials: " + authentication.getCredentials());
}
}
4.认证服务
/**
* 认证服务,一般需要我们提供
*
* <p>认证服务需要实现 UserDetailsService 接口,该接口只定义了一个方法 loadUserByUsername()
* <p>这里就是我们的认证逻辑了,可以根据 username 去数据库查找用户的信息,角色以及权限,然后包装成 UserDetails 对象,
* 供后续使用
*/
class MyUserDetailsService implements UserDetailsService {
/**
* 这里应该查数据库来组装 UserDetails 对象,
* 为了演示方便我直接写死了
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 存放用户的角色或者权限
List<GrantedAuthority> authorities = new ArrayList<>();
// 角色的名字以 "ROLE_" 开头
SimpleGrantedAuthority authority1 = new SimpleGrantedAuthority("ROLE_ADMIN");
// 权限的名字没有限制,起啥都行
SimpleGrantedAuthority authority2 = new SimpleGrantedAuthority("read");
// 添加角色
authorities.add(authority1);
// 添加权限
authorities.add(authority2);
// 构造 User 对象并返回,构造参数的含义注释都有写
return new org.springframework.security.core.userdetails.User(username, "64c8b1e43d8ba3115ab40bcea57f010b", true, true, true,
true, authorities);
}
}
5.密码认证处理器
/**
* 密码认证处理器,如果你数据库里的密码是加密的话,还需要提供一个密码认证处理器
*
* <p>构造方法接收一个 String 类型的参数,用来指定加密的类型
* <p>也就是说你数据库里的密码是用什么加密的,这里就传入什么,后面的认证提供者会用到
*/
class MyMessageDigestPasswordEncoder extends MessageDigestPasswordEncoder {
public MyMessageDigestPasswordEncoder(String algorithm) {
super(algorithm);
}
/**
* @param encPass 数据库密码(通常是加密的)
* @param rawPass 前端传送过来的密码(通常是明文的)
* @param salt 盐值
*/
@Override
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
// 此处应该根据你自己的加密规则来校验密码
// 为了演示方便,我这里只简单的进行比较
return Objects.equals(encPass, rawPass);
}
}
伪代码执行的流程图如下:
调用堆栈如下:
SpringSecurity 踩坑了,遇到了无限循环认证的问题。具体是认证失败后,我想转发到登录页面,并且返回一些提示信息,结果就踩坑了。
我是 SpringMVC 4.1.7,SpringSecurity 4.0.4
这是 xml 配置
<security:form-login login-page="/admin/login"
login-processing-url="/admin/login"
username-parameter="username"
password-parameter="password" />
我配置了登录页面是 /admin/login
,登录表单提交的地址是 /admin/login
当认证失败时,SpringSecurity 默认的失败处理器是 SimpleUrlAuthenticationFailureHandler
类,它默认采用重定向的方式,重定向到 login-page
设置的 URL,也就是重定向到登录页面。
然后我想在认证失败后,根据失败信息返回给客户端一些友好的提示。但是采用重定向的方式,我的提示信息就丢失了,客户端无法收到提示信息。所以我想将重定向的方式改成转发的方式。
这是我改后的 xml 配置
<security:form-login login-page="/admin/login"
login-processing-url="/admin/login"
username-parameter="username"
password-parameter="password"
authentication-failure-handler-ref="simpleUrlAuthenticationFailureHandler"/>
<bean id="simpleUrlAuthenticationFailureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<constructor-arg name="defaultFailureUrl" value="/admin/login"></constructor-arg>
<property name="useForward" value="true" />
</bean>
我手动配置了 SimpleUrlAuthenticationFailureHandler
类,将 useForward
属性设置为 true
,表示启用转发的方式。defaultFailureUrl
属性设置为 /admin/login
,表示转发到登录页面。这里说明一下,defaultFailureUrl
属性是认证失败后,重定向或者转发的 URL。如果不设置,默认是 login-page
的值。
重启后发现果然采用了转发的方式,但是这样又遇到了一个问题,转发到 /admin/login
之后,又进入了认证的方法,认证失败后又转发到 /admin/login
,然后又进入认证的方法,一直无限循环下去。。。
仔细想了一下,应该是我的配置出了问题
我配置的登录页是 /admin/login
,请求方法是 GET
,登录表单提交的地址是 /admin/login
,请求方式是 POST
,也就是说登录页面和登录提交的地址都是同一个
,唯一的区别是请求方法不同。
转发之前的地址是 admin/login
,请求方法是 POST
,由于转发是同一个请求,它的请求方法不会变,所以转发的请求方法也是 POST
。又由于我设置的转发 URL 是admin/login
,所以转发之后又会进入认证的方法,相当于重新提交了登录表单。所以无论如何都不会进入到登录页面,所以就会造成无限循环。
这里也特别说明一下,为什么默认的重定向不会造成无限循环。因为重定向是客户端的行为,相当于客户端重新发起请求,并且重定向肯定是 GET
请求,所以会正常进入到登录页面,不会造成无线循环。
最后的 xml 配置如下:
<security:form-login login-page="/admin/login.jsp"
login-processing-url="/admin/login"
username-parameter="username"
password-parameter="password"
authentication-failure-handler-ref="simpleUrlAuthenticationFailureHandler"/>
<bean id="simpleUrlAuthenticationFailureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<constructor-arg name="defaultFailureUrl" value="/admin/login.jsp"></constructor-arg>
<property name="useForward" value="true" />
</bean>
结论:login-page
和 login-processing-url
最好不要设置相同的 URL
我的 SpringMVC 版本是 4.1.7,SpringSecurity 版本是 4.1.5
在 SpringMVC 里集成 SpringSecurity 后,运行出错:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityFilterChainRegistration' defined in class path resource [org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [javax.servlet.Filter]: : Error creating bean with name 'org.springframework.security.filterChainProxy': Cannot resolve reference to bean 'org.springframework.security.filterChains' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChains': Cannot resolve reference to bean 'org.springframework.security.web.DefaultSecurityFilterChain#0' while setting bean property 'sourceList' with key [0]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.DefaultSecurityFilterChain#0': Cannot create inner bean '(inner bean)#3da73b75' of type [org.springframework.security.web.util.matcher.AntPathRequestMatcher] while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#3da73b75': Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.util.matcher.AntPathRequestMatcher]: Constructor threw exception; nested exception is java.lang.NoSuchMethodError: org.springframework.util.AntPathMatcher.setCaseSensitive(Z)V; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChainProxy': Cannot resolve reference to bean 'org.springframework.security.filterChains' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChains': Cannot resolve reference to bean 'org.springframework.security.web.DefaultSecurityFilterChain#0' while setting bean property 'sourceList' with key [0]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.DefaultSecurityFilterChain#0': Cannot create inner bean '(inner bean)#3da73b75' of type [org.springframework.security.web.util.matcher.AntPathRequestMatcher] while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#3da73b75': Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.util.matcher.AntPathRequestMatcher]: Constructor threw exception; nested exception is java.lang.NoSuchMethodError: org.springframework.util.AntPathMatcher.setCaseSensitive(Z)V
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:464)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1119)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1014)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.boot.context.embedded.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:176)
at org.springframework.boot.context.embedded.ServletContextInitializerBeans.addServletContextInitializerBeans(ServletContextInitializerBeans.java:80)
at org.springframework.boot.context.embedded.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:68)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.getServletContextInitializerBeans(EmbeddedWebApplicationContext.java:216)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext$1.onStartup(EmbeddedWebApplicationContext.java:202)
at org.springframework.boot.context.embedded.tomcat.ServletContextInitializerLifecycleListener.lifecycleEvent(ServletContextInitializerLifecycleListener.java:64)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5095)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1409)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1399)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChainProxy': Cannot resolve reference to bean 'org.springframework.security.filterChains' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChains': Cannot resolve reference to bean 'org.springframework.security.web.DefaultSecurityFilterChain#0' while setting bean property 'sourceList' with key [0]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.DefaultSecurityFilterChain#0': Cannot create inner bean '(inner bean)#3da73b75' of type [org.springframework.security.web.util.matcher.AntPathRequestMatcher] while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#3da73b75': Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.util.matcher.AntPathRequestMatcher]: Constructor threw exception; nested exception is java.lang.NoSuchMethodError: org.springframework.util.AntPathMatcher.setCaseSensitive(Z)V
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:634)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:140)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1139)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1042)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
... 25 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChains': Cannot resolve reference to bean 'org.springframework.security.web.DefaultSecurityFilterChain#0' while setting bean property 'sourceList' with key [0]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.DefaultSecurityFilterChain#0': Cannot create inner bean '(inner bean)#3da73b75' of type [org.springframework.security.web.util.matcher.AntPathRequestMatcher] while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#3da73b75': Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.util.matcher.AntPathRequestMatcher]: Constructor threw exception; nested exception is java.lang.NoSuchMethodError: org.springframework.util.AntPathMatcher.setCaseSensitive(Z)V
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveManagedList(BeanDefinitionValueResolver.java:382)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:157)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1477)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1222)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
... 41 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.DefaultSecurityFilterChain#0': Cannot create inner bean '(inner bean)#3da73b75' of type [org.springframework.security.web.util.matcher.AntPathRequestMatcher] while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#3da73b75': Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.util.matcher.AntPathRequestMatcher]: Constructor threw exception; nested exception is java.lang.NoSuchMethodError: org.springframework.util.AntPathMatcher.setCaseSensitive(Z)V
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:313)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:129)
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:634)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:140)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1139)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1042)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
... 53 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#3da73b75': Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.util.matcher.AntPathRequestMatcher]: Constructor threw exception; nested exception is java.lang.NoSuchMethodError: org.springframework.util.AntPathMatcher.setCaseSensitive(Z)V
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:275)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1139)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1042)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:299)
... 65 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.util.matcher.AntPathRequestMatcher]: Constructor threw exception; nested exception is java.lang.NoSuchMethodError: org.springframework.util.AntPathMatcher.setCaseSensitive(Z)V
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:163)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:122)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:267)
... 70 more
Caused by: java.lang.NoSuchMethodError: org.springframework.util.AntPathMatcher.setCaseSensitive(Z)V
at org.springframework.security.web.util.matcher.AntPathRequestMatcher$SpringAntMatcher.createMatcher(AntPathRequestMatcher.java:291)
at org.springframework.security.web.util.matcher.AntPathRequestMatcher$SpringAntMatcher.<init>(AntPathRequestMatcher.java:275)
at org.springframework.security.web.util.matcher.AntPathRequestMatcher$SpringAntMatcher.<init>(AntPathRequestMatcher.java:268)
at org.springframework.security.web.util.matcher.AntPathRequestMatcher.<init>(AntPathRequestMatcher.java:134)
at org.springframework.security.web.util.matcher.AntPathRequestMatcher.<init>(AntPathRequestMatcher.java:101)
at org.springframework.security.web.util.matcher.AntPathRequestMatcher.<init>(AntPathRequestMatcher.java:87)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
... 72 more
原因是 spring-core-4.1.x
不存在 AntPathMatcher
的 setCaseSensitive()
方法,请参阅 4.1.X.RELEASE
上的 AntPathMatcher
和 4.2.X.RELEASE
上的 AntPathMatcher
。
解决方法:使用 spring-core-4.2.x
版本,或者降低 SpringSecurity
的版本。
我采用的是第二种方法,将 4.1.5
改成 4.0.4
,这是我修改后的 pom.xml
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.0.4.RELEASE</version >
</dependency>
遇到一个诡异的问题,MyBatis 根据主键 ID 删除数据,不报错,SQL 也正常打印出来,但是数据并没有删除。拎 SQL 到数据库中执行,却能正常执行,能把数据删除。
MyBatis 打印的 SQL,好像没啥问题
但是数据还在,并没有删除
拎 SQL 到数据库中执行,却能正常执行,能把数据删除。
这真是太奇怪了
一开始想的会不会是 MyBatis 没有 commit,但是仔细想,MyBatis 不是自动 commit 的吗,insert 和 update 都能正常执行,怎么 delete 反而没效果呢?
debug 看看
差点吐血。。。原来是 ID 前面多了一个空格。。。
改了之后果然能删除了
这种错误真的是低级而又不容易发现
form 表单提交数据映射到后台对象,中文出现乱码
解决方法如下:
1.application.properties 文件配置字符编码
spring.banner.charset=UTF-8
spring.messages.encoding=UTF-8
spring.http.encoding.charset=UTF-8
spring.http.encoding.force=true
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8
2.配置 CharacterEncodingFilter
之前 SpringMVC 是在 web.xml 里配置的
<filter>
<description>字符集过滤器</description>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<description>字符集编码</description>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
SpringBoot 的话直接注入 bean 即可
@Configuration
public class CharacterEncodingFilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setForceEncoding(true);
characterEncodingFilter.setEncoding("UTF-8");
registrationBean.setFilter(characterEncodingFilter);
return registrationBean;
}
}
重启之后,重新提交表单,中文就可以正常显示了
BaseException 是我自定义的业务异常类,包含错误码和错误信息。
public class BaseExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception e) {
String contentType = request.getContentType();
// 返回 json
if (Objects.equals("application/json", contentType)) {
// 使用这个类可以返回 json 数据
MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
jsonView.setExtractValueFromSingleKeyModel(true);
ModelAndView mv = new ModelAndView(jsonView);
if (e instanceof BaseException) {
BaseException be = (BaseException) e;
response.setStatus(be.getHttpCode());
mv.addObject(new Result<>(be.getErrorCode(), be.getMessage()));
return mv;
} else {
response.setStatus(BaseErrorCodeEnum.INTERNAL_ERROR.getHttpCode());
mv.addObject(new Result<>(BaseErrorCodeEnum.INTERNAL_ERROR.getErrorCode(),
BaseErrorCodeEnum.INTERNAL_ERROR.getMessage()));
return mv;
}
} else {
// 返回页面
ModelAndView mv = new ModelAndView();
if (e instanceof BaseException) {
BaseException be = (BaseException) e;
response.setStatus(be.getHttpCode());
mv.addObject("exception", be.getMessage());
mv.addObject("errorCode", be.getErrorCode());
mv.setViewName("/default/front/error/error");
return mv;
} else {
response.setStatus(BaseErrorCodeEnum.INTERNAL_ERROR.getHttpCode());
mv.addObject("exception", BaseErrorCodeEnum.INTERNAL_ERROR.getMessage());
mv.addObject("errorCode", BaseErrorCodeEnum.INTERNAL_ERROR.getErrorCode());
mv.setViewName("/default/front/error/error");
return mv;
}
}
}
}
这种方式只能处理 Controller 层抛出的异常,对于 DispatcherServlet 抛出的异常无能为力。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(Exception e, HttpServletRequest request, HttpServletResponse response) throws Exception {
String contentType = request.getContentType();
if (Objects.equals("application/json", contentType)) {
MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
jsonView.setExtractValueFromSingleKeyModel(true);
ModelAndView mv = new ModelAndView(jsonView);
if (e instanceof BaseException) {
BaseException be = (BaseException) e;
response.setStatus(be.getHttpCode());
mv.addObject(new Result<>(be.getErrorCode(), be.getMessage()));
return mv;
} else {
response.setStatus(BaseErrorCodeEnum.INTERNAL_ERROR.getHttpCode());
mv.addObject(new Result<>(BaseErrorCodeEnum.INTERNAL_ERROR.getErrorCode(),
BaseErrorCodeEnum.INTERNAL_ERROR.getMessage()));
return mv;
}
} else {
ModelAndView mv = new ModelAndView();
if (e instanceof BaseException) {
BaseException be = (BaseException) e;
response.setStatus(be.getHttpCode());
mv.addObject("exception", be.getMessage());
mv.addObject("errorCode", be.getErrorCode());
mv.setViewName("/default/front/error/error");
return mv;
} else {
response.setStatus(BaseErrorCodeEnum.INTERNAL_ERROR.getHttpCode());
mv.addObject("exception", BaseErrorCodeEnum.INTERNAL_ERROR.getMessage());
mv.addObject("errorCode", BaseErrorCodeEnum.INTERNAL_ERROR.getErrorCode());
mv.setViewName("/default/front/error/error");
return mv;
}
}
}
}
这种方式可以处理 Controller 层和 DispatcherServlet 抛出的异常,所以用这种方式就好了。
DispatcherServlet 中有一个 throwExceptionIfNoHandlerFound 属性,如下图所示:
它的作用是:如果找不到处理该请求的处理程序,是否抛出 NoHandlerFoundException?
它默认是 false,也就是说找不到处理器时不抛出 NoHandlerFoundException 异常,DispatcherServlet 会自己处理。
所以上面那两个全局处理类都无法捕获到 NoHandlerFoundException 异常,也就无法处理 404 错误。
你可以在 application.properties 加上这段参数,将 throwExceptionIfNoHandlerFound 属性设置为 true。
spring.mvc.throw-exception-if-no-handler-found=true
但是这样还是无法起作用,关键是这段代码:
这段代码表明只有找不到处理器,也就是 mappedHandler 为 null 的情况下,才会进入 noHandlerFound 方法。
然而 SpringBoot 会默认配置一个静态资源映射处理器 ResourceHttpRequestHandler,所以不会进入 noHandlerFound 方法。
幸好 SpringBoot 还提供了配置接口,我们只要在 application.properties 里加入以下配置,即可关闭默认的静态资源映射处理器。
spring.resources.add-mappings=false
这样,当 DispatcherServlet 找不到处理器时,又没有默认的静态资源映射处理器,同时我们把 throwExceptionIfNoHandlerFound 属性设置了 true,那么 DispatcherServlet 就会抛出 NoHandlerFoundException 异常。就会被我们上面定义的 GlobalExceptionHandler 类捕获到,返回我们自定义的处理结果。
但是这种方式有一个弊端,就是关闭静态资源映射处理器后,无法访问到静态资源,只适合纯后端接口,没有前端的项目。
如果在不影响静态资源访问的情况下,又能处理 404 错误呢?请看下面这种方式:
SpringBoot 提供了一个接口,名字叫做 ErrorController。这个接口有一个实现类 BasicErrorController,它主要定义了两个方法:
DispatcherServlet 处理一个不存在的请求时,该请求会转发到 /${context-path}/error,并且不会被 ControllerAdvice 拦截。
它默认的页面是这样的:
默认的 JSON 是这样的:
所以,我们可以自己定义一个 BasicErrorController,返回自定义的页面和 JSON 数据。
比如我是这样写的:
@RequestMapping(value = "/error", produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request) {
ModelAndView mv = new ModelAndView();
mv.addObject("exception", BaseErrorCodeEnum.INTERNAL_ERROR.getMessage());
mv.addObject("errorCode", getStatus(request).value());
mv.setViewName("/default/front/error/error");
return mv;
}
@RequestMapping(value = "/error")
@ResponseBody
public ResponseEntity<Result<String>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
return new ResponseEntity<Result<String>>(
new Result<>(String.valueOf(status.value()), BaseErrorCodeEnum.INTERNAL_ERROR.getMessage()), status);
}
这时候 ErrorController 接口应该有两个实现类,一个是 SpringBoot 的,还有一个是自己定义的。
但是这样也不会冲突,因为 SpringBoot 会判断,如果已经存在 ErrorController 的实现类时,它就不会加载 BasicErrorController。
源码链接:
成员变量不具有多态性,请看下面的例子:
public class Super {
public int field = 0;
public int getField() {
return field;
}
}
public class Sub extends Super {
public int field = 1;
public int getField() {
return field;
}
public int getSuperField() {
return super.field;
}
}
public static void main(String[] args) {
Super sup = new Sub();
System.out.println("sup.field = " + sup.field + ", sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " + sub.field + ", sub.getField() = " + sub.getField()
+ ", sub.getSuperField() = " + sub.getSuperField());
}
输出结果:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
本例中,Super 和 Sub 各有一个成员变量 field,当 Sub 继承 Super 后,Sub 实际包含两个名为 field 的成员变量:一个是它自己的,还有一个是从 Super 那里继承得来的。尽管这两个成员变量的名字相同,但是它们不是同一个变量,因为它们在内存中分配了不同的存储空间。当 Sub 对象向上转型为 Super 引用时,任何成员变量的访问操作都将由编译器解析。所以使用 Super 类型的引用访问 field 变量时,指向的是 Super 类中的 field。同理,使用 Sub 类型的引用访问 field 变量时,指向的是 Sub 类中的 field。你如果想访问从 Super 那里继承来的 field 时,必须使用 super 关键字,显示地调用 suepr.field。
虽然这看起来有些混淆,但是实际中,这些问题是不会发生的。首先,你通常会将所有的成员变量的访问权限都设置成 private,因此你无法直接访问它们,而是通过提供的 get 方法访问。另外,你通常不会对子类和父类的变量设置相同的名字,因为这种做法本身就容易令人混淆。
静态方法也不具有多态性,请看下面的例子:
public class Super {
public static String staticGet() {
return "Super staticGet()";
}
public String dynamicGet() {
return "Super dynamicGet()";
}
}
public class Sub extends Super {
public static String staticGet() {
return "Sub staticGet()";
}
public String dynamicGet() {
return "Sub dynamicGet()";
}
}
public static void main(String[] args) {
Super sup = new Sub();
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
输出结果:
Super staticGet()
Sub dynamicGet()
静态方法是与类,而非某个对象相关联的,所以静态方法不具有多态性。
用git pull来更新代码的时候,遇到了下面的问题:
$ git pull
Updating 725dd43..c34cb92
error: Your local changes to the following files would be overwritten by merge:
src/main/java/cn/roothub/bbs/common/dao/jdbc/builder/DataSourceBuilder.java
Please commit your changes or stash them before you merge.
Aborting
出现这个问题的原因是其他人修改了 xxx.java 并提交到版本库中去了,而你本地也修改了xxx.java,这时候你进行 git pull 操作就好出现冲突了。
有两种解决方法:
1、保留本地的修改
git stash
git pull
git stash pop
通过 git stash 将工作区恢复到上次提交的内容,同时备份本地所做的修改,之后就可以正常 git pull 了,git pull 完成后,执行 git stash pop 将之前本地做的修改应用到当前工作区。
git stash: 备份当前的工作区的内容,从最近的一次提交中读取相关内容,让工作区保证和上次提交的内容一致。同时,将当前的工作区内容保存到Git栈中。
git stash pop: 从Git栈中读取最近一次保存的内容,恢复工作区的相关内容。由于可能存在多个 Stash 的 内容,所以用栈来管理,pop 会从最近的一个 stash 中读取内容并恢复。
git stash list: 显示 Git 栈内的所有备份,可以利用这个列表来决定从那个地方恢复。
git stash clear: 清空 Git 栈。此时使用 gitg 等图形化工具会发现,原来 stash 的哪些节点都消失了。
2、放弃本地修改
这种方法会丢弃本地修改的代码,而且不可找回。
git reset --hard
git pull
通过上述的方法 pull 之后,可能还会报这个错误:
error: The following untracked working tree files would be overwritten by merge:
src/main/java/wang/miansen/roothub/common/dto/BaseDTO.java
Please move or remove them before you merge.
Aborting
这是由于一些 untracked working tree files(未跟踪的文件)引起的问题。
可以执行这个命令删除这些未跟踪的文件:
# 首先确认要删除的文件
git clean -fd -n
# 如果以上命令给出的文件列表是你想删除的, 那么接下来执行
git clean -d -fx
对于这个命令 git clean -d -fx
,-f表示文件 -d表示目录, 如果还要删除.gitignore中的文件那么再加上-x。
命名参考
git删除未跟踪文件
git clean -f
git clean -fd
git clean -fdx
git clean -nf
git clean -nfd
git clean -nfdx