iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0

Review

Container的職責在於創建、配置與組裝bean,昨天我們學到了
如何使用@Value的用法、完全註解開發、泛型依賴注入

今日將進入新的領域AOP,今日我們先談談AOP是在什麼樣的場景出現的吧

先看一個例子

看看我們怎麼為計算器加入log

public interface Calculator {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
}

我們可能會用System.out.println,但我們會發現要改log的format要改四個地方@@

public class MyCalcultor implements Calculator{
    @Override
    public int add(int i, int j) {
        System.out.println("add method start,parameter:"+i +","+j);
        int result = i+j;
        System.out.println("add method end,result:"+result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        System.out.println("sub method start,parameter:"+i +","+j);
        int result = i-j;
        System.out.println("sub method end,result:"+result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        System.out.println("mul method start,parameter:"+i +","+j);
        int result = i*j;
        System.out.println("mul method end,result:"+result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("div method start,parameter:"+i +","+j);
        int result = i/j;
        System.out.println("div method end,result:"+result);
        return result;
    }
}

我們可能會想到寫一個LogUtils來取代System.out.println出現的位置

public class LogUtils {
    public static void logStart(String methodName,Object...args){
        System.out.println(methodName+" method start,parameter:"+ Arrays.asList(args));
    }
    public static void logEnd(String methodName,Object result){
        System.out.println(methodName+" method end,result:"+ result);
    }
}

然後你可能還會想要在方法的前後加入開始與結束時間,看一下效能

public class MyCalcultor implements Calculator{
    @Override
    public int add(int i, int j) {
        LogUtils.logStart("add",i,j);
        long startTime = System.currentTimeMillis();
        int result = i+j;
        LogUtils.logEnd("add",result);
        long endTime = System.currentTimeMillis();
        System.out.println("add method execute:"+(endTime - startTime));
        return result;
    }

    @Override
    public int sub(int i, int j) {
        LogUtils.logStart("add",i,j);
        long startTime = System.currentTimeMillis();
        int result = i-j;
        LogUtils.logEnd("add",result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        LogUtils.logStart("add",i,j);
        long startTime = System.currentTimeMillis();
        int result = i*j;
        LogUtils.logEnd("add",result);
        long endTime = System.currentTimeMillis();
        System.out.println("add method execute:"+(endTime - startTime));
        return result;
    }

    @Override
    public int div(int i, int j) {
        LogUtils.logStart("add",i,j);
        long startTime = System.currentTimeMillis();
        int result = i/j;
        LogUtils.logEnd("add",result);
        long endTime = System.currentTimeMillis();
        System.out.println("add method execute:"+(endTime - startTime));
        return result;
    }
}

然後...是不是可以再增加稽核軌跡看一下是那個user執行這個作業。然後你就會發現程式都糊再一起,真正的商業邏輯都被淹沒了。那有沒有方法能夠只保留商業邏輯像面這樣呢,而且還能保留log、稽核軌跡、效能紀錄呢,答案是可以的,就透過AOP來達成。

public class MyCalcultor implements Calculator{
    @Override
    public int add(int i, int j) {
        int result = i+j;
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i-j;
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i*j;
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i/j;
        return result;
    }
}

AOP (Aspect Oriented Programming)

面向切面編程,指在程式運行期間,將某段代碼動態的切入到指定的方法跟指定位置進行運作。面向能夠模組化我們關注的點,例如transaction management

AOP原理

要能達到將代碼動態切入到指定方法跟位置執行,需要透過動態代理的方式達成。透過代理對象在invoke對象方法前後就可以輕易加入想要執行的動作。
https://ithelp.ithome.com.tw/upload/images/20221002/20128084TbLccUDSbb.jpg
圖片來源:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-proxying

動態代理

AOP底層的動態代理有兩種方式

  • 代理對象Interface,使用JDK動態代理
  • 代理對象Interface,使用CGLIB動態代理

JDK動態代理範例

public class CalculatorProxy {
    public static Calculator getProxy(final Calculator calculator){
        InvocationHandler invokHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                LogUtils.logStart(method.getName(),args);
                Object result = method.invoke(calculator,args);
                LogUtils.logEnd(method.getName(),result);
                return result;
            }
        };
        Class<?>[] interfaces = calculator.getClass().getInterfaces();
        ClassLoader loder =calculator.getClass().getClassLoader();
        Object proxy = Proxy.newProxyInstance(loder,interfaces,invokHandler);
        return (Calculator)proxy;
    }
}
@Test
public void testDay17(){
    Calculator proxy = CalculatorProxy.getProxy(new MyCalcultor());
    proxy.add(1,5);
}

Result
https://ithelp.ithome.com.tw/upload/images/20221002/20128084sdN2nJY8HW.jpg

Reference


上一篇
Day16 - Dependency Injection (11)
下一篇
Day18 - AOP 初試啼聲
系列文
這些年,我們早該學會的Spring Framework30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言