Spring Aop 應用實例與設計淺析

来源:http://www.cnblogs.com/kingszelda/archive/2017/07/12/7138142.html
-Advertisement-
Play Games

0.代碼概述 代碼說明:第一章中的代碼為了突出模塊化拆分的必要性,所以db採用了真實操作。下麵代碼中dao層使用了列印日誌模擬插入db的方法,方便所有人運行demo。 1.項目代碼地址:https://github.com/kingszelda/SpringAopPractice 2.結構化拆分,代 ...


0.代碼概述

代碼說明:第一章中的代碼為了突出模塊化拆分的必要性,所以db採用了真實操作。下麵代碼中dao層使用了列印日誌模擬插入db的方法,方便所有人運行demo。

1.項目代碼地址:https://github.com/kingszelda/SpringAopPractice

2.結構化拆分,代碼包結構:org.kingszelda.version1

3.Spring AOP,代碼包結構:org.kingszelda.version2

4.AspectJ AOP,代碼包結構:org.kingszelda.version3

1.為什麼會出現AOP

  相信很多人和我一樣,編程入門從c語言開始,之後接觸的java等其他面向對象語言。剛接觸編程語言時編寫的代碼以實現功能為首要目標,因此很少考慮模塊化、封裝等因素,以一個計算功能的web項目為例。該web項目有如下功能:

  •  通過http介面提供加法計算功能
  •  計算請求與結果需要留存
  •  需統計介面調用次數,列印請求響應日誌,列印方法運行時間

  就算基礎java語言而言,這個功能也比較簡單。如果不考慮http協議的問題,單獨編寫demo功能只需要一部分。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 計算器
 */
public class CalculateService {


    private static final Logger logger = LoggerFactory.getLogger(CalculateService.class);

    //1.構造介面調用次數計數Map
    public Map<String, Integer> countMap = new HashMap<String, Integer>();

    public int add(int first, int second) throws Exception {
        //2.獲得計算開始時間
        long start = System.currentTimeMillis();
        //3.列印入口參數
        logger.info("方法入參為:{}{}", first, second);
        //4.將該方法調用次數+1後放入map
        countMap.put("calcAdd", count);
        //5.計算加法
        int result = first + second;
        //6.載入mysql驅動
        Class.forName("com.mysql.jdbc.Driver");
        //7.配置mysql連接屬性,地址、用戶名、密碼
        String url = "jdbc:mysql://localhost:3306/samp_db";
        String userName = "root";
        String passWord = "123456";
        //8.獲得mysql連接
        Connection conn = DriverManager.getConnection(url, userName, passWord);
        //9.生成插入sql
        String sql = "INSERT INTO calcAdd (`first`,`second`,`result`) VALUES(?,?,?)";
        //10.使用preparedStatement防止sql註入
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, String.valueOf(first));
        ps.setString(2, String.valueOf(second));
        ps.setString(3, String.valueOf(result));
        //11.執行sql
        ps.execute();
        //12.釋放sql與con連接
        ps.close();
        conn.close();
        //13.列印返回參數
        logger.info("方法結果為:{}", result);
        //14.列印方法總耗時
        logger.info("運行時間:{}ms", System.currentTimeMillis() - start);
        //13.返回計算結果
        return result;
    }
}

 

1.1 模塊化

  上述代碼完全可以滿足要求,只是看起來有點長,當新增減法計算器等其他計算功能的時候,新增的代碼重覆的很多,比如計算的“+”號換成“-”號,存入資料庫表從加法表換到減法表,然後計算介面調用次數。從模塊化的角度來看,上面的加法計算器代碼就可以做如下拆分:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.Statement;

/**
 * 資料庫工具類
 */
public class DbUtil {

    public static Connection getConnection() throws Exception {
        //1.載入mysql驅動
        Class.forName("com.mysql.jdbc.Driver");
        //2.配置mysql連接屬性,地址、用戶名、密碼
        String url = "jdbc:mysql://localhost:3306/samp_db";
        String userName = "root";
        String passWord = "123456";
        //3.獲得mysql連接
        return DriverManager.getConnection(url, userName, passWord);
    }

    public static void closeCon(Connection connection, Statement statement) throws Exception {
        //1.先關閉sql連接
        statement.close();
        //2.再關閉資料庫連接
        connection.close();
    }

    public static Statement getInsertAddStatement(int first, int second, int result, Connection connection) throws Exception {
        //7.生成插入sql
        String sql = "INSERT INTO calcAdd (`first`,`second`,`result`) VALUES(?,?,?)";
        //8.使用preparedStatement防止sql註入
        PreparedStatement ps = connection.prepareStatement(sql);
        ps.setString(1, String.valueOf(first));
        ps.setString(2, String.valueOf(second));
        ps.setString(3, String.valueOf(result));
        return ps;
    }

}
import java.util.HashMap;
import java.util.Map;

/**
 * 方法調用次數計數器
 */
public class CountUtil {

    public static Map<String, Integer> countMap = new HashMap<>();

    public static void countMethod(String methodName) {
        Integer count = countMap.get(methodName);
        count = (count != null) ? new Integer(count + 1) : new Integer(1);
        countMap.put(methodName, count);
    }
}
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 計算器
 */
public class CalculateService {


    private static final Logger logger = LoggerFactory.getLogger(CalculateService.class);

    //1.構造介面調用次數計數Map
    public Map<String, Integer> countMap = new HashMap<String, Integer>();
    
    public int add(int first, int second) throws Exception {
        //2.獲得計算開始時間
        long start = System.currentTimeMillis();
        //3.列印入口參數
        logger.info("方法入參為:{}{}", first, second);
        //4.將該方法調用次數+1後放入map
        CountUtil.countMethod("calcAdd");
        //5.計算加法
        int result = first + second;
        //6.資料庫操作
        Connection conn = DbUtil.getConnection();
        Statement ps = DbUtil.getInsertAddStatement(first, second, result, conn);
        DbUtil.closeCon(conn, ps);
        //7.列印返回參數
        logger.info("方法結果為:{}", result);
        //8.列印方法總耗時
        logger.info("運行時間:{}ms", System.currentTimeMillis() - start);
        //9.返回計算結果
        return result;
    }
}

 

經過上述拆分,功能由原來的一塊代碼變為2大塊與3小塊,大塊分別是:

  1. 資料庫相關處理
  2. 方法調用計數處理

小塊則是:

  1. 列印請求
  2. 列印方法耗時
  3. 列印響應
  4. 統計方法調用次數

  經過拆分以後,代碼的復用性增強了很多,模塊之間的邊界也變得很清晰。並且隨著設計模式的發展,上面的2大塊可以進行進一步抽象,可以抽象出一個統一的主邏輯,然後加法計算進一步抽象為運算模塊,這樣就可以通過派生支持減法乘法等其他方法運算。這是數據設計模式的內容,這裡不做贅述。此時代碼拆分邏輯顯然是垂直拆分,如圖:

  如圖所示,模塊化之前,代碼是一整塊,當功能越來越餓複雜之後,這塊代碼將無法區分邊界,變得不好維護。模塊化之後,代碼分模塊獨立,邊界清晰、好維護。由於代碼總是一行一行的從上到下執行,所以很自然的拆分邏輯就是從上到下縱向拆分。

  但是當我們仔細分析不同模塊之間的區別之後,現有的縱向拆分並非達到了最佳狀態。因為大塊之間雖然清晰了,但是小塊之間還是散落在各處,並且都是簡單的兩行,無法進行進一步封裝。並且代碼模塊的業務也不相同。比如上述的存入資料庫與計算方法調用次數之間就有區別。資料庫模塊關心具體業務,比如是哪個資料庫,那張表,插入語句的具體內容。但是計算方法調用次數模塊是不關心業務的,只是對調用次數進行統計,這種模塊的應用有很多,比如列印日誌,計算qps,計算運行時間等,不論是什麼業務,這種運算總是相同的。所以,當業務變複雜之後,這些代碼可以進行橫向拆分。

至此,我們可以先給出模塊化拆分之後的代碼:

此時的代碼結構是:

此時的代碼為:

package org.kingszelda.version1.service;

import org.kingszelda.common.dao.AddDao;
import org.kingszelda.common.dao.SubDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;

/**
 * Created by shining.cui on 2017/7/15.
 */
@Service
@RequestMapping("version1")
public class CalculateService {

    private static final Logger logger = LoggerFactory.getLogger(CalculateService.class);

    @Resource
    private AddDao addDao;

    @Resource
    private SubDao subDao;

    @Resource
    private MethodCounter methodCounter;

    public int add(int first, int second) {
        //1.獲得計算開始時間
        long start = System.currentTimeMillis();
        //2.列印入口參數
        logger.info("方法入參為:{}{}", first, second);
        //3.計算調用次數
        methodCounter.count("sub");
        //4.計算加法
        int result = first + second;
        //5.插入資料庫
        addDao.insert(first, second, result);
        //6.列印返回參數
        logger.info("方法結果為:{}", result);
        //7.列印方法總耗時
        logger.info("運行時間:{}ms", System.currentTimeMillis() - start);
        //8.返回結果
        return result;
    }

    public int sub(int first, int second) {
        //1.獲得計算開始時間
        long start = System.currentTimeMillis();
        //2.列印入口參數
        logger.info("方法入參為:{}{}", first, second);
        //3.計算調用次數
        methodCounter.count("sub");
        //4.計算加法
        int result = first - second;
        //5.插入資料庫
        subDao.insert(first, second, result);
        //6.列印返回參數
        logger.info("sub 方法結果為:{}", result);
        //7.列印方法總耗時
        logger.info("運行時間:{}ms", System.currentTimeMillis() - start);
        //8.返回結果
        return result;
    }
}
package org.kingszelda.version1.service;

import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * Created by shining.cui on 2017/7/15.
 */
@Component
public class MethodCounter {

    private static final Logger logger = LoggerFactory.getLogger(MethodCounter.class);

    //防止併發
    private static final Map<String, Integer> methodCountMap = Maps.newConcurrentMap();

    /**
     * 根據方法名進行調用次數計數
     */
    public void count(String methodName) {
        Integer methodCount = methodCountMap.get(methodName);
        methodCount = (methodCount != null) ? new Integer(methodCount + 1) : new Integer(1);
        logger.info("對方法{}進行次數加1,當前次數為:{}", methodName, methodCount);
        methodCountMap.put(methodName, methodCount);
    }

    public Map<String, Integer> getMethodCountMap() {
        return methodCountMap;
    }
}

1.2 切麵織入

  正如上面的代碼一樣,我們的項目開始支持加法與減法兩種計算,此時的縱向拆分的代碼是這樣的結構:

這樣的結構也很清晰,沒有任何問題,但是存在了一些小瑕疵,那就是違反了DRY法則(Don't Repeat Yourself):計算次數、列印求響應、列印方法耗時出現在不同的程式的相同位置。雖然模塊化之後的這兩個功能調用都只需要一行,但是依然是發生了重覆,這時候就是Aop登場的最佳時刻。

  當時用了Aop橫向拆分之後,業務模塊就只關心業務(加法計算器只關心加法計算與存入加法表),不用再關心一些通用的處理功能——日誌與qps。這時候的代碼發生了本質上的改變,首先需要一個Aop模塊功能,然後通過"配置"的方式橫向“織入”想要的代碼模塊中。這時候就算新增了乘法計算器,也只需要編寫業務功能——“乘法計算與入庫”,然後配置之前的Aop模塊即可生效。

  從圖中我們可以看到,進行切麵編程有三個步驟:

  1. 定義切麵功能,Advice即通知,比如列印請求,計算調用次數的功能。
  2. 定義切點,Pointcut即切點,比如開始的時候列印請求,結束的時候列印響應。對應功能1的調用時間定義。
  3. 組織功能,即Advisor通知器,將切麵功能織入切點上。

  是時候引出Aop的定義了,以下定義引自維基百科:

面向側面的程式設計(aspect-oriented programming,AOP,又譯作面向方面的程式設計觀點導向編程剖面導向程式設計)是電腦科學中的一個術語,指一種程式設計範型。該範型以一種稱為側面(aspect,又譯作方面)的語言構造為基礎,側面是一種新的模塊化機制,用來描述分散在對象函數中的橫切關註點(crosscutting concern)。

aop_維基百科

2.AOP與Spring AOP

  AOP的出現使得代碼的整體設計括了一個維度,豎向寫業務邏輯,橫向切麵寫公共邏輯。如同其他概念一樣,這項技術有著各種各樣的實現,比較著名的有AspectJ,Javassist等。為了制定統一規範,於是出現了AOP聯盟來起引導與約束的作用。

  正如上圖所示,切麵拆分場景一般分為4種情況:

  1. 進入業務之前,比如統計qps,列印請求參數
  2. 完成業務之後,比如列印響應結果
  3. 環繞業務,比如計算方法耗時,需要在業務前計時,業務後取時間差
  4. 業務拋異常後,比如統一的異常處理。這一點上面的代碼沒有體現。

  上面4種情況的的間隔其實比較模糊,比如環繞業務其實可以包含前、後、異常這三種情況,因為本質上都是在業務運行前後加通用邏輯,其實Spring AOP的AfterReturningAdvice,MethodBeforeAdvice,ThrowsAdvice也是基於MethodInterceptor的環繞業務實現的。

  對於Spring來說,其核心模塊是IoC與AOP,其AOP是與IoC結合使用的。Spring不僅支持本身的Aop實現,同時也支持AspectJ的AOP功能。因此我們說:

  1. AOP是一種技術規範,本身與Spring無關
  2. Spring 實現了自身的AOP,即Spring AOP
  3. Spring 同時支持AspectJ的AOP功能

3.使用Spring AOP架構的代碼

使用Spring AOP調整後的業務代碼得到了一定的精簡,整體代碼結構如圖:

首先是定義三個切麵業務Advice,進行列印日誌、運行時間與統計qps功能。

package org.kingszelda.version2.aop;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 方法後切麵,列印響應結果
 * Created by shining.cui on 2017/7/15.
 */
@Component
public class CalculateAfterAdvice implements AfterReturningAdvice {

    private static final Logger logger = LoggerFactory.getLogger(CalculateAfterAdvice.class);

    public void afterReturning(Object returnObject, Method method, Object[] args, Object target) throws Throwable {
        String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
        String returnValue = String.valueOf(returnObject);
        logger.info("方法{}的響應結果為{}", methodName, returnValue);
    }
}
package org.kingszelda.version2.aop;

import org.kingszelda.version2.service.MethodCounter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 方法前切麵,列印請求參數,統計調用次數
 * Created by shining.cui on 2017/7/15.
 */
@Component
public class CalculateBeforeAdvice extends MethodCounter implements MethodBeforeAdvice {

    private static final Logger logger = LoggerFactory.getLogger(CalculateBeforeAdvice.class);

    public void before(Method method, Object[] args, Object target) throws Throwable {
        String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
        String argStr = Arrays.toString(args);
        logger.info("方法{}的請求參數為{}", methodName, argStr);
        count(methodName);
    }
}
package org.kingszelda.version2.aop;

import org.aopalliance.intercept.MethodInvocation;
import org.kingszelda.version2.service.CalculateService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.IntroductionInterceptor;
import org.springframework.stereotype.Component;

/**
 * 方法後切麵,列印響應結果
 * Created by shining.cui on 2017/7/15.
 */
@Component
public class CalculateMethodInterceptor implements IntroductionInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(CalculateMethodInterceptor.class);

    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        //1.獲得計算開始時間
        long start = System.currentTimeMillis();
        String methodName = methodInvocation.getMethod().getDeclaringClass().getSimpleName() + "." + methodInvocation.getMethod().getName();
        //2.運行程式
        Object proceed = methodInvocation.proceed();
        //3.列印間隔時間
        logger.info("方法{}運行時間:{}ms", methodName, System.currentTimeMillis() - start);
        return proceed;
    }

    public boolean implementsInterface(Class<?> aClass) {
        //滿足CalculateService介面的方法都進行攔截
        return aClass.isAssignableFrom(CalculateService.class);
    }
}

此時的業務代碼精簡為如下:

package org.kingszelda.version2.service.impl;

import org.kingszelda.common.dao.AddDao;
import org.kingszelda.common.dao.SubDao;
import org.kingszelda.version2.service.CalculateService;

import javax.annotation.Resource;

/**
 * Created by shining.cui on 2017/7/15.
 */
public class CalculateServiceImpl implements CalculateService {

    @Resource
    private AddDao addDao;

    @Resource
    private SubDao subDao;

    @Override
    public int add(int first, int second) {
        //1.計算加法
        int result = first + second;
        //2.插入資料庫
        addDao.insert(first, second, result);
        //3.返回結果
        return result;
    }

    @Override
    public int sub(int first, int second) {
        //1.計算加法
        int result = first - second;
        //2.插入資料庫
        subDao.insert(first, second, result);
        //3.返回結果
        return result;
    }
}

  到目前為止,切麵邏輯已經與業務邏輯分離,接下來需要做的就是定義切點PointCut與通知器Advisor,即將業務與切麵在合適的時候組合起來。這也是最容易出錯的地方。

<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd  http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--spring mvc註解預設配置-->
    <context:component-scan base-package="org.kingszelda"/>
    <mvc:annotation-driven/>

    <!--下麵是version2的配置,二選一均可-->

    <!-- version2 配置方案1 -->
    <bean id="calculateServiceV2" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 需要代理的介面 -->
        <property name="proxyInterfaces" value="org.kingszelda.version2.service.CalculateService"/>
        <!-- 被代理的實體,一定要是上面介面的實現類 -->
        <property name="target">
            <bean class="org.kingszelda.version2.service.impl.CalculateServiceImpl"/>
        </property>
        <!-- 調用代理對象方法時的額外操作 -->
        <property name="interceptorNames">
            <!--這裡的順序對責任鏈順序有直接影響,所以能合併的都可以合併在一個過濾器中,本例都可以合併到middleInterceptor中-->
            <list>
                <value>calculateMethodInterceptor</value>
                <value>calculateBeforeAdvice</value>
                <value>calculateAfterAdvice</value>
            </list>
        </property>
    </bean>

    <!-- version2 配置方案2 -->
    <!-- 業務前切點 -->
    <bean id="beforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <constructor-arg ref="calculateBeforeAdvice"/>
        <constructor-arg ref="pointcut"/>
    </bean>

    <!-- 返回前切點 -->
    <bean id="afterAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <constructor-arg ref="calculateAfterAdvice"/>
        <constructor-arg ref="pointcut"/>
    </bean>

    <!-- 環繞式切點,相對於before after throw,這是屬於高一層設計,因為before等也是通過Interceptor實現的 -->
    <bean id="middleInterceptor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <constructor-arg ref="calculateMethodInterceptor"/>
        <constructor-arg ref="pointcut"/>
    </bean>

    <!-- 定義切點位置,只有滿足正則的才攔截,如果不配置切點,則攔截所有介面方法 -->
    <bean id="pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*version2.*add|.*version2.*sub"/>
    </bean>

</beans>

  可以看到,Spring AOPde 配置方式可以分為兩種:一種是手動配置被代理對象,只對配置對象織入。另外一種是只聲明Advisor通知器,由Spring 根據定義的切點PointCut進行織入。兩種方法都可以,第一種更貼近於源碼的實現。

  至此,基於Spring AOP的整合已經結束,運行代碼後發現雖然業務沒有關係日誌等操作,但切麵邏輯已經完全織入業務中,業務代碼整潔了好多。

  這裡需要註意一點,我們已經不能像之前使用calculateService那樣直接註入使用了,因為我們需要使用的是calculateService的代理類,這個代理類在調用真正的業務實現之前根據配置依次調用切麵邏輯。上面的配置很明朗,我們使用的calculateServiceV2是由ProxyFactoryBean生成的對於介面proxyInterfaces的Proxy實現,真正被代理的對象對象是target,在調用target之前會依次執行interceptorNames配置的攔截器責任鏈。與此相關的代碼解釋的文章很多,這裡就扒源碼了。

4.Spring集成AspectJ的AOP功能

  在我看來Spring的AOP功能做的不夠有決心,要麼就定義好各種Spring AOP且切麵場景,要麼則都使用Intercepter由用戶統一管理。兩者都兼顧的Spring AOP在讓用戶使用的時候會產生一定的困惑,究竟使用那種方式更加好一些。並且Spring的老毛病也凸現出來,那就是xml如何妥善配置。

  而Spring對AspectJ的集成則方便了很多。項目結構如圖:

相對於Spring AOP時的代碼,變動只有一個切麵類與xml配置

package org.kingszelda.version3.aop;

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.stereotype.Component;

import java.util.Arrays;

/**
 * 基於AspectJ的切麵功能
 *
 * @author shining.cui
 * @since 2017-07-16
 */
@Aspect
@Component
public class ControllerAop extends MethodCounter {

    private static final Logger logger = LoggerFactory.getLogger(ControllerAop.class);

    // 攔截version3包中的所有service
    @Pointcut("(execution(* org.kingszelda.version3.service.*.*(..)))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object controllerHandler(ProceedingJoinPoint joinPoint) {
        String signature = joinPoint.getSignature().toShortString();
        //1.統計調用次數
        count(signature);
        String argStr = Arrays.toString(joinPoint.getArgs());
        //2.列印請求
        logger.info("方法{}的請求參數為{}", signature, argStr);
        Object result = null;
        //3.獲得計算開始時間
        long start = System.currentTimeMillis();
        try {
            //4.運行業務
            result = joinPoint.proceed();
        } catch (Throwable e) {
            logger.error("web 應用發生異常", e);
        }
        //5.列印運行時間
        logger.info("方法{}運行時間:{}ms", signature, System.currentTimeMillis() - start);
        String returnValue = String.valueOf(result);
        //6.列印響應
        logger.info("方法{}的響應結果為{}", signature, returnValue);
        return result;
    }

}

  配置也簡單了很多:

    <!-- 集成AspectJ的AOP功能 -->
    <aop:aspectj-autoproxy />

  程式運行結果與上一章相同。

5.總結

  Spring AOP是AOP聯盟規範中Spring的一種實現形式,同時Spring也支持了AspectJ的AOP實現。相對於應用來說,AspectJ的AOP結構更加清晰,配置也更加簡單。Spring中的AOP實現都是通過代理完成的,在預設的情況下,如果代理類是介面,則使用jdk動態代理,如果不是則使用CGLIB進行代理。

  通過AOP功能可以很好的將通用邏輯與業務邏輯分離,使得結構化更明朗。對於列印日誌,統計qps,統計運行時間,發送消息,寫入請求記錄表等操作均可以很好的支持。Spring框架本身的一些功能也是基於AOP實現的,比如Spring的事務管理。因此研究Spring AOP總是會給我們的學習與工作帶來好處。

  最後,以上代碼均可運行,地址參見第0章。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MultiProcessing模塊是一個優秀的類似多線程MultiThreading模塊處理併發的包 之前接觸過一點這個庫,但是並沒有深入研究,這次閑著無聊就研究了一下,算是解惑吧。 ...
  • 歐拉篩素數: 時間複雜度:O(n) 主要思路:對於每一個合數,讓他的最大的約數把他篩去 ...
  • 題目鏈接 DESCRIPTION INPUT 題目鏈接 DESCRIPTION INPUT INPUT INPUT OUTPUT OUTPUT SAMPLE INPUT 1 4 2 1 2 5 2 3 5 3 4 5 5 5 SAMPLE INPUT 1 4 2 1 2 5 2 3 5 3 4 5 ...
  • 位運算符 &與 |或 ^異或 <<左移 >>右移 >>>無符號右移 ~取反 註意:位運算是針對整數運算的 int i = 6,j = 10; 方式一:利用第三方變數 int k = i; i = j; j = k; 方式二:利用加減法 i = i + j; j = i - j; >j = i + j ...
  • 2017-07-15,這是我學習python的第一天。 首先,python是一門當下很火熱的開發語言,它的創始人是Guido Van Rossum。就目前情況而言,python語言的熱度持續上升,已經超過C#排名第四。Python崇尚優美,簡潔,清晰,是一種優秀並且廣泛使用的語言。 一、Python ...
  • 十進位轉成十六進位: Integer.toHexString(int i) 十進位轉成八進位 Integer.toOctalString(int i) 十進位轉成二進位 Integer.toBinaryString(int i) 十六進位轉成十進位 Integer.valueOf("FFFF",16 ...
  • 金庸經典《射雕英雄傳》里,黃蓉為了讓洪七公交自己和靖哥哥武功,天天對師傅美食相待,在做了“玉笛誰家聽落梅”這樣一些世間珍品之後,告訴師傅說今天要做的是"炒白菜"。洪七公露出非常欣賞的眼光,說:“好,我倒要看看你怎樣化腐朽為神奇。”上周五聽了一個我們內部的深度學習講座,基本這方面處於初始探索階段。上周 ...
  • 函數要短。短才方便閱讀、維護和設計。 函數只做一件事。依照單一職責原則(一個類只會因為一個原因改變)設計函數。一個函數要麼進行流程式控制制或邏輯判斷,要麼改變某事物的狀態,要麼計算並返回結果,要麼調用多個下一抽象級的其他函數(另一種流程式控制制而已)。不要有多餘功能。 我們可以把函數分解成多個抽象層級來設計 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...