JAVA代码优化实践

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集合要小很多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值