Spring-Aop切面+自定义注解将日志记录存储到数据库

SpringAop切面+自定义注解实现日志记录

 

在之前的SpringAop当中,实现了对service层的方法定义了切入点,从而在执行service层的方法的时候会自动加入事务,而这些操作,都是在配置文件当中去实现的;

 

在这篇博文当中,会一不同的方式来实现日志事务的添加,用的式注解的方式,而且切入点和事务都是一起定义在切面类当中的;

 

首先来说明一些概念:

 

SpringAop 底层的原理就是动态代理,

 

但是运行时织入的,也就是在用到目标类或者方法的时候,生成一个代理类,然后将配好的通知织入

扫描二维码关注公众号,回复: 2617028 查看本文章

 

但就Aop而言的话它本身还有的方式就是,编译时织入,比如说是前置通知,当你编译了以后他就会织入到方法当中,所以你通过反射的方法去看的话会看到相应的生成代码

,还有就是在类加载期间织入的,

但是就性能而言的,SpringAop的这种动态代理的方式,是有着较高的效率的;

Spring提供了四种aop切面的支持

 

   基于代理的经典的pringAop

   纯POJO切面

   @AspectJ注解驱动切面//底层也是Spring的动态代理 只是参考了AspectJ的一些形式

   注入式Aspectj切面

而本此使用的就是@AspectJ注解驱动切面的方式。

 

 

1.要实现自定义注解的切面实现;

首先我们要有一个自定义的注解,下面就是自定义注解的代码,关于自定义注解大家可以自己去了解,下面式创建过程:

 

在创建类的时候选择Annotation这个注解方式,即可;

下面是代码:

我们在OperationLogger这样一个注解类中写如下代码:

@Retention(RetentionPolicy.RUNTIME)//注解会在class中存在,运行时可通过反射获取
@Target(ElementType.METHOD)//目标是方法
public @interface OperationLogger
{

    String modelName()
default "";
//
首先是被调用的方法的名称,其默认值是“”
    String user();
//
接下了一个就是当前使用这个方法的用户是谁
    String option();
//
之后就是这个用户所做的是什么操作
}

其实这个类就是一个特殊的接口,只不过这个注解是声明在方法之上的;

 

2.在我们定义好了注解之后我们还需要的就是一个切面类:

我们将切面的名称命名为:SysLogAspect,在这个类当中我们将声明各种通知,以及处理通知的方法,并调用Logservice来实现将记录的日志写入到数据库当中,用到的就是反射的方法:

下面是代码:

首先声明这是一个事务切面
@Aspect
@Component

让其被Spring容器去管理
public class SysLogAspect
{

我们在这和切面当中声明服务层的接口,来实现将我们获取到的日志存入到数据库当中

    @Autowired
   
private LoggerService loggerService;

之后声明一个日志的对象
    private static final Logger logger =

Logger.getLogger(SysLogAspect.class);


然后配置切点,切点实在空方法上去配置的,当然这是简便写法,因为这样的话,后面方法上面的切面就直接可以用户这个空的方法名去代替这样就避免了重复去写契入点
    @Pointcut("@annotation(com.qf.annocation.OperationLogger)")
   
public void controllerAspect()
    {
        System.
out.println("我是一个切入点");
    }

1)配置通知

    /**
     *
前置通知(Before advice :在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。
     * @param joinPoint
    
*/

前置通知的切入点就直接写的是之前声明切入点的方法
    @Before("controllerAspect()")
   
public void doBefore(JoinPoint joinPoint)
    {
        System.
out.println("=====SysLogAspect前置通知开始=====");

//调用日志处理类去处理我们的日志
        handleLog(joinPoint, null);
    }

   
/**
     *
后通知(After advice :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
     * @param joinPoint
    
*/

同样后置通知的切入点就直接写的是之前声明切入点的方法
    @AfterReturning(pointcut = "controllerAspect()")
   
public void doAfter(JoinPoint joinPoint)
    {
        System.
out.println("=====SysLogAspect后置通知开始=====");

//调用日志处理类去处理我们的日志
        handleLog(joinPoint, null);
    }

   
/**
     *
抛出异常后通知(After throwing advice 在方法抛出异常退出时执行的通知。
     * @param joinPoint
    
* @param e
    
*/

异常通知,我们用value去接收切入点,

这里解释一下当你的参数是一个的时候默认是用value去接收的参数是多个的时候必须做声明,因为是异常通知所需要一个异常注解在上面以便于记录
    @AfterThrowing(value = "controllerAspect()", throwing = "e")
   
public void doAfter(JoinPoint joinPoint, Exception e)
    {


        System.out.println("=====SysLogAspect异常通知开始=====")

//调用日志处理类去处理我们的日志,传入异常;
        handleLog(joinPoint, e);
      

    }

   
/**
     *
环绕通知(Around advice :包围一个连接点的通知,类似WebServlet规范中的FilterdoFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。
     * @param joinPoint
    
*/

然后是环绕通知对于环绕通知来说,它于其它的不同之处有两点:

  1. 参数是ProceedingJoinPoint
  2. 使用环绕通知的时候必须去手动执行方法,如果你不去执行方法,当你直接使用的是环绕通知的时候,它会将你日志注解声明之下的方法的返回值屏蔽这样前端是得不到数据的这样会造成一堆的问题,所以要手动去执行proceed方法

  3.     @Around("controllerAspect()")
       
    public Object doAround(ProceedingJoinPoint joinPoint)
        {
            Object proceed=
    null;
            System.
    out.println("=====SysLogAspect 环绕通知开始=====");

//调用日志处理类去处理我们的日志
        handleLog(joinPoint, null);

       
try {
            proceed = joinPoint.proceed();
           
return proceed;
        }
catch (Throwable throwable) {
            throwable.printStackTrace();
        }
       
return null;
    }


   /**
 
   2* 日志处理方法
     *
     * @param
joinPoint
    
* @param e
    
*/
   
private void handleLog(JoinPoint joinPoint, Exception e)
    {
       
try
       
{

在这里我们首先根据得到的JoinPoint获得注解,具体调用的方法在下面:
            //获得注解,这是在
            OperationLogger logger = giveController(joinPoint);

判断一下返回的是不是空值,防止出现空指针错误
            if (logger == null)
            {
               
return;
            }


// 获取目标方法的签名

String signature = joinPoint.getSignature().toString();

//根据的带的签名去截取方法名

String methodName =

在目标方法的签名当中以最后一个点加1开始,以包裹参数的第一个尖括号结尾截取方法名

signature.substring(signature.lastIndexOf(".")+1  

 

signature.indexOf("("));

当然这个变量并不会在这里用到
            String longTemp = joinPoint.getStaticPart().toLongString();

 

再通过得到调用方法的目标对象,从而获取它的类名
            String classType =

joinPoint.getTarget().getClass().getName();
再通过反射来得到这个类对象
            Class<?> clazz = Class.forName(classType);
得到它内部所有的方法
            Method[] methods = clazz.getDeclaredMethods();
            System.
out.println("methodName: " + methodName);
对得到的方法集合进行变脸
            for (Method method : methods)
            {

如果以这个方法上面的注解值日志注解并且方法的名称是之前截取到的方法名
                if (method.isAnnotationPresent(OperationLogger.class)


                        && method.getName().equals(methodName))
                {

这个时候就可以通过这个方法去获取其上面的注解
      我们将获取主借注解的功能封装成一个方法,将之前反射得到的类对象当作参数传递过去
                    String clazzName = clazz.getName();
                    System.
out.println("clazzName: " + clazzName + ", methodName: "
                           
+ methodName);

调用方法,在这里实现的就是对方法的遍历
                    String process = SysLogAspect.process(clazz);

这里就调用我们的服务层去将得到的数据写入数据库
                    loggerService.addLogger(process,methodName);
                }
            }

        }
catch (Exception exp)
        {
           
logger.error("异常信息:{}", exp);
            exp.printStackTrace();
        }
    }

   
/**
     *
获得注解
     * @param joinPoint
    
* @return
    
* @throws Exception
     */
   
private static OperationLogger giveController(JoinPoint joinPoint) throws Exception
    {
      

访问目标方法参数,有三种方法(实际有四种)

1.joinpoint.getargs():获取带参方法的参数

2.joinpoint.getTarget():.获取他们的目标对象信息

3..joinpoint.getSignature():(signature是信号,标识的意思):获取被增强的方法相关信息

Signature signature = joinPoint.getSignature();

//将其强转为MethodSignature 在根据这个去得到具体使用的方法       

 

 

MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

       
if (method != null)
        {

根据方法提供的方法去的到它上面的注解,参数就是我们的自定义注解的类对象

在这里,参数是什么样的注解,你得到的就是相应注解的对象,接收的时候返回值类型也就是相应的类型
            return method.getAnnotation(OperationLogger.class);
        }
       
return null;
    }

 

被调用的方法,在这里获得方法上面的注解返回即可,这里由于我们的注解比较的少,所以我们直接返回的就是字符串,如果有多个,可以分装一个对象这里直接返回对像即可
    public static String process(Class clazz) {
       
//找方法

        String modelName=null;
        Method[] methods = clazz.getMethods();
       
for (Method method : methods) {
           
OperationLogger annotation = method.getAnnotation(OperationLogger.class);//获取指定类型的注解
            //动态代理对象,

            if (annotation != null) {
               
//说明有注解
                modelName = annotation.modelName();
                String user = annotation.user();

            }
        }
       
return modelName;
    }
}

以上就是我们的日志事务切面,以及对日志的处理都已经准备完毕

 

准备好了注解以及事务切面之后,我们来写一个控制类

再其上面声明我我们自定义的注解

我们以得到数据库列表这个控制器为例

 

之后我们就来编写服务层

再得到参数之后将其直接插入数据库即可

 

之后我们除了之前与SSM一样的配置意外,我们还应该对我们的注解开启扫描:

 

在Springmvc的配置文件当中加尔u这两个即可;

 

最后我们来测试的结果:

以上就完成了通过springAop的日志事务来将我们用户的一步步操作注入到数据库当中,实现了,数据库存储操所日志的功能

 版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_42112846;

 

猜你喜欢

转载自blog.csdn.net/qq_42112846/article/details/81293420
今日推荐