使用Aop和自定义注解赋予方法堆积能力,一次性处理多条数据
前言\color{blue}{前言}前言
在项目开发过程的当中,处理业务时需要访问数据库来查询数据结果,而在某些高并发和实时请求数较多时,数据库往往会承受不住这种压力,从而导致我们项目的性能降低。这时我们就需要进行处理两者之间的关系,便捷的方法是通过设置缓存数据库如Redis,这也是目前最为流行的方式,读者可以在网络上查找到很多相关的设计方案,但这种方式往往需要改变原有的项目结构。那么接下来我将提供另一种方式来解决。(PS:实际的项目开发而言,有条件的话还是建议添加缓存数据库,堆积处理虽然能够保证项目的原有代码,但同样也存在着性能瓶颈,只是一种临时的解决方案比如当你要跑路老板却让你改bug)
实现步骤\color{blue}{实现步骤}实现步骤
首先我们编写一个自定义注解@Delay,注解内容可以根据个人需求添加。
import java.lang.annotation.*;
/**
* @author Lin
* @date 2023/3/16
* 使用此注解可以将方法赋予堆积能力,在单次调用的时候不会立刻执行,能一次处理多条数据
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Delay {
int maxsize() default 10;//最大堆积延迟条数
long time() default Long.MAX_VALUE;//时间间隔,无论是否到达最大条数都执行堆积中的请求
}
仅仅靠一个注解是没有办法实现我们想要的结果的,它仅仅是个标识的作用,更重要的是我们对它所写的逻辑,下面我们将会详细介绍具体的实现。如果对AOP不了解的读者可以先上网看看相关的介绍。
为了简化我们Aop的过程,也为了代码的可读性,我们首先编写了一个AopUtils来简化我们开发的过程。
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
/**
* @author Lin
* @Description 简化Aop的开发过程
* @Date 2023/3/17
*/
@Slf4j
public class AOPUtils {
public static class MoreAnnotations extends Exception {
public MoreAnnotations() {
}
public MoreAnnotations(String message){
super(message);
}
}
private AOPUtils(){
}
public static class ProceedingJoinPointContent{
Class<?> aClass; //代理方法所属的类
String methodName; //代理的方法名
Annotation[] annotations; //代理方法上的注解
Class<?>[] parametersType; //代理目标方法需要传入的参数类型
Method method; //代理目标方法本身
Object[] args; //代理目标方法实际传入的参数
public ProceedingJoinPointContent(ProceedingJoinPoint pjp) throws NoSuchMethodException {
aClass = pjp.getTarget().getClass();
methodName = pjp.getSignature().getName();
parametersType = ((MethodSignature) pjp.getSignature()).getParameterTypes();
args = pjp.getArgs();
method = aClass.getMethod(methodName, parametersType);
annotations = method.getAnnotations();
}
/**
* 获取单个方法中的参数(返回第一个匹配到的)
* @param clazz 要获取参数的类
* @param <T> 类
* @return 该对象
*/
@Deprecated
public <T> T getSingleArg(Class<T> clazz){
for(Object arg: args) {
if(arg.getClass() == clazz.getClass()){
return (T)arg;
}
}
return null;
}
/**
* 获取多个方法中的参数
* @param tClass 要获取参数的类
* @param <T> 类
* @return 该类型对象的列表
*/
public <T> List<T> getMoreArg(Class<T> tClass) {
if(args == null){
return null;
}
List<T> args = new LinkedList<>();
for(Object arg : args){
if(arg.getClass().getName().equals(tClass.getTypeName())){
args.add((T) arg);
}
}
return args;
}
/**
* @return 方法参数中的HttpServletRequest
*/
public HttpServletRequest getRequestArg() {
for(Object arg : args){
if(arg == null){
continue;
}
String typeName = arg.getClass().getTypeName();
if(typeName.equals("org.apache.catalina.connector.RequestFacade")){
return (HttpServletRequest) arg;
}
}
return null;
}
public HttpServletResponse getResponseArg(){
for(Object arg : args){
String typeName = arg.getClass().getTypeName();
if(typeName.equals("org.apache.catalina.connector.ResponseFacade")){
return (HttpServletResponse) arg;
}
}
return null;
}
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
for(Annotation annotation : annotations){
if(annotation.annotationType() == annotationClass){
return annotationClass.cast(annotation);
}
}
return null;
}
public <T extends Annotation> Annotation getAnnotationInClass(Class<T> annotationClass) throws MoreAnnotations{
Objects.requireNonNull(annotationClass);
Annotation[] annotationsByType = aClass.getAnnotationsByType(annotationClass);
if(annotationsByType.length == 1){
return annotations[0];
}else if(annotationsByType.length == 0){
return null;
}else {
throw new MoreAnnotations(annotationClass.getTypeName() + "在类{" + aClass.getTypeName() + "}上注解重复");
}
}
public Class<?> getaClass() {
return aClass;
}
public void setaClass(Class<?> aClass) {
this.aClass = aClass;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Annotation[] getAnnotations() {
return annotations;
}
public void setAnnotations(Annotation[] annotations) {
this.annotations = annotations;
}
public Class<?>[] getParametersType() {
return parametersType;
}
public void setParametersType(Class<?>[] parametersType) {
this.parametersType = parametersType;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Object[] getArgs() {
return args;
}
public void setArgs(Object[] args) {
this.args = args;
}
@Override
public String toString() {
return "ProceedingJoinPointContent{" +
"aClass=" + aClass +
", methodName='" + methodName + ''' +
", annotations=" + Arrays.toString(annotations) +
", parametersType=" + Arrays.toString(parametersType) +
", method=" + method +
", args=" + Arrays.toString(args) +
'}';
}
public static ProceedingJoinPointContent getPJPContent(ProceedingJoinPoint pjp) throws NoSuchMethodException {
return new ProceedingJoinPointContent(pjp);
}
}
}
首先我们编写了一个异常MoreAnnotations,这会方便我们在后面代码实现时抛出异常。
我们知道Proceedingjoinpoint 继承了JoinPoint,在JoinPoint的基础上暴露出 proceed(), 这个方法是AOP代理链执行的方法。我们将会通过传入这个参数拿到我们的代理目标和我们所需要的信息。
在静态类ProceedingJoinPointContent中我们编写了getSingleArg()等方法来简化我们后续的开发过程,对方法具体的解析我都添加了注释,以便帮助读者理解。
在我们完成了对工具类的编写后,就可以进入到切面类DelayAspect的开发。完整的代码如下。
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @date 2023/3/16
* @author Lin
* @Description Delay的具体描述
*/
@Slf4j
@Component
@Aspect
@Order(value = Integer.MIN_VALUE)
public class DelayAspect {
@Autowired
private DelayListMap delayListMap;
@Pointcut("@annotation(com.example.aop.annotation.Delay)")
public void pointcut(){
}
@Around(value = "pointcut()")
public Object achieveDelay(ProceedingJoinPoint pjp) throws NoSuchMethodException, UnTypeException {
AOPUtils.ProceedingJoinPointContent content = new AOPUtils.ProceedingJoinPointContent(pjp);
Method method = content.getMethod();
Object[] args = content.getArgs();
Object target = pjp.getTarget();
delayListMap.pushAndInvoke(method, target, args);
log.info("方法的返回值为:{}",method.getReturnType());
if(!method.getReturnType().toString().equals("void")){
throw new UnTypeException("使用@Delay必须保证其方法返回值为void");
}
return null;
}
}
对于@Order这个注解是用于定义Spring中Bean的执行顺序,value的值越小优先级越高。
我们通过AOPUtils.ProceedingJoinPointContent(pjp)让我们的AOPUtils帮我们处理pjp这个切入点注解方法的信息,这样我们就可以获取我们所需要的数据信息,具体需要什么可以根据实际情况或者读者想要的实现的功能选取。这里我们将拿到的method,target,args传入pushAndInvoke()这个方法来实现堆积效果。
读者阅读到这已经能够发现最重要的逻辑实现就是这个DelayListMap类,我们通过它来实现对方法和参数的管理。
import com.example.aop.annotation.Delay;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
@Component
@Slf4j
public class DelayListMap {
private final Map<Method, List<Object>> curMap = new ConcurrentHashMap<>();
private final ExecutorService executorService = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors() * 2,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue(50000));
/**
* @auher Lin
* @param method 可堆积的方法
* @param target
* @param data 数据
* @throws UnTypeException
*/
public void pushAndInvoke(Method method, Object target, Object[] data) throws UnTypeException {
List<Object> list;
Delay annotation = method.getAnnotation(Delay.class);
if(annotation == null){
throw new UnTypeException("该方法没有标注@Delay");
}
int MaxSize = annotation.maxsize();
//加锁
synchronized (this){
list = curMap.computeIfAbsent(method,k -> new LinkedList<>());
if(data.length + list.size() >= MaxSize ){
curMap.put(method,new LinkedList<>());
}
}
list.addAll(Arrays.asList(data));
List<Object> finalList = list;
log.info("list == {}",list.size());
if(list.size() >= MaxSize) {
executorService.execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
Parameter[] parameters = method.getParameters();
if(parameters == null || parameters[0] == null){
throw new UnTypeException("@Delay注解下的参数没有被找到或者为空");
}
//单例执行
if(parameters[0].getClass().isArray()){
method.invoke(target, finalList);
//log.info("method执行的序列 = {}", obj);
}else {
//数组或者列表
for(Object o : finalList){
method.invoke(target, finalList);
//log.info("method执行的序列 = {}", obj);
}
}
}
});
}
}
}
相信读者在做实际项目的时候能够发现好的代码一定是能提高性能和响应,同时降低资源消耗的。所以我们在堆积处理时采用了线程池ExecutorService来提高吞吐。现在让我们来看pushAndInvoke()这个方法,我们通过Delay annotation = method.getAnnotation(Delay.class)拿到了注解@Delay,同时创建list来记录堆积数是否达到我们注解设置的maxsize。因为我们想使用线程池,所以为了避免线程安全问题我们使用线程安全的ConcurrentHashMap<>()来替换HashMap<>(),并使用computeIfAbsent()来实现堆积的计数。
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
通过对computerIfAbsent()源码的查看,我们发现如果 key 对应的 value 不存在,则使用获取 remappingFunction 重新计算后的值,并保存为该 key 的 value,否则返回 value。这样传入的method就可以被识别到,并进行记录。
现在让我们回到之前的代码中去
executorService.execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
Parameter[] parameters = method.getParameters();
if(parameters == null || parameters[0] == null){
throw new UnTypeException("@Delay注解下的参数没有被找到或者为空");
}
//单例执行
if(parameters[0].getClass().isArray()){
method.invoke(target, finalList);
//log.info("method执行的序列 = {}", obj);
}else {
//数组或者列表
for(Object o : finalList){
method.invoke(target, finalList);
//log.info("method执行的序列 = {}", obj);
}
}
}
});
熟悉Java反射的读者对invoke()这个方法一定不会陌生,它允许我们可以动态调用,这样我们就可以动态的传入参数。这样我们就可以调用method类中代表的方法,从而实现我们堆积方法并一次性处理的功能。
总结\color{blue}{总结}总结
本文主要介绍了一些自己在项目中遇到数据过多时的解决办法和自己对Aop的一些理解,然后现在也是各个大厂春招和暑期实习的时间,希望大家都可以找到自己期望的工作。
转载自:https://juejin.cn/post/7211716002383527997