一、spring事件机制的使用
spring中的事件是一种发布-订阅模式,允许组件监听和响应特定的事件,也可以当作观察者模式的一种。其可以用来异步操作,通知,日志记录等。其主要接口和类如下:
ApplicationEvent:事件对象。通过该类,可以自定义需要监听的事件。
ApplicationListener:事件监听类,持有事件对象。通过该类,可以自定义事件的处理
ApplicationEventPublisher:事件发布者类。通过该类,可以在需要的地方触发特定事件
ApplicationContext:事件发布类,发布到applicationContext,applicationContext传递个特定的事件监听类。
其异步记录日志demo如下:
typescript
// 日志事件对象
public class LogEventDemo extends ApplicationEvent {
private String logMessage;
public LogEventDemo(Object source, String logMessage) {
super(source);
this.logMessage = logMessage;
}
public String getLogMessage() {
return logMessage;
}
}
// 日志事件监听类,需要spring进行管理,除此之外,也可以通过@EventListener注解(方法)来实现
public class LogEventListener implements ApplicationListener<LogEventDemo> {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,4,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(3));
@Override
public void onApplicationEvent(LogEventDemo event) {
// 线程池中活跃的线程数量
System.out.println("activeCount:"+pool.getActiveCount());
// 线程池中已经装载的最大数量
System.out.println("largestPoolSize:"+pool.getLargestPoolSize());
BlockingQueue<Runnable> queue = pool.getQueue();
System.out.println("size:"+queue.size());
// 线程池可容纳的最大数量=maximunPoolSize+阻塞队列的容量
pool.execute(()->{
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"日志打印:"+event.getLogMessage());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
// 事件发布者类,需要spring管理
public class Logger {
@Autowired
private ApplicationEventPublisher publisher;
public void log(String message) {
// create and publisher event
LogEventDemo logEvent = new LogEventDemo(this, message);
publisher.publishEvent(logEvent);
}
}
当需要打印日志时,通过调用logger.log()即可实现
二、文件资源获取:Resource
spring中对文件资源的操作都封装在了Resource类中,可根据此类,自定义文件资源的获取。常用的有一个工具类ResourceUtils,读取文件。其用法如下:
ini
// 获取当前项目下的文件
File file = ResourceUtils.getFile("config/1.txt");
System.out.println(file.getAbsolutePath());
if(!file.exists()) {
// 获取类路径下的文件
file = ResourceUtils.getFile("classpath:1.txt");
}
三、@Scheduled定时任务注解
如何动态传入cron表达式:在配置文件中定义,然后在注解当中使用 @scheduled(cron="${属性名:-}"),此表示如果表达式的值为空则不启用任务
四、ServletRequest的使用
前言: servletRequest对象在调用getInputStream()方法后,不能重复使用的问题。这和inputStream的流的读取有关,其内部通过指针实现,在读取完毕后指针并不会rest。
1、数据封装原理(实际使用都是经过springmvc封装好的):
css
1.1:post请求,传参形式为 form-data:直接用实体类,或者在controller上用参数接收,手动可以通过request.getParameter()
1.2:post请求,传参形式为 json时,其需要在实体类之前添加@RequestBody注解或者使用request.getInputStream()获取流数据
2、springmvc的封装
scss
2.1 对于get请求,不做处理,一般参数在url后面
2.2 对于post/put请求,发送 form-data数据,会将其封装成一个StandardMultipartHttpServletReques对象,调用parseRequest(request)对象,对InputStream流进一步封装
2.3 对于post/put请求,发送 application/x-www-form-urlencoded 格式的数据时,会在第一次调用getParameter()或getParameterNames()时,从Inpustream流中读取数据,将其封装到map中
2.4 对于post/put请求,对于发送json格式数据,则会调用getInputStream()或者getReader()获取
3、解决方案
实现Filter接口,将流拷贝一份,即可实现重复使用,在spring项目中通过继承HttpServletRequestWrapper,重写getInputStream()和getReader()方法即可。
第一步:自定义request包装类,继承HttpServletRequestWrapper
第二步:将自定义的包装类,添加到filter链中,将其设为优先级最高的
具体实现代码为如下:
自定义包装类:
public class CustomHttpRequestWrapper extends HttpServletRequestWrapper {
byte[] b;
public CustomHttpRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
b = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
ByteArrayInputStream bis = new ByteArrayInputStream(b);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return bis.read();
}
};
}
}
添加到拦截器链中:
@Component
@Order(-1)
public class CustomFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest request = new CustomHttpRequestWrapper(httpServletRequest);
filterChain.doFilter(request,httpServletResponse);
}
}
五、Graceful Response的使用
graceful Response会自动对响应结果进行封装
源码地址:github.com/feiniaojin/...
文档地址:doc.feiniaojin.com/graceful-re...
maven引入(以springboot2为例,如果是springboot3将版本改为3.2.0-boot3即可):
xml
<dependency>
<groupId>com.feiniaojin</groupId>
<artifactId>graceful-response</artifactId>
<version>3.2.0-boot2</version>
</dependency>
六、断言
对于一些常见的判断,可以通过断言来实现,而不用手动判断,断言类为org.springframework.util.Assert;
七、springboot应用监控 actuator
一、引入:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
二、在配置文件中配置相对应的信息
yaml
management:
endpoints:
web:
exposure:
include: [beans,health,env,info,mappings,httptrace,metrics,scheduledtasks,loggers,] #开放的端点
enabled-by-default: true # false关闭所有端点暴露
endpoint:
health:
show-details: always #此开关不仅可以展示springboot应用,还可以展示第三方应用的健康信息
浏览器访问方式:ip:port/actuator/端点名,该种方式有可能造成信息的泄漏,强烈建议对该url的访问进行验证
八、获取springboot项目启动后的pid
springboot中使用自带的ApplicationPidFileWriter类,可将pid持久化到文件当中,本身不开启,需要手动添加
一、使用方式如下
ini
SpringApplication application = new SpringApplication(StartMain.class);
//将项目启动后的进程pid持久化
application.addListeners(new ApplicationPidFileWriter());
application.run(args);
然后再配置文件中配置文件的位置和名称: spring.pid.file:文件位置和名称,
默认是application.pid,默认与jar包统一目录
二、根据pid文件,进行项目的启停脚本
bash
启动脚本:
nohup java -jar jar包名 > log.out 2>&1 &
停止脚本:
kill -9 $(cat pid文件名称)
echo 'success'
# 删除pid文件
rm -rf pid文件名称
九、springboot打包pom常用配置
xml
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<!--重新打包,剔除其他依赖-->
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<!--打包后的路径,当前根路径下-->
<outputDirectory>${basedir}/${project.artifactId}</outputDirectory>
<!--打包后的包名-->
<finalName>springboot-demo2</finalName>
<!--打包类型,可忽略,类型为大写 ZIP JAR WAR-->
<!-- <layout>JAR</layout>-->
</configuration>
</plugin>
<!--apache打包资源的插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<!-- <version>3.2.0</version>-->
<executions>
<execution>
<!--拷贝资源文件-->
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<!--输出文件到指定目录,比如我这里是config目录-->
<outputDirectory>${basedir}/${project.artifactId}/config/
</outputDirectory>
<resources>
<!--可包含多个resource,里面可以指定需要拷贝的资源目录,然后可以进行包含或者排除操作,最终放到上面指定的目录-->
<resource>
<!--需要拷贝资源文件的目录-->
<directory>src/main/resources/</directory>
<!--包含文件,也可以使用排除excludes-->
<includes>
<include>**/application.properties</include>
<include>**/application.yml</include>
<include>**/logback.xml</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
十、对配置文件中的明文进行加密,解密
使用jasypt工具类可以进行配置文件中信息的加密,解密
使用方法如下:
yaml
1、引入jasypt jar包
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
2、 在配置文件中配置相关信息:
jasypt:
encryptor:
#秘钥
password: 1qaz2wsx!@#
property:
#密文前缀
prefix: ENC( #默认值
#密文后缀
suffix: ) #默认值
# bean: jasyptStrEncryptor #指定加解密的类
其中秘钥可以不放在配置文件中,而是在启动jar包是指定,其方式如下:
java -jar xxx.jar --jasypt.encryptor.password=秘钥
如果jdk为1.8且启动时如果提示以下错误: Encryption raised an exception. A possible cause is you are using strong encryption algorithms and you have not installed the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files in this Java Virtual Machine
解决方案: 这是由于jdk中的安全认证包不支持,可通过替换新的即可,其地址如下: www.oracle.com/technetwork...
需要替换的jar包位置为:jre\lib\security\