Spring MVC AOP通過自定義註解方式攔截Controller等實現日誌管理

衣舞晨風發表於2016-08-18

之前一直寫.net,沒玩過spring,一直沒用過aop(面向切面程式設計)這類功能,當然不是說.net裡面沒有這類框架,企業庫就可以微軟企業庫官網

開始上程式碼:

註解定義

package com.jiankunking.common;

import java.lang.annotation.*;

/**
 * @author jiankunking
 * @Date: 2016/8/15
 * @Time: 11:09
 * @annotation OperationLogger
 */
@Retention(RetentionPolicy.RUNTIME)//註解會在class中存在,執行時可通過反射獲取
@Target(ElementType.METHOD)//目標是方法
@Documented//文件生成時,該註解將被包含在javadoc中,可去掉
public @interface OperationLogger
{
    /**
     * 模組名字
     */
    String modelName() default "";

    /**
     * 操作型別
     */
    String option();

}

@interface是用來自定義註釋型別的。
註釋的宣告用@符號後面跟上這個註釋型別的名字,再後面跟上括號,括號中列出這個註釋中元 素/方法的key-value對。值必須是常量。

AOP攔截部分

package com.jiankunking.common;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author jiankunking
 * @Date: 2016/8/15
 * @Time: 11:11
 * @annotation SysLogAspect
 */
@Aspect
@Component
public class SysLogAspect
{
    private static final Logger logger = Logger.getLogger(SysLogAspect.class);

    /**
     * 定義Pointcut,Pointcut的名稱,此方法不能有返回值,該方法只是一個標示
     */
    @Pointcut("@annotation(com.jiankunking.common.OperationLogger)")
    public void controllerAspect()
    {
        System.out.println("我是一個切入點");
    }

    /**
     * 前置通知(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
     */
    @AfterThrowing(value = "controllerAspect()", throwing = "e")
    public void doAfter(JoinPoint joinPoint, Exception e)
    {
        System.out.println("=====SysLogAspect異常通知開始=====");
        //handleLog(joinPoint, e);
    }

    /**
     * 環繞通知(Around advice) :包圍一個連線點的通知,類似Web中Servlet規範中的Filter的doFilter方法。可以在方法的呼叫前後完成自定義的行為,也可以選擇不執行。
     * @param joinPoint
     */
    @Around("controllerAspect()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable
    {
        System.out.println("=====SysLogAspect 環繞通知開始=====");
        //handleLog(joinPoint, null);
        Object obj= joinPoint.proceed();
        System.out.println("=====SysLogAspect 環繞通知結束=====");
        return  obj;
    }

    /**
     * 日誌處理
     *
     * @param joinPoint
     * @param e
     */
    private void handleLog(JoinPoint joinPoint, Exception e)
    {
        try
        {
            //獲得註解
            OperationLogger logger = giveController(joinPoint);
            if (logger == null)
            {
                return;
            }

            String signature = joinPoint.getSignature().toString(); // 獲取目標方法簽名
            String methodName = 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))
                {
                    //OpLogger logger = method.getAnnotation(OpLogger.class);
                    String clazzName = clazz.getName();
                    System.out.println("clazzName: " + clazzName + ", methodName: "
                            + methodName);
                }
            }

        } catch (Exception exp)
        {
            logger.error("異常資訊:{}", exp);
            exp.printStackTrace();
        }
    }

    /**
     * 獲得註解
     * @param joinPoint
     * @return
     * @throws Exception
     */
    private static OperationLogger giveController(JoinPoint joinPoint) throws Exception
    {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null)
        {
            return method.getAnnotation(OperationLogger.class);
        }
        return null;
    }

}

Aspect通常用於將必要的但和業務無關的邏輯和業務邏輯分離。

Spring使用的AOP註解分為三個層次:
前提條件是在xml中放開了

<!-- 開啟切面程式設計功能 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
  1. @Aspect放在類頭上,把這個類作為一個切面。
  2. @Pointcut放在方法頭上,定義一個可被別的方法引用的切入點表示式。
  3. 5種通知。
    1. @Before,前置通知,放在方法頭上。
    2. @After,後置【finally】通知,放在方法頭上。
    3. @AfterReturning,後置【try】通知,放在方法頭上,使用returning來引用方法返回值。
    4. @AfterThrowing,後置【catch】通知,放在方法頭上,使用throwing來引用丟擲的異常。
    5. @Around,環繞通知,放在方法頭上,這個方法要決定真實的方法是否執行,而且必須有返回值。

在Maven中加入以下以依賴

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mkyong.common</groupId>
    <artifactId>spring-mvc-log4j</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>SpringMVC + Log4j</name>

    <properties>
        <jdk.version>1.7</jdk.version>
        <spring.version>4.3.2.RELEASE</spring.version>
        <log4j.version>2.6.2</log4j.version>
        <jstl.version>1.2</jstl.version>
        <servletapi.version>3.1.0</servletapi.version>
        <org.aspectj-version>1.7.4</org.aspectj-version>
        <cglib.version>3.1</cglib.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- Log4j  start-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <!-- Log4j  end-->

        <!-- jstl -->
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>${jstl.version}</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servletapi.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <!-- AspectJ -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${org.aspectj-version}</version>
        </dependency>

        <!-- @Inject -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>${cglib.version}</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>${jdk.version}</source>
                    <target>${jdk.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

在spring-*.xml中加入spring支援,開啟aop功能

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"

       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd

       ">

    <!-- aop -->
    <bean id="logService" class="com.jiankunking.common.SysLogAspect"></bean>

    <!--Spring MVC使用ViewResolver來根據controller中返回的view名關聯到具體的View物件。使用View物件來渲染返回值以生成最終的檢視,如html,json或pdf等-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/WEB-INF/pages/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>
    <!-- 啟動對@AspectJ註解的支援  -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

    <!-- 啟用MVC註解 -->
    <mvc:annotation-driven/>

    <!-- 指定Sping元件掃描的基本包路徑 -->
    <context:component-scan base-package="com.jiankunking.controller">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <!--靜態檔案處理-->
    <mvc:resources location="/resources/" mapping="/resources/**"/>
</beans>

註解也寫好了,spring也配置好了,在controller裡面怎麼用呢?

Controller應用

package com.jiankunking.controller;

import com.jiankunking.common.OperationLogger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping(value = "/Welcome", produces = "text/html;charset=UTF-8")
public class WelcomeController
{
    @OperationLogger(modelName = "WelcomeController", option = "getWelcome")
    @RequestMapping(value = "/getWelcome", method = RequestMethod.POST)
    public void getWelcome()
    {
        //異常攔截測試
        //int i = 9 / 0;
        System.out.println("controller方法執行!");
    }

}

如何測試呢?
從前端發起ajax請求controller,即可:

$.ajax({
                type: "POST",
                url: "/Welcome/getWelcome",
                contentType: "application/json",
                data: null,
                success: function ()
                {
//                    alert("22");
                },
                error: function ()
                {
//                    alert("失敗!");
                }
            });

效果如下:
這裡寫圖片描述

由於這裡是通過Spring的@Aspect註解實現的AOP,所以同一個類中的某個方法A(該方法沒有註解標識)呼叫另一個有註解標識的方法B時,方法B上的註解是不會起作用的。

這裡寫圖片描述

演示demo:
http://download.csdn.net/detail/xunzaosiyecao/9609918

xml方式實現aop攔截及aop基礎知識參考:
xml實現aop攔截

Spring Boot 自定義註解方式攔截Controller等實現日誌管理:
Spring Boot 自定義註解

個人微信公眾號:
這裡寫圖片描述

作者:jiankunking 出處:http://blog.csdn.net/jiankunking

相關文章