Spring事件机制实现解耦
举个例子:用户修改密码,修改完密码后需要短信通知用户,记录关键性日志,等等其他业务操作。
如下图,就是我们需要调用多个服务来进行实现一个修改密码的功能。
使用了事件机制后,我们只需要发布一个事件,无需关心其扩展的逻辑,让我们的事件监听器去处理,从而实现了模块之间的解耦。
定义一个事件
/**
* @Author JiaQIng
* @Description 修改密码事件
* @ClassName UserChangePasswordEvent
* @Date 2023/3/26 13:55
**/
@Getter
@Setter
public class UserChangePasswordEvent extends ApplicationEvent {
private String userId;
public UserChangePasswordEvent(String userId) {
super(new Object());
this.userId = userId;
}
}
监听事件
新建一个事件监听器,注入到Spring容器中,交给Spring管理。在指定方法上添加@EventListener参数为监听的事件。方法为业务代码。使用 @EventListener 注解的好处是一个类可以写很多监听器,定向监听不同的事件,或者同一个事件。
/**
* @Author JiaQIng
* @Description 事件监听器
* @ClassName LogListener
* @Date 2023/3/26 14:22
**/
@Component
public class ListenerEvent {
@EventListener({ UserChangePasswordEvent.class })
public void LogListener(UserChangePasswordEvent event) {
System.out.println("收到事件:" + event);
System.out.println("开始执行业务操作生成关键日志。用户userId为:" + event.getUserId());
}
@EventListener({ UserChangePasswordEvent.class })
public void messageListener(UserChangePasswordEvent event) {
System.out.println("收到事件:" + event);
System.out.println("开始执行业务操作给用户发送短信。用户userId为:" + event.getUserId());
}
}
@TransactionalEventListener 定义一个监听器
与@EventListener不同的就是@EventListener标记一个方法作为监听器,他默认是同步执行,如果发布事件的方法处于事务中,那么事务会在监听器方法执行完毕之后才提交。事件发布之后就由监听器去处理,而不要影响原有的事务,也就是说希望事务及时提交。我们就可以使用该注解来标识。注意此注解需要spring-tx的依赖。
使用方式如下。phase事务类型,value指定事件。
/**
* @Author JiaQIng
* @Description 事件监听器
* @ClassName LogListener
* @Date 2023/3/26 14:22
**/
@Component
public class ListenerEvent {
@EventListener({ UserChangePasswordEvent.class })
public void logListener(UserChangePasswordEvent event) {
System.out.println("收到事件:" + event);
System.out.println("开始执行业务操作生成关键日志。用户userId为:" + event.getUserId());
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT,value = { UserChangePasswordEvent.class })
public void messageListener(UserChangePasswordEvent event) {
System.out.println("收到事件:" + event);
System.out.println("开始执行业务操作给用户发送短信。用户userId为:" + event.getUserId());
}
}
事件发布
@SpringBootTest
class SpirngEventApplicationTests {
@Autowired
ApplicationEventPublisher appEventPublisher;
@Test
void contextLoads() {
appEventPublisher.publishEvent(new UserChangePasswordEvent("1111111"));
}
}
创建可缓冲的IO流
IO流想必大家都使用得比较多,我们经常需要把数据写入某个文件,或者从某个文件中读取数据到内存中,甚至还有可能把文件a,从目录b,复制到目录c下等。
JDK给我们提供了非常丰富的API,可以去操作IO流。
例如:
public class IoTest1 {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File srcFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/1.txt");
File destFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/2.txt");
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
int len;
while ((len = fis.read()) != -1) {
fos.write(len);
}
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这个例子主要的功能,是将1.txt文件中的内容复制到2.txt文件中。这例子使用普通的IO流从功能的角度来说,也能满足需求,但性能却不太好。
因为这个例子中,从1.txt文件中读一个字节的数据,就会马上写入2.txt文件中,需要非常频繁的读写文件。
优化:
public class IoTest {
public static void main(String[] args) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File srcFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/1.txt");
File destFile = new File("/Users/dv_susan/Documents/workspace/jump/src/main/java/com/sue/jump/service/test1/2.txt");
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bos != null) {
bos.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bis != null) {
bis.close();
}
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这个例子使用BufferedInputStream和BufferedOutputStream创建了可缓冲的输入输出流。
最关键的地方是定义了一个buffer字节数组,把从1.txt文件中读取的数据临时保存起来,后面再把该buffer字节数组的数据,一次性批量写入到2.txt中。
这样做的好处是,减少了读写文件的次数,而我们都知道读写文件是非常耗时的操作。也就是说使用可缓存的输入输出流,可以提升IO的性能,特别是遇到文件非常大时,效率会得到显著提升。
减少循环次数
在我们日常开发中,循环遍历集合是必不可少的操作。
但如果循环层级比较深,循环中套循环,可能会影响代码的执行效率。
反例:
for(User user: userList) {
for(Role role: roleList) {
if(user.getRoleId().equals(role.getId())) {
user.setRoleName(role.getName());
}
}
}
这个例子中有两层循环,如果userList和roleList数据比较多的话,需要循环遍历很多次,才能获取我们所需要的数据,非常消耗cpu资源。
正例:
Map<Long, List<Role>> roleMap = roleList.stream().collect(Collectors.groupingBy(Role::getId));
for (User user : userList) {
List<Role> roles = roleMap.get(user.getRoleId());
if(CollectionUtils.isNotEmpty(roles)) {
user.setRoleName(roles.get(0).getName());
}
}
减少循环次数,最简单的办法是,把第二层循环的集合变成map,这样可以直接通过key,获取想要的value数据。
虽说map的key存在hash冲突的情况,但遍历存放数据的链表或者红黑树的时间复杂度,比遍历整个list集合要小很多。