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

mac2025-10-14  8

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)