面向切面编程-编写一个计算方法加载时间的拦截器

什么是切面(AOP)?

切面的概念

AOP:面向切面编程,相当于OOP面向对象编程。SpringAOP的存在目的就是解耦,AOP可以让一组类共享相同的行为。(出自《Spring Boot实战》)
AOP为开发人员提供了一种描写叙述横切关注点的机制,并可以自己主动将横切关注点织入到面向对象的软件系统中。从而实现了横切关注点的模块化。
AOP可以将那些与业务无关,却为业务模块所共同调用的逻辑或责任。比如事务处理、日志管理、权限控制等。封装起来,便于降低系统的反复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

切面的名词解析

切面(Aspect):由切点和增强组成,既包含了横切逻辑的定义。也包含了连接点的定义。

建言(Advice):是切面的详细实现。
以目标方法为参照点,依据放置的地方不同,可分为前置通知(Before)、后置返回通知(AfterReturning)、后置异常通知(AfterThrowing)、后置终于通知(After)与围绕通知(Around)5种。在实际应用中一般是切面类中的一个方法。详细属于哪类通知。相同是在配置中指定的。

连接点(Joinpoint):程序运行的某个特定的位置。比方类初始化前,初始化后。方法调用前。方法调用后等等

切入点(Pointcut):用于定义通知应该切入到哪些连接点上。

不同的通知通常须要切入到不同的连接点上,这样的精准的匹配是由切入点的正则表达式来定义的。

目标对象(Target):增强逻辑的织入目标类。

代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。

能够简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共同拥有功能。代理对象对于使用者而言是透明的。是程序执行过程中的产物。

织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程能够发生在编译期、类装载期及执行期,当然不同的发生点有着不同的前提条件。
譬如发生在编译期的话。就要求有一个支持这样的AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;仅仅有发生在执行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。
增强:织入到目标类连接点上的一段代码

切面的应用场景

此处链接: 面向切面编程(AOP)的理解,此博主说明简洁,生动,便于理解。

代码实战

此处实现方法计算执行时间,打印日志的功能。可能大家会疑惑,我在方法的起止处加上两行System.currentTimeMillis()就行了,为何要这么麻烦?嗯,从单个方法来看是这样的,然而,每个方法都加上去不就显得很lower,而且无法保证统一。那聪明的同学可能会提个工具类调用,然而你的方法是不是还得调用。现实开发中我们写业务代码也没见过每个方法写个调用,日志自动就输出时间了,参数等东西的,那是如何做到的?
创建拦截规则

/**
 * 拦截器顺序。
 * 
 * @author 芬达
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface InterceptorOrder {
    /**
     * 值越低优先级越高。
     * 
     * @return
     */
    int order() default LOWEST_ORDER;

    int LOWEST_ORDER = Integer.MAX_VALUE;
    int HIGHTEST_ORDER = Integer.MAX_VALUE;

}

构造拦截器

/**
 * 拦截器实体
 * 
 * @author 芬达
 *
 */
public abstract class InspireInterceptor {
    @Getter
    @Setter
    private int order;

    /**
     * 执行完目标方法之前拦截。
     */
    public boolean executrBefore(InspireContext context) {
        return true;
    }

    /**
     * 执行完目标方法之后进行拦截。
     */
    public void executeAfter(InspireContext context) {
    }
}

构造请求的上下文

/**
 * 拦截器上下文类。
 * 
 * @author 芬达
 *
 */
@Data
public class InspireContext {

    private InspireRequest request;

    private InspireResponse response;
}

构造请求参数

/**
 * 请求参数。
 * 
 * @author 芬达
 *
 */
@Data
public class InspireRequest {
    /**
     * 方法名
     */
    private String methodName;

    private Map<String, Object> paramMap;
}

目标方法的执行结果

/**
 * 目标方法的执行结果。
 * 
 * @author 芬达
 *
 * @param <T>
 */
@Data
public class InspireResponse<T> {
    /**
     * 目标方法的执行结果
     */
    private T data;
}

此处使用责任链模式作为拦截器管理,目的是方便之后添加多个拦截器。

/**
 * 拦截器管理。
 * 
 * @author 芬达
 *
 */
@Component
public class InterceptorChainClient {

    @Autowired
    private List<InspireInterceptor> interceptorList;

    @PostConstruct
    public void loadInterceptors() {
        if (!CollectionUtils.isEmpty(interceptorList)) {
            interceptorList.forEach(interceptor -> {
                interceptor.setOrder(resolveOreder(interceptor));
            });
            Collections.sort(interceptorList, (o1, o2) -> o1.getOrder() - o2.getOrder());

        }

    }

    /**
     * 获取拦截器优先级
     * 
     * @param interceptor
     * @return
     */
    private int resolveOreder(InspireInterceptor interceptor) {
        if (!interceptor.getClass().isAnnotationPresent(InterceptorOrder.class)) {
            return InterceptorOrder.LOWEST_ORDER;
        } else {
            return interceptor.getClass().getAnnotation(InterceptorOrder.class).order();
        }
    }

    public boolean processBefore(InspireContext context) {
        for (InspireInterceptor interceptor : interceptorList) {
            boolean isPass = interceptor.executrBefore(context);
            if (!isPass) {
                return false;
            }
        }
        return true;

    }

    public void processAfter(InspireContext context) {
        for (InspireInterceptor interceptor : interceptorList) {
            interceptor.executeAfter(context);
        }

    }
}

方法调用时间的拦截器

/**
 * 方法调用的时间
 * 
 * @author 芬达
 *
 */
@Slf4j
@Component
@InterceptorOrder(order = 20)
public class CostTimeInterceptor extends InspireInterceptor {

    /**
     * 开始时间。
     */
    private Long start;
    /**
     * 结束时间。
     */
    private Long end;

    @Override
    public boolean executrBefore(InspireContext context) {
        start = System.currentTimeMillis();
        return super.executrBefore(context);
    }

    @Override
    public void executeAfter(InspireContext context) {
        end = System.currentTimeMillis();
        long costTime = end - start;
        log.info("invoke method:{},costTime:{}", context.getRequest().getMethodName(), costTime);
    }
}

AOP拦截


@Slf4j
@Aspect
@Component
public class MethodAspect {

    @Autowired
    private InterceptorChainClient interceptorChainClient;

    @Pointcut("execution(* com.example.demo.rpc.service.*.*(..))")
    private void doLogPointcut() {
    };

    @Around("doLogPointcut()")
    public Object doAfterReturning(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("===============invole  aop start=====================");
        InspireContext inspireContext = buildInspireContext(joinPoint);
        // 目标方法执行之前
        interceptorChainClient.processBefore(inspireContext);

        RpcResult result = (RpcResult) joinPoint.proceed();
        inspireContext.getResponse().setData(result);
        // 目标方法执行之后
        interceptorChainClient.processAfter(inspireContext);

        System.out.println("===============invole  aop end=====================");
        return result;
    }

    private InspireContext buildInspireContext(ProceedingJoinPoint joinPoint) {
        InspireRequest inspireRequest = new InspireRequest();
        // 方法名
        inspireRequest.setMethodName(joinPoint.getSignature().getName());
        // 参数名
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] paramsNames = methodSignature.getParameterNames();
        log.info("invoke class:{}", signature.getDeclaringType());
        // 参数值
        Object[] paramsValue = joinPoint.getArgs();
        Map<String, Object> paramsMap = new HashMap<>();
        for (int i = 0; i < paramsNames.length; i++) {
            paramsMap.put(paramsNames[i], paramsValue[i]);

        }
        inspireRequest.setParamMap(paramsMap);
        InspireContext inspireContext = new InspireContext();
        inspireContext.setRequest(inspireRequest);
        inspireContext.setResponse(new InspireResponse());
        return inspireContext;
    };

}

远程调用结果

/**
 * 统一的结果参数
 * 
 * @author 芬达
 *
 * @param <T>
 */
@Data
public class RpcResult<T> implements Serializable {

    /**
     * 序列化
     */
    private static final long serialVersionUID = 1L;
    private Integer code;
    private boolean success;
    private String msg;
    private T data;
}

测试类

@Controller
@RequestMapping("order")
public class RefundController {

    @Autowired
    private RefundService refundService;

    @RequestMapping("/refund")
    public String refund(@Param("orderId") String orderId, @Param("reason") String reason) {
        refundService.refund(orderId, reason);
        return "index";
    }
}

/**
 * 接口测试类。
 * 
 * @author 芬达
 *
 */
public interface RefundService {
    RpcResult<Boolean> refund(String orderId, String reason);
}

@Service("refundService")
public class RefundServiceImpl implements RefundService {

    @Override
    public RpcResult<Boolean> refund(String orderId, String reason) {
        if ("1001".equals(orderId)) {
            return RpcResultBuilder.buildSuccess(true);
        } else {
            return RpcResultBuilder.buildFail(false);

        }
    }

}

/**
 * 结果处理
 * 
 * @author 芬达
 *
 */
public class RpcResultBuilder {

    public static <T> RpcResult<T> buildSuccess(T data) {
        RpcResult<T> result = new RpcResult<T>();
        result.setSuccess(true);
        result.setCode(200);
        result.setMsg("success");
        result.setData(data);
        return result;
    }

    public static <T> RpcResult<T> buildFail(T data) {
        RpcResult<T> result = new RpcResult<T>();
        result.setSuccess(false);
        result.setCode(500);
        result.setMsg("fail");
        result.setData(data);
        return result;
    }
}

目录层级
层级结构
调用接口模式
http://localhost:9002/order/refund?orderId=1001&reason=true
结果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章