1. 程式人生 > >反射包 java.lang.reflect⑦ 之 Java 中 Proxy 動態代理類 探祕(三)

反射包 java.lang.reflect⑦ 之 Java 中 Proxy 動態代理類 探祕(三)

這個系列好久沒有續作了,你以為完了,錯了。這個動態代理有說不完的知識點,我也是在不斷的學習中才瞭解得到更多的知識。但無可否認的一點是它需更多其他的知識的支援,比如設計模式,設計思想。工作越久越覺得設計模式這個東西的重要性。

 其實動態代理的前兩個例子只是簡單的列出了它的一個demo而已,實際工作中寫程式碼也不會是這麼寫的,當然知識點還是用到這麼一點。實際中所使用的是結合更復雜的業務來實現的。

接下來,我來自己使用動態代理模仿AOP實現一個快取。

用到的知識點:

自定義註解;

動態代理;

代理模式;

解耦思想(個人認為)。

1.定義幾個註解(不明白註解是什麼意思的我覺得也不要緊,可以理解放到某個類或某個方法上面的標記(事實註解的本質就是這個意思))。

1)快取註解(它將會放到某個方法的頭頭上,標記這個方法執行時會做一些快取操作)

package com.hlm.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 新增快取自定義註解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyCache {
     String key() default "" ;
     String value() default "";
}

2)快取失效註解(它將會放到某個方法的頭頭上,標記這個方法執行時會做一些快取失效操作)

package com.hlm.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 快取失效自定義註解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface EviMyCache {
    String key()  default "";
    String value() default "";
}

3)我的aop註解(它將會放到某個類的頭頭上,標記這個的例項將使用代理)

package com.hlm.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAop {
    String value() default "";
}

2.先定義一個介面來規範我自己的這個aop

package com.hlm.annotation;

import java.lang.reflect.Method;

public interface FlowInterface {

    public Object doPre(Object target, Method method, Object[] args) throws Exception;

    public Object doafter(Object target, Method method, Object[] args) throws Exception;

    public Object process(Object target, Method method, Object[] args)throws Exception;
}

這個是什麼意思呢?我的想法是,所有想用我這個aop的業務場景的類都得去實現我這個接口才能使用我的aop,也就是出規矩。比如我現在是使用這個aop實現一個快取功能,可能有一天我使用它去實現一個日誌功能。這都得通過實現我這個接口才能用。

3.代理類

1).這個代理類我不用工廠的方法去實現而使用了一種類似自生自賣的方式(工廠跟它建立的東西在一個類中)來實現,它自身有一個快取它曾建立過的物件的功能。

2).切面要織入的功能通過註解的方式來確實。也就是通過註解這個標記來看要織入什麼功能。看一開始的flowMap 就知道我定義了兩種織入。MyCacheImpl和FlowImpl都是我定義介面FlowInterface的實現類。

3)。動態代理類一般都是去實現一個InvocationHandler介面,必需複寫的方法invoke最終呼叫的其實是我定義介面的實現類的process方法。這就給了實現了很自由的空間了。

package com.hlm.annotation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

/**
 * 我的動態代理類
 */
public class MyDynamicProxy implements InvocationHandler {

    private static Map<String,FlowInterface> flowMap = new HashMap<String,FlowInterface>();
    static {
        flowMap.put("MyCacheImpl" ,new MyCacheImpl());
        flowMap.put("FlowImpl" ,new FlowImpl());
    }

    Logger log = LoggerFactory.getLogger(MyDynamicProxy.class);

    private Object target ;//當前代理目標
    private Object proxy ;//當前代理
    private FlowInterface flowInterface ;//方法前後需要注入的東西在此類中定義
    //用於儲存一些已生成過代理的物件,防止重得生成浪費效能(相當於自己的一個快取)
    private static Map<Class<?>,MyDynamicProxy> myMap = new HashMap<Class<?>,MyDynamicProxy>();


    public MyDynamicProxy(){

    }

    /**
     * 獲取入參的代理物件方法
     * @param clazz
     * @param <T>
     * @return
     */
    public synchronized  static<T> T getProxyInstants(Class<T> clazz ){

        //優先在自己的快取內尋找
        MyDynamicProxy newProxy = myMap.get(clazz) ;
        if(newProxy != null){
            return (T)newProxy.getProxy() ;
        }

        //找不到則新建一個
        newProxy = new MyDynamicProxy();
        try{
            //先為目標類生成目標物件(如果該類中有一些自動注入的屬性會有什麼情況呢)
            T target = clazz.newInstance();

            //未使用aop註解則直接返回自身
            MyAop ap = clazz.getAnnotation(MyAop.class);

            if(ap == null){
                return  target ;
            }
            String val = ap.value() ;
            FlowInterface flowInterface = null;
            if(val == null || val.equals("")){
                return  target ;
            }else{
                flowInterface = flowMap.get(ap.value());
            }

            Annotation[] l = clazz.getDeclaredAnnotations();
            //生成代理物件
            T proxy = (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),newProxy);

            //設定
            newProxy.setTarget(target);
            newProxy.setProxy(proxy);
            newProxy.setFlowInterface(flowInterface);
            myMap.put(clazz , newProxy) ;

        } catch (IllegalAccessException e) {

            e.printStackTrace();
        } catch (InstantiationException e) {

            e.printStackTrace();
        }

        //返回生成的代理
        return (T)newProxy.getProxy() ;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //log.info("呼叫方法"+method.getName()+"前");
        /*flowInterface.doPre(method.getName(),args);
        Object result = method.invoke(target , args) ;
        //log.info("呼叫方法"+method.getName()+"後");
        flowInterface.doPre(method.getName(),args);*/
        Object result = flowInterface.process(target ,method,args) ;
        return result;
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public Object getProxy() {
        return proxy;
    }

    public void setProxy(Object proxy) {
        this.proxy = proxy;
    }

    public FlowInterface getFlowInterface() {
        return flowInterface;
    }

    public void setFlowInterface(FlowInterface flowInterface) {
        this.flowInterface = flowInterface;
    }
}

4.快取實現類

1).通過獲取方法上的註解來確定是快取還是快取失效。

2).通過註解方法的值來確定快取的key是什麼,這裡簡單模仿@caching的樣子,表示式只支援方法入參這一層物件,不支援到入參的屬性往後的。

package com.hlm.annotation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;

import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

public class MyCacheImpl implements FlowInterface {

    ConcurrentHashMap<String , Object> cache = new ConcurrentHashMap<String , Object>();

    Logger log = LoggerFactory.getLogger(MyCacheImpl.class);

    @Override
    public Object doPre(Object target, Method method, Object[] args)throws Exception {

        Object obj = null ;
        Method[] targetMethods = target.getClass().getMethods();
        Method targetMethod = null ;
        for(Method m : targetMethods){
            if(m.getName().equals(method.getName())){
                targetMethod = m ;
                break ;
            }
        }
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] params = u.getParameterNames(targetMethod);
            //獲取註解
            MyCache an = targetMethod.getAnnotation(MyCache.class);
            //log.info("MyCache :"+an);
            EviMyCache ean = targetMethod.getAnnotation(EviMyCache.class);
            //log.info("EviMyCache :"+ean);
            if(an != null){
                String key =an.key();
                String[] afterKeys = transfeKeys(key,params,args);
                String val = an.value();
                obj = cache(target,method ,args,afterKeys ,val);
            }else if(ean != null){
                String key =ean.key();
                String[] afterKeys = transfeKeys(key,params,args);
                String val = ean.value();
                obj = eviCache(target,method ,args,afterKeys ,val);
            }

            return obj ;
    }

    @Override
    public Object doafter(Object target, Method method, Object[] args) {
        return null ;
    }

    @Override
    public Object process(Object target, Method method, Object[] args) throws Exception {

        Object obj = this.doPre(target, method ,args);
        return obj;
    }

    /**
     * 快取
     * @param target
     * @param method
     * @param args
     * @return
     * @throws Exception
     */
    private Object cache(Object target, Method method, Object[] args ,String[] keys ,String val)throws Exception{

        StringBuffer key = buildKeys(method,args,keys,val);
        log.info("key is :"+key);
        //先從快取裡取
        Object obj = cache.get(key.toString());
        //取到了直接返回
        if(obj != null){
            log.info("快取命中,返回從快取中取得的結果");
            return obj ;
        }
        //取不到則調目標的方法去取
        try{
            obj = method.invoke(target ,args) ;
        }catch(Exception e){
            throw e ;
        }
        //目標方法返回的結果為空,那麼不玩了,直接返回
        if(obj == null){
            return null ;
        }
        //目標方法有返回結果,先將其快取,再返回
        log.info("快取未命中,將結果儲存到快取中");
        cache.put(key.toString() ,obj );
        return obj ;
    }

    /**
     * 快取失效
     * @param target
     * @param method
     * @param args
     * @throws Exception
     */
    private Object eviCache(Object target, Method method, Object[] args,String[] keys ,String val)throws Exception{
        StringBuffer key =buildKeys(method,args,keys,val);
        log.info("key is :"+key);
        //快取失效
        cache.remove(key.toString());
        log.info("快取失效");
        Object obj = null ;
        try{
            obj = method.invoke(target ,args) ;
        }catch(Exception e){
            throw e ;
        }

        return obj ;

    }

    private StringBuffer buildKeys( Method method, Object[] args,String[] keys ,String val){
        StringBuffer key =new StringBuffer(val);
        if(keys == null){
            //先組裝key:methodName+arg1+arg2+...argn(預設所有入參作為key)
            key.append(method.getName());
            for(Object arg: args){
                key.append(arg);
            }
        }else{
            for(Object arg: keys){
                key.append(arg);
            }
        }
        return key ;
    }

    /**
     *
     * @param key
     * @param params
     * @param args
     * @return
     */
    private String[] transfeKeys(String key ,String[] params , Object[] args){
        String[] keys = null ;
        String[] afterKeys = null;
        //沒有設定key,則用預設的
        if(key!=null && key != ""){
                key = key.replace("{", "");
                key = key.replace("}", "");
                keys = key.split(",");
                afterKeys = new String[keys.length] ;
                for(int i=0 ; i<keys.length ; i++){
                    if(!keys[i].startsWith("#")){
                        afterKeys[i] = keys[i];
                        continue;
                    }
                    for(int j = 0; j < params.length ; j++){
                        String k = keys[i].substring(1);
                        if(params[j].equals(k)){
                            afterKeys[i] =  args[j].toString();
                            break ;
                        }
                    }
                }
        }
        return afterKeys ;
    }
}

5.測試類

有一個首要的條件,想使用動態代理,目標類必需是介面的實現。

介面

package com.hlm.annotation;

public interface TestInterface {

    void do1(String str);

    void do2(String str);

    String do3(Long id);

    String do4(String name , Integer age);

    void do5(Long id);

    String do6(Long id);
}

實現類

package com.hlm.annotation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

//@MyAop(value = "MyCacheImpl")
@MyAop(value = "FlowImpl")
public class Test implements TestInterface {

    Logger log = LoggerFactory.getLogger(Test.class);

    @Override
    public void do1(String str) {
        System.out.println("進入到do1 方法,入引數為:"+str);
    }

    @Override
    public void do2(String str) {
        System.out.println("進入到do2 方法,入引數為:"+str);
    }

    @MyCache(value="Test" , key="{#id}")
    @Override
    public String do3(Long id) {
        return "我是do3的結果";
    }

    @MyCache(value="Test" , key="{#name,#age}")
    @Override
    public String do4(String name, Integer age) {
        return "我是do4的結果";
    }

    @EviMyCache(value="Test" , key="{#id}")
    @Override
    public void do5(Long id){
        log.info("正在執行do5方法");
    }

    @MyCache(value="Test" , key="{#id,special}")
    @Override
    public String do6(Long id){
        return "我是do6的結果";
    }
}

6.main 方法

package com.hlm.annotation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class TestMain {
    Logger log = LoggerFactory.getLogger(TestMain.class);
    public  static void main(String[] args){

        //通過代理生成物件
        TestInterface test = MyDynamicProxy.getProxyInstants(Test.class);
        System.out.println(test.do3(1L));
        System.out.println(test.do4("櫻木花道",18));
        System.out.println(test.do6(1L));
        System.out.println("<------------我是可愛的分割線,下面測試快取------------>");
        System.out.println(test.do3(1L));
        System.out.println(test.do4("櫻木花道",18));
        System.out.println(test.do6(1L));
        System.out.println("<------------我是可愛的分割線,下面測試快取失效------------>");
        test.do5(1L);
        System.out.println(test.do3(1L));
    }
}

7.執行結果

23:27:21.774 [main] INFO com.hlm.annotation.MyCacheImpl - key is :Test1

23:27:21.777 [main] INFO com.hlm.annotation.MyCacheImpl - 快取未命中,將結果儲存到快取中

我是do3的結果

23:27:21.779 [main] INFO com.hlm.annotation.MyCacheImpl - key is :Test櫻木花道18

23:27:21.779 [main] INFO com.hlm.annotation.MyCacheImpl - 快取未命中,將結果儲存到快取中

我是do4的結果

23:27:21.782 [main] INFO com.hlm.annotation.MyCacheImpl - key is :Test1special

23:27:21.782 [main] INFO com.hlm.annotation.MyCacheImpl - 快取未命中,將結果儲存到快取中

我是do6的結果

<------------我是可愛的分割線,下面測試快取------------>

23:27:21.783 [main] INFO com.hlm.annotation.MyCacheImpl - key is :Test1

23:27:21.783 [main] INFO com.hlm.annotation.MyCacheImpl - 快取命中,返回從快取中取得的結果

我是do3的結果

23:27:21.784 [main] INFO com.hlm.annotation.MyCacheImpl - key is :Test櫻木花道18

23:27:21.784 [main] INFO com.hlm.annotation.MyCacheImpl - 快取命中,返回從快取中取得的結果

我是do4的結果

23:27:21.785 [main] INFO com.hlm.annotation.MyCacheImpl - key is :Test1special

23:27:21.785 [main] INFO com.hlm.annotation.MyCacheImpl - 快取命中,返回從快取中取得的結果

我是do6的結果

<------------我是可愛的分割線,下面測試快取失效------------>

23:27:21.792 [main] INFO com.hlm.annotation.MyCacheImpl - key is :Test1

23:27:21.792 [main] INFO com.hlm.annotation.MyCacheImpl - 快取失效

23:27:21.792 [main] INFO com.hlm.annotation.Test - 正在執行do5方法

23:27:21.845 [main] INFO com.hlm.annotation.MyCacheImpl - key is :Test1

23:27:21.845 [main] INFO com.hlm.annotation.MyCacheImpl - 快取未命中,將結果儲存到快取中

我是do3的結果

8.再來一個日誌的實現

package com.hlm.annotation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;

public class FlowImpl implements  FlowInterface {
    Logger log = LoggerFactory.getLogger(MyDynamicProxy.class);
    @Override
    public Object doPre(Object target, Method method, Object[] args) {
        log.info("呼叫方法"+method.getName()+"前");
        return null;
    }

    @Override
    public Object doafter(Object target, Method method, Object[] args) {
        log.info("呼叫方法"+method.getName()+"後");
        return null;
    }

    @Override
    public Object process(Object target, Method method, Object[] args)throws Exception {
        this.doPre(target,method ,args);
        Object obj = null ;
        try{
             obj = method.invoke(target ,args) ;
        }catch(Exception e){
            throw e ;
        }
        this.doafter(target,method ,args);
        return obj;
    }
}

執行結果

23:28:33.999 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do3前

23:28:34.023 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do3後

我是do3的結果

23:28:34.023 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do4前

23:28:34.023 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do4後

我是do4的結果

23:28:34.024 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do6前

23:28:34.024 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do6後

我是do6的結果

<------------我是可愛的分割線,下面測試快取------------>

23:28:34.024 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do3前

23:28:34.024 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do3後

我是do3的結果

23:28:34.024 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do4前

23:28:34.024 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do4後

我是do4的結果

23:28:34.024 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do6前

23:28:34.024 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do6後

我是do6的結果

<------------我是可愛的分割線,下面測試快取失效------------>

23:28:34.025 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do5前

23:28:34.025 [main] INFO com.hlm.annotation.Test - 正在執行do5方法

23:28:34.025 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do5後

23:28:34.025 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do3前

23:28:34.025 [main] INFO com.hlm.annotation.MyDynamicProxy - 呼叫方法do3後

我是do3的結果