您的位置:首页 > 运维架构

Aop+自定义注解实现日志记录

2019-04-21 22:49 148 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/weixin_42881343/article/details/89441594

首先先说一下注解:
注解:
注解可以看作是对 一个 类/方法 的一个扩展的模版,每个 类/方法 按照注解类中的规则,来为 类/方法 注解不同的参数,在用到的地方可以得到不同的 类/方法 中注解的各种参数与值。

注解的原理
   注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池

java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
@Documented –注解是否将包含在JavaDoc中
@Retention –什么时候使用该注解
@Target –注解用于什么地方
@Inherited – 是否允许子类继承该注解
(1)@Retention– 定义该注解的生命周期
● RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
● RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
● RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
2.)Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括
● ElementType.CONSTRUCTOR:用于描述构造器
● ElementType.FIELD:成员变量、对象、属性(包括enum实例)
● ElementType.LOCAL_VARIABLE:用于描述局部变量
● ElementType.METHOD:用于描述方法
● ElementType.PACKAGE:用于描述包
● ElementType.PARAMETER:用于描述参数
● ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
3.)@Documented–一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。
4.)@Inherited – 定义该注释和子类的关系
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

自定义注解:
自定义注解类编写的一些规则:

  1. Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
  2. 参数成员只能用public或默认(default)这两个访问权修饰
  3. 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
  4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法
  5. 注解也可以没有定义成员, 不过这样注解就没啥用了
    PS:自定义注解需要使用到元注解

AOP
(1)AOP是什么?AOP与拦截器的区别?
太抽象的不说,如果你知道Struts2的拦截器,拦截器就是应用的AOP的思想,它用于拦截Action以进行一些预处理或结果处理。而Spring的AOP是一种更通用的模式,可以拦截Spring管理的Bean,功能更强大,适用范围也更广,它是通过动态代理与反射机制实现的。(更客 https://www.geek-share.com/detail/2676725533.html
(2)使用AOP需要的一些概念。
1.通知(Advice)
通知定义了在切入点代码执行时间点附近需要做的工作。
Spring支持五种类型的通知:
Before(前) org.apringframework.aop.MethodBeforeAdvice
after(后)
After-returning(返回后) org.springframework.aop.AfterReturningAdvice
After-throwing(抛出后) org.springframework.aop.ThrowsAdvice
Arround(周围) org.aopaliance.intercept.MethodInterceptor
Introduction(引入) org.springframework.aop.IntroductionInterceptor
2.连接点(Joinpoint)
程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法调用时、异常抛出时、方法返回后等等。
3.切入点(Pointcut)
通知定义了切面要发生的“故事”,连接点定义了“故事”发生的时机,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定。
4.切面(Aspect)
通知、连接点、切入点共同组成了切面:时间、地点和要发生的“故事”。
5.引入(Introduction)
引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)。
6.目标(Target)
即被通知的对象,如果没有AOP,那么通知的逻辑就要写在目标对象中,有了AOP之后它可以只关注自己要做的事,解耦合!
7.代理(proxy)
应用通知的对象,详细内容参见设计模式里面的动态代理模式。
8.织入(Weaving)
把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器;
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码;
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术。

切点表达式
在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点" 例如定义切入点表达式
expression=“execution(* com.fh.service….(…))”
expression=“execution(* com.fh.action.login.LoginAction.login(…))”
execution()是最常用的切点函数,其语法如下所示: 整个表达式可以分为五个部分:
1、execution(): 表达式主体。
2、第一个*号:表示返回类型,号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个号:表示类名,号表示所有的类。
5、(…):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

三、使用AOP的几种方式
1.经典的基于代理的AOP
2.@AspectJ注解驱动的切面
3.纯POJO切面

如何使用
@AspectJ 进行注解配置

org.aspectj aspectjweaver 1.8.8

1.配置
在springMVC中增加配置
头部文件中加入
xmlns:aop=“http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd

2.加入配置
开启注解模式
aop:aspectj-autoproxy/
扫描注解路径
<context:component-scan base-package=“com.fh.aop” />
3.定义自定义注解接口
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ILogAcpect {
String methodInfo() default “”;

String modelName() default “”;
}
4.配置注解版的AOP切面类

@Aspect
@Component
public 3ff7 class LogAspect {
@Pointcut(“execution(* com.fh.controller….(…))”)
private void doMethod() {
}
@Before(“doMethod()”)
public void beforeAdvice(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
// 请求的IP
String ip = request.getRemoteAddr();
System.out.println(ip);
try {
// 当前访问的类路径
String targetName = joinPoint.getTarget().getClass().getName();
// 当前访问的方法名
String methodName = joinPoint.getSignature().getName();
// 获取当前类中的公共方法
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String params = “”;
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {

params = Arrays.toString(joinPoint.getArgs());
}
Object[] arguments = joinPoint.getArgs();

String logName = “”;
String modelName = “”;
for (Method method : methods) {

if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
if (method.getAnnotation(ILogAcpect.class) != null) {
logName = method.getAnnotation(ILogAcpect.class).methodInfo();
modelName = method.getAnnotation(ILogAcpect.class).modelName();
}
break;
}
}
}
// 通过类路径获取所有的方法
System.out.println(“日志输出的明细为:” + logName);
System.out.println(“日志输出访问模块:” + modelName);
System.out.println(“访问参数:” + params);
System.out.println(“访问的类路径:” + targetName);
System.out.println(“访问的方法名:” + methodName);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

@After(“doMethod()”)
public void afterAdvice() {
System.out.println(“afterAdvice”);
}
@AfterThrowing(pointcut = “doMethod()”, throwing = “e”)
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.err.println(“异常通知:” + e.getMessage());
}
}

aop提供一种思想:再不改变源代码的基础上增加新的功能。
事务控制,日志记录,权限控制,安全控制,性能统计,动态数据源的切换。
@Order(2) 数字越小越先执行。
过滤器可以写多个:可以控制先后执行顺序
拦截器也可以写多个:可以控制先后执行顺序
AOP也可以写多个: 可以控制先后执行顺序

AOP的组成
切面:Aspect (java类,可以来写AOP)
切点:PointCut(切点表达式来指明我们的AOP对谁起作用)
通知:advice (
四种通知:
before:前置通知
after:后置通知
arround:环绕通知
afterThrowing:抛出异常后通知
具体业务实现:
)

Spring AOP的环绕通知和前置、后置通知有着很大的区别,主要有两个重要的区别:

1)目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,而前置和后置通知是不能决定的,它们只是在方法的调用前后执行通知而已,即目标方法肯定是要执行的。joinPoint.proceed()就是执行目标方法的代码。

2)环绕通知可以控制返回对象,即可以返回一个与目标对象完全不同的返回值。虽然这很危险,但是却可以做到。
连接点:JoinPoint(可以得到你访问的类全名以及方法名等)

环绕通知用法
(1)声明拦截条件
@Around(“pointCut() && @annotation(logAnnotation)”)
pointCut()为选择的切点
@annotation(logAnnotation) 为过滤条件 只拦截添加了对象为logannotation类型注解的方法
(2)配置环绕通知执行方法
public Object aroundAdvice1(ProceedingJoinPoint joinPoint,LogAnnotation logAnnotation){
ProceedingJoinPoint 为环绕通知连接点,通过Java反射可以获取所切到方法和类的所有信息,通常用来获取且到类的全路径和方法的方法名
获取类全路径:
joinPoint.getTarget().getClass().getName()
获取方法名:
joinPoint.getSignature().getName()

LogAnnotation 为自定义注解 因为声明了只有添加了@LogAnnotation注解的方法才可以被拦截 根据Java反射通过代理对象logAnnotation可以获取自定义注解的信息
(3)执行方法
Around环绕通知和Before和After不同
它可以控制方法是否执行只有执行了joinPoint.proceed()方法
切到的方法才可以继续执行下去,否则就直接跳过返回前台
所以在joinPoint.proceed()方法前面的代码相当于前置通知即在方法执行前执行
在joinPoint.proceed()之后的方法相当于后置通知
而异常捕捉catch里面相当于AfterThrowing
但要注意一点
try {
System.out.println(“前置通知==============”);
result=joinPoint.proceed();
System.out.println(“方法执行完================”);
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println(“异常抛出”);
}
System.out.println(“后置通知”);

后置方法要写在try catch之后(
如果抛出异常了就直接跳过了
System.out.println(“方法执行完================”);这部分代码
)

注:
result=joinPoint.proceed();
把joinPoint.proceed()执行完成的返回值赋值给了result,返回给前台,不会改变源代码返回值

代码:
@Around(“doMethod() && @annotation(logAnno)”)
public Object aroundAdvice1(ProceedingJoinPoint joinPoint, LogAnno logAnno){
Object result=null;
LogBean log=new LogBean();
long startTime=System.currentTimeMillis();
log.setAnnotation(logAnno.value());
log.setClassName(joinPoint.getTarget().getClass().getName());
log.setMethodName(joinPoint.getSignature().getName());
try {
System.out.println(“前置通知==============”);
result=joinPoint.proceed();
System.out.println(“方法执行完================”);
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println(“异常抛出”);
}
System.out.println(“后置通知”);
ServletRequestAttributes attribute= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request=attribute.getRequest();
System.out.println(“IP地址为============”+AdressUtil.getIpAdress(request));

log.setIpAdrtress(AdressUtil.getIpAdress(request));
long endTime=System.currentTimeMillis();
System.out.println("方法的执行时间========"+(endTime-startTime));
log.setConsumeTime(endTime-startTime);
log.setLoginName("zhs");
String uuid=UUID.randomUUID().toString().replace("-","");
sendMsg.send(uuid,log);
return result;

}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: