代理模式和面向切面編程


前言:從代理的角度總結Spring AOP

一、靜態代理:你不知道我想做什么,我也不關心你做了什么

package simpleproxy;

// 設計一個接口
interface Shape {
    void draw();

    void erase();

}

// 接口的實現
class Rectangle implements Shape {

    @Override
    public void draw() {
        System.out.println("draw Rectangle");
    }

    @Override
    public void erase() {
        System.out.println("erase Rectangle");
    }

}

// 針對接口設計代理,添加代理的邏輯
class SimpleProxy implements Shape {
    Shape shape;

    void setShape(Shape shape) {
        this.shape = shape;
    }

    @Override
    public void draw() {
        System.out.println("create Rectangle");
        shape.draw();
    }

    @Override
    public void erase() {
        shape.erase();
        System.out.println("destroy Rectangle");
    }
}

public class SimpleProxyDemo {
    private Shape shape;

    public SimpleProxyDemo(Shape shape) {
        this.shape = shape;
    }

    public static void main(String[] args) {
        Rectangle target = new Rectangle();
        SimpleProxy proxy = new SimpleProxy();
        proxy.setShape(target);
        SimpleProxyDemo demo = new SimpleProxyDemo(proxy);
        demo.shape.draw();
        demo.shape.erase();
    }
}
View Code

或許大多數時候通過這樣的方式實現代理已經足夠了,不過這只是故事的開始。

二、JDK動態代理:反射遇上動態編譯

JDK動態代理的本質是通過反射形成.java文件,再利用動態編譯生成.class文件

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Shape {
    void draw();

    void erase();

}

class Rectangle implements Shape {

    @Override
    public void draw() {
        System.out.println("draw Rectangle");
    }

    @Override
    public void erase() {
        System.out.println("erase Rectangle");
    }

}

// 把代理的邏輯放在InvocationHandler實現類中,從而可以讓任意接口代理相同的邏輯
class Handler implements InvocationHandler {
    Object targer;

    Handler(Object targer) {
        this.targer = targer;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("create Target");
        Object o = method.invoke(targer, args);
        System.out.println("destroy Target");
        return o;
    }

}

public class DynamicProxyDemo {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        Handler h = new Handler(rectangle);
        Shape proxy = (Shape) Proxy.newProxyInstance(Rectangle.class.getClassLoader(), Rectangle.class.getInterfaces(),
                h);
        proxy.draw();
        proxy.erase();
    }
}
View Code

三、CGLIB動態代理:字節碼技術的輝煌一筆

需要引入的兩個依賴關系

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.ow2.asm/asm -->
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>4.2</version>
</dependency>

當代理對象不是通過接口實現的時候,JDK動態代理就失去了往日的榮光。可是孜孜不倦的程序員們永遠不會停住前進的腳步,勝利的旗幟又一次插在了巫妖王的城堡上。

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

// 丟失的Shape接口
class Rectangle {
    public void draw() {
        System.out.println("Draw Rectangle");
    }

    public void erase() {
        System.out.println("Erase Rectangle");
    }
}

// 共同的代理邏輯
class Interceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("create Target");
        Object o = proxy.invokeSuper(obj, args);
        System.out.println("destroy Target");
        return o;
    }
}

public class CglibDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Rectangle.class);
        enhancer.setCallback(new Interceptor());
        Rectangle proxy = (Rectangle) enhancer.create();
        proxy.draw();
        proxy.erase();
    }
}
View Code

四、Spring AOP:面向切面的集大成者

無論是JDK的實現方式還是CGLIB的字節碼技術,我們都可以利用Spring AOP做最后的統一處理。要說利用Spring AOP創建代理的唯一不足之處就是,無論被代理對象還是代理對象本身都必須交給Spring容器來管理,如果你僅僅是希望實現一個簡單的代理邏輯而並不願意大范圍修改代碼,引入Spring顯然過於笨重。

一個簡單的Service接口

package aop;

public interface Service {
    void bar();
}

創建ServiceImpl類實現上述接口

package aop;

public class ServiceImpl implements Service {

    @Override
    public void bar() {
        System.out.println("service bar");
    }
}

編寫ServiceB類(沒有實現接口的被代理對象)

package aop;

public class ServiceB {
    public void throwException(int type) {
        System.out.println("service B");
        if (type == 1) {
            throw new IllegalArgumentException("測試異常");
        }
    }
}

 

編寫功能齊備的代理邏輯

package aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class AdviceImpl {
    // 方法前插入
    public void doBefore(JoinPoint jp) {
        System.out.println(
                "log Begining method: " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName());
    }
    
    // 方法后插入
    public void doAfter(JoinPoint jp) {
        System.out.println(
                "log Ending method: " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName());
    }
        
    // 前后一起插入的完整邏輯
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        long time = System.currentTimeMillis();
        Object retVal = pjp.proceed();
        time = System.currentTimeMillis() - time;
        System.out.println("process time: " + time + " ms");
        return retVal;
    }

    // 捕獲異常,相當於把被代理對象放置在try...catch中
    public void doThrowing(JoinPoint jp, Throwable ex) {
        System.out.println("method " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName()
                + " throw exception");
        System.out.println(ex.getMessage());
    }
} 

通過xml文件配置切面、切點和建言。

<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
    <aop:config>
        <aop:aspect id="aspect" ref="aspectBean">
            <aop:pointcut id="businessService" expression="execution(* aop.*.*(..))" />
            <aop:before pointcut-ref="businessService" method="doBefore" />
            <aop:after pointcut-ref="businessService" method="doAfter" />
            <aop:around pointcut-ref="businessService" method="doAround" />
            <aop:after-throwing pointcut-ref="businessService"
                method="doThrowing" throwing="ex" />
        </aop:aspect>
    </aop:config>
    <bean id="aspectBean" class="aop.AdviceImpl" />
    <bean id="service" class="aop.ServiceImpl" />
    <bean id="serviceB" class="aop.ServiceB" />
</beans>

通過junit演示

package aop;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopTest {
    @Test
    public void aop() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("aop/spring.xml");
      // 查看所有對象是否都正確的交給spring來管理
        for (String beanName : ctx.getBeanDefinitionNames()) {
            System.out.println(beanName);
        }
        System.out.println("===============偉大的分割線================");
        ServiceB serviceB = (ServiceB) ctx.getBean("serviceB");
        serviceB.throwException(1);
    }
}

之前我曾經寫過一篇博客專門總結過spring官方文檔有關aop的章節,感興趣的朋友可以自己查看。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM