- 自动扫描配置不需要扫描某个包或者某个类,下面的写法不会扫描org.xxx.yyy.*下的包以及MyClassToExclude.class
@ComponentScan(
excludeFilters = {
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "org.xxx.yyy.*"),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = MyClassToExclude.class) })
2. Multipart上传文件,过一段时间以后将会出现org.springframework.web.multipart.MultipartException,解决办法在大神的博客中,增加-Djava.io.tmpdir启动参数,或者修改程序new MultipartConfigFactory().setLocation("/app/pttms/tmp"),我采用的是在配置文件中增加server.tomcat.basedir=${user.home}/deployer/tomcat
3.弹出浏览器自带的用户名和密码框, 输入用户名和密码以后每次请求在Header中都会自动带上Authorization: Basic Base64.encode("username:password")
public static void ret401(HttpServletRequest request, HttpServletResponse response){
String serverName = request.getServerName();
response.setStatus(401);
response.setHeader("Cache-Control", "no-store");
response.setDateHeader("Expires", 0);
response.setHeader("WWW-authenticate", "Basic Realm=\"" + serverName + "\"");
}
4.RestTemplate的坑
RestTemplate会对url会自动进行编码,如果url的参数已经编码过就会出现重复编码, 并且带有特殊符号的参数自动编码的结果和URLEncoder.encode编码的结果并不是完全一致
解决办法如下: 如果url的参数已经Encode过了则不再重新编码, UricomponentsBuilder.fromHttpUrl(url).build(true).toUri()或者new URI(url)
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
// url的参数已经编码过了,所以不需要再次编码
ResponseEntity<String> responseEntity = restTemplate.getForEntity(builder.build(true).toUri(), String.class);
或者
ResponseEntity<String> responseEntity = restTemplate.getForEntity(new URI(url), String.class);
5. 日志冲突问题
经常出现log4j和logback产生冲突的错误,看以下几篇文章基本都能解决
最终只能使用一种具体日志框架打印slf4j接口日志,排除多余的日志框架然后用log4j-over-slf4、jcl-over-slf4j等依赖包将依赖jar包中的日志转接到slf4j中进行打印
slf4j、jcl、jul、log4j1、log4j2、logback大总结
6. logback.xml根据环境切换日志配置
需求背景: 公司要求把所有的错误日志统一搜集到阿里云日志loghub中,但是开发环境明显会有很多错误不需要上传
方法一: 创建不同环境的文件夹,每个文件夹下都有一个logback.xml,用maven的resources配置引入logback.xml
<build>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources/${profiles.active}</directory>
</resource>
</resources>
...
</build>
方法二:使用logback-spring.xml
application.properties文件中加入
logging.config=classpath:logback-spring.xml
logback-spring.xml
<springProfile name="prod">
<root>
<level value="INFO"/>
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
<appender-ref ref="loghub" />
</root>
</springProfile>
<springProfile name="dev,test,uat">
<root>
<level value="INFO"/>
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</springProfile>
方法三: 自定义LoggerStartupListener类, 缺点是还需要配置系统变量或者环境变量
参考文章: 根据环境变量切换logback配置
System.getenv 获取系统变量
System.getProperties获取环境变量,启动命令中-Dspring.profiles.active=dev可以通过getProperties拿到
--spring.profiles.active=dev 只能通过main方法的args拿到
可以在main方法中把启动变量塞到环境变量中,然后在LoggerStartupListener通过System.getProperties("spring.profiles.active")拿到当前环境变量
public static void main(String[] args) {
for(String arg: args){
if(arg.startsWith("--spring.profiles.active")){
System.getProperties().put("spring.profiles.active", arg.replace("--spring.profiles.active=",""));
}
}
...
}
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.LoggerContextListener;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.spi.LifeCycle;
import org.apache.commons.lang.StringUtils;
public class LoggerStartupListener extends ContextAwareBase implements LoggerContextListener, LifeCycle {
private boolean isStarted = false;
@Override
public void start() {
if(isStarted){
return;
}
String profile = System.getProperty("spring.profiles.active","dev");
Context context = getContext();
context.putProperty("ACTIVE_PROFILE", profile);
isStarted = true;
}
}
方法四:使用logback.{env}.xml, 参照一种Spring下的logback多profile配置的暴力流解决方案
方法五:使用logback.{env}.xml, 参照一种相对优雅的Spring下logback多profile配置解决方案
方法六: 推荐,一个logback.xml,一个logback-loghub.xml文件, loghub的配置在logback-loghub.xml文件中
只需要在application-prod.properties中增加一条配置:logging.config=classpath:logback-loghub.xml
7.Memcached连接池问题
com.danga.Memcached和spymemcached的key哈希规则不一样,同一个公司的系统最好统一使用一种连接池框架
com.danga.Memcached的默认哈希规则比较简单,就是key.hashCode()%serverCount
private final long getBucket(String key, Integer hashCode) {
long hc = getHash(key, hashCode);
if (this.hashingAlg == CONSISTENT_HASH) {
return findPointFor(hc);
} else {
long bucket = hc % buckets.size();
if (bucket < 0)
bucket *= -1;
return bucket;
}
}
private final long getHash(String key, Integer hashCode) {
if (hashCode != null) {
if (hashingAlg == CONSISTENT_HASH)
return hashCode.longValue() & 0xffffffffL;
else
return hashCode.longValue();
} else {
switch (hashingAlg) {
case NATIVE_HASH:
return (long) key.hashCode();
case OLD_COMPAT_HASH:
return origCompatHashingAlg(key);
case NEW_COMPAT_HASH:
return newCompatHashingAlg(key);
case CONSISTENT_HASH:
return md5HashingAlg(key);
default:
// use the native hash as a default
hashingAlg = NATIVE_HASH;
return (long) key.hashCode();
}
}
}
8.获取resources下静态文件的问题
打包成jar包使用内嵌tomcat容器的时候,只能拿到某个文件的文件流InputStream,拿不到文件对象File
// 方法一
ClassPathResource classPathResource = new ClassPathResource("1.txt");
InputStream inputStream = classPathResource.getInputStream();// 能够拿到文件流,但是classPathResource.getFile()拿不到文件
// 方法二
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("1.txt");
// 方法三
InputStream inputStream = this.getClass().getResourceAsStream("1.txt");
// 如果要遍历一个目录下所有的文件,先从InputStream中得到所有的文件名,拼接每个文件的完整路径,再重新获取每个文件的文件流
String directoryPath = "dict";
List<String> files = IOUtils.readLines(new ClassPathResource(directoryPath).getInputStream()); // 返回的是目录下所有文件或者文件夹的名字
for(String fileName:files){
InputStream fileInputStream = new ClassPathResource(directoryPath+File.separator+fileName).getInputStream()
}