Spring 注解 之 AOP基于@Aspect的AOP配置

Spring AOP面向切面编程,可以用来配置事务、做日志、权限验证、在用户请求时做一些处理等等。用@Aspect做一个切面,就可以直接实现。

 

  1.首先定义一个切面类,加上@Component  @Aspect这两个注解   


@Aspect
@Configuration
//或者@Component
public class InterfaceLogAspect {
    private final Logger logger = LoggerFactory.getLogger(InterfaceLogAspect.class);

    private final int cacheTime = 3600*30;
    
    。。。

}

 2.定义切点     

private final String POINT_CUT = "execution(* com.nio.portal.controller.api.*.*(..))";

@Pointcut(POINT_CUT)
public void pointCut(){}


或者

 // 定义切点Pointcut
    @Pointcut("execution(* com.nio.portal.controller.api.*.*(..))")
    public void excudeService() {

    }

 切点表达式中,..两个点表明多个,*代表一个,  上面表达式代表切入com.xhx.springboot.controller包下的所有类的所有方法,方法参数不限,返回类型不限。  其中访问修饰符可以不写,不能用*,,第一个*代表返回类型不限,第二个*表示所有类,第三个*表示所有方法,..两个点表示方法里的参数不限。  然后用@Pointcut切点注解,想在一个空方法上面,一会儿在Advice通知中,直接调用这个空方法就行了,也可以把切点表达式卸载Advice通知中的,单独定义出来主要是为了好管理。

 3.Advice,通知增强,主要包括五个注解Before,After,AfterReturning,AfterThrowing,Around,下面代码中关键地方都有注释,我都列出来了。

   @Before  在切点方法之前执行

   @After  在切点方法之后执行

    @AfterReturning 切点方法返回后执行

   @AfterThrowing 切点方法抛异常执行

  @Around 属于环绕增强,能控制切点执行前,执行后,,用这个注解后,程序抛异常,会影响@AfterThrowing这个注解

 

package com.nio.portal.common.aspect;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import com.nio.portal.common.util.CacheMap;
import com.nio.portal.common.util.MessageUtil;
import com.nio.portal.model.output.AppInterface;
import com.nio.portal.model.output.ApplicationInfo;
import com.nio.portal.service.ApplicationService;
import org.apache.commons.lang3.StringUtils;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.nio.portal.common.outresult.Result;
import com.nio.portal.common.outresult.ResultType;
import com.nio.portal.common.util.JsonHelper;
import com.nio.portal.common.util.RequestUtil;
import com.nio.portal.persistence.entity.InterfaceLogEntity;
import com.nio.portal.service.MongoService;

@Aspect
@Configuration
public class InterfaceLogAspect {
    private final Logger logger = LoggerFactory.getLogger(InterfaceLogAspect.class);

    private final int cacheTime = 3600*30;

//    private final int signCheckCacheTime = 3600*60;

    @Autowired
    MongoService mongoService;

    @Autowired
    ApplicationService applicationService;


    @Value("${api.check.sign}")
    private String needCheckSign;

    // 定义切点Pointcut
    @Pointcut("execution(* com.nio.portal.controller.api.*.*(..))")
    public void excudeService() {
    }

    @Around("excudeService()")
    public Object addInterfaceLog(ProceedingJoinPoint proceeding) throws Throwable {
        Result result = null;
        InterfaceLogEntity logEntity = new InterfaceLogEntity();
        String requestId = UUID.randomUUID().toString();
        logEntity.setStartTime(new Date());
        //获取
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes)ra;
        HttpServletRequest request = sra.getRequest();
        //获取调用的url
        String url = request.getRequestURL().toString();
        //接口调用的时间记录
        //包括业务参数bizData和系统级参数
        Map<String, String> map = RequestUtil.getAllParameter(request);
        String ip = RequestUtil.getRemoteAddr(request);
        String method = request.getMethod();
        String uri = request.getRequestURI().replaceAll("//", "/");;
        //记录日志
        logger.info("请求开始reqeustId:{}, ip: {}, url: {}, method: {}, uri: {}, params: {}",requestId,ip, url, method, uri, map);
        Map<String, String> headerMap = RequestUtil.getHeaderParameter(request);
        //设置接口输入
        String input = map.get("bizData");
        logEntity.setInput(input);
        //获取调用的url
        logEntity.setRequestUrl(url.substring(0,url.contains("?")?url.indexOf("?"):url.length()));
        //记录request id
        logEntity.setSysRequestId(requestId);

        try{
            //开始校验系统级别参数
            boolean checkParam= true;
            StringBuilder errorInfo = new StringBuilder();
            String timeStamp = getSystemLevelParam(headerMap,map,"timestamp");
            if(StringUtils.isEmpty(timeStamp)){
                //没有传入调用时间
                logEntity.setStatus(2);
                checkParam = false;
                errorInfo.append("没有传入调用时间timestamp").append(" ");
            }else{
                logEntity.setRequestTime(new Date(Long.parseLong(timeStamp)));
            }

            if("true".equalsIgnoreCase(needCheckSign)){
                String appId = getSystemLevelParam(headerMap,map,"appId");
                if(StringUtils.isEmpty(appId)){
                    //没有传入app id
                    logEntity.setStatus(2);
                    checkParam = false;
                    errorInfo.append("没有传入系统id appId").append(" ");
                }else{
                    //从缓存中获取application信息
                    ApplicationInfo applicationInfo = CacheMap.appCache.get(appId);
                    logger.info("缓存中的applicationInfo:" + JsonHelper.parseToJson(applicationInfo));
                    if(applicationInfo == null || new Date().getTime() - applicationInfo.getTime().getTime() > cacheTime){
                        //缓存中没有或者已经超过1小时 从数据库中重新获取
                        applicationInfo = applicationService.getApplicationInfoByAppId(appId);
                        logger.info("查询出的applicationInfo:" + JsonHelper.parseToJson(applicationInfo));
                        if(applicationInfo != null){
                            applicationInfo.setTime(new Date());
                            CacheMap.appCache.put(appId,applicationInfo);
                        }

                    }
                    if(applicationInfo == null){
                        //app id 不存在
                        logEntity.setStatus(2);
                        checkParam = false;
                        errorInfo.append("app id 不存在").append(" ");
                    }else{
                        //进行接口校验 当前application 是否可以调用改接口
                        logger.info("校验applicationInfo:" + JsonHelper.parseToJson(applicationInfo));
                        logger.info("请求uri:" + uri);
                        AppInterface appInterface = getAppInterfaceByValue(uri,applicationInfo);
                        if(appInterface == null){
                            logEntity.setStatus(2);
                            checkParam = false;
                            errorInfo.append("当前接口不能被此appId调用").append(" ");
                        }else{
                            logEntity.setApiName(appInterface.getName());
                            //进行签名校验
                            Map<String,Object> signMap = new HashMap<>();
                            String sign = getSystemLevelParam(headerMap,map,"sign");
                            signMap.put("sign",sign);
                            signMap.put("timestamp",timeStamp);
                            signMap.put("appId",appId);
                            signMap.put("bizData",input);
                            //取出调用的系统名称
                            String system = getSystemLevelParam(headerMap,map,"system");
                            if(StringUtils.isNotEmpty(system)){
                                signMap.put("system",system);
                            }
                            logger.info("签名参数为:"+JsonHelper.parseToJson(signMap));
                            logger.info("签名秘钥为:"+applicationInfo.getSecurityKey());
                            if(!MessageUtil.verifySign(signMap,applicationInfo.getSecurityKey())){
                                //签名校验失败
                                logEntity.setStatus(2);
                                checkParam = false;
                                errorInfo.append("签名错误 ").append(" ");
                            }
                        }
                    }
                }
            }



            if(checkParam ){
                //调用接口
                result = (Result) proceeding.proceed();//(动态代理重点)
                //设置接口输出
                logEntity.setOutput(JsonHelper.parseToJson(result));
                //设置log状态
                if(ResultType.SUCCESS.getCode().equalsIgnoreCase(result.getResultCode())){
                    //接口调用成功
                    logEntity.setStatus(1);
                }else{
                    //接口调用失败
                    logEntity.setStatus(2);
                }
            }else{
                logEntity.setOutput(errorInfo.toString().trim());
                result = Result.result(ResultType.API_INVOTE_ERROR.getCode(),errorInfo.toString().trim());
            }
        }catch (Exception e){
            logEntity.setOutput(e.toString());
            logEntity.setStatus(2);
            result = Result.result(ResultType.SYSTEM_ERROR);
            logger.error("请求结束requestId:{}, 发生异常:" ,requestId);
            logger.error(uri, e);
        } finally{
            //设置返回时间
            logEntity.setEndTime(new Date());
            logEntity.setType(1);
            try {
            //保存日志
                mongoService.insert(logEntity);
                logger.info("接口日志保存成功! parms={}",JsonHelper.parseToJson(logEntity));
			} catch (Exception e2) {
				logger.error("日志保存失败!",e2);
			}
            
            logger.info("请求结束requestId:{}, controller的返回值是:{}" ,requestId,JsonHelper.parseToJson(result));
        }
        return result;

    }

    private String getSystemLevelParam(Map<String,String> headerMap,Map<String,String> paramMap,String name){
        String value = null;
        if(headerMap.get(name) != null){
            value = headerMap.get(name);
        }else if(paramMap.get(name) != null){
            value = paramMap.get(name);
        }
        return value;
    }

    private AppInterface getAppInterfaceByValue(String value,ApplicationInfo applicationInfo){
        if(StringUtils.isEmpty(value) || applicationInfo == null){
            return null;
        }
        List<AppInterface> list = applicationInfo.getInterfaces();
        if(list == null || list.isEmpty()){
            return null;
        }
        for(AppInterface appInterface : list){
            if(appInterface.getUrl().equalsIgnoreCase(value)){
                return appInterface;
            }
        }
        return null;
    }

}

 

动态代理部分:

上面代码中用到了ProceedingJoinPoint,下面来解释一下ProceedingJoinPoint与JoinPoint的区别和联系:

 现在AOP的场景越来越多,所以我们有必要理解下和AOP相关的一些概念和机制。基础知识和原理类大家搜索spring aop/aspectj,有大量现成的可以参考,基本上只要理解了jdk动态代理、cglib字节码动态生成代理就足够了,而且必须知道这个代理类是spring托管的(如果是自己创建的代理类,是无法被拦截的,此时只能使用过滤器/拦截器机制,他们本身是链式的,跟代理无关),所以这里就不重复废话了。

 

import org.aspectj.lang.reflect.SourceLocation;  
public interface JoinPoint {  
   String toString();       //连接点所在位置的相关信息  
   String toShortString();  //连接点所在位置的简短相关信息  
   String toLongString();   //连接点所在位置的全部相关信息  
   Object getThis();        //返回AOP代理对象,也就是com.sun.proxy.$Proxy18
   Object getTarget();      //返回目标对象,一般我们都需要它或者(也就是定义方法的接口或类,为        
                         什么会是接口呢?这主   要是在目标对象本身是动态代理的情况下,例如Mapper。所以返回的是定义方法的对象如aoptest.daoimpl.GoodDaoImpl或com.b.base.BaseMapper<T, E, PK>)
   Object[] getArgs();        //返回被通知方法参数列表  
   Signature getSignature();  //返回当前连接点签名  其getName()方法返回方法的FQN,如void     
                              aoptest.dao.GoodDao.delete()或  com.b.base.BaseMapper.insert(T)(需要注意的是,很多时候我们定义了子类继承父类的时候,我们希望拿到基于子类的FQN,这直接可拿不到,要依赖于 
   
   AopUtils.getTargetClass(point.getTarget())获取原始代理对象,下面会详细讲解)
   SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置  
   String getKind();                  //连接点类型  
   StaticPart getStaticPart();        //返回连接点静态部分  
  }  
 
 public interface ProceedingJoinPoint extends JoinPoint {  
       public Object proceed() throws Throwable;  
       public Object proceed(Object[] args) throws Throwable;  
 } 

 JoinPoint.StaticPart:提供访问连接点的静态部分,如被通知方法签名、连接点类型等:

public interface StaticPart {  
   Signature getSignature();    //返回当前连接点签名  
   String getKind();          //连接点类型  
   int getId();               //唯一标识  
   String toString();         //连接点所在位置的相关信息  
   String toShortString();     //连接点所在位置的简短相关信息  
   String toLongString();     //连接点所在位置的全部相关信息  
}

环绕通知 ProceedingJoinPoint 执行proceed方法的作用是让目标方法执行,这也是环绕通知和前置、后置通知方法的一个最大区别。

 Proceedingjoinpoint 继承了 JoinPoint 。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。

 

暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,这跟切面类型有关), 能决定是否走代理链还是走自己拦截的其他逻辑。建议看一下 JdkDynamicAopProxy的invoke方法,了解一下代理链的执行原理。

典型的用法如下:

public Object around(ProceedingJoinPoint point) throws Throwable {
        Signature signature = point.getSignature();
        // AopUtils.getTargetClass(point.getTarget())获取原始对象,例如对于Mapper而言,它获取的是具体代理的Mapper如com.b.mapper.DefaultDsMapper(如果前者继承了后者的话)而不是定义该方法的Mapper如com.b.base.BaseMapper<Info, InfoExample, InfoKey>,如下图
        Type[] types = AopUtils.getTargetClass(point.getTarget()).getGenericInterfaces(); // getGenericInterfaces方法能够获取类/接口实现的所有接口
        Annotation nologgingAnno = ((Class)types[0]).getAnnotation(Nologging.class); // type是所有类型的父接口
        MethodSignature methodSignature = (MethodSignature)signature;
        Method targetMethod = methodSignature.getMethod();

 

 

现在来补充下Java中Type接口与Class类的区别联系。

package java.lang.reflect;

/**
 * Type is the common superinterface for all types in the Java
 * programming language. These include raw types, parameterized types,
 * array types, type variables and primitive types.
 *
 * @since 1.5
 */
public interface Type {
    /**
     * Returns a string describing this type, including information
     * about any type parameters.
     *
     * @implSpec The default implementation calls {@code toString}.
     *
     * @return a string describing this type
     * @since 1.8
     */
    default String getTypeName() {
        return toString();
    }
}

 

总结来说:

  • Type是一个接口。
  • Type是Java中所有类型的父接口,有一些子类,如上所示。
  • Type包括:raw type(原始类型,对应Class),parameterized types(参数化类型), array types(数组类型), type variables(类型变量) and primitive types(基本类型,对应Class).
  • Type是JDK1.5引入的,主要是为了泛型。

Type接口与Class类的区别联系

  • Type是Class的父接口。
  • Class是Type的子类。

每天进步一点点,未来可期!加油!!!!

下文中还会对@advice方法中的几个类做介绍!!!

e.g: RequestContextHolder  ServletRequestAttributes
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值