『猪弟アーチJava』連載番外編:Javaエージェント(中)

47383 ワード

前回ではエージェントの概念を比較的詳細なケースで説明したが,この論文では主にJDK動的エージェントとcglibサブクラスエージェントについて述べた.
JDKダイナミックエージェント
まず簡単なケースでお話しします
ケース:
入力ログ印刷、異常印刷と処理、結果印刷の戻り、メソッド呼び出し終了印刷を1つのメール機能に提供する必要がある.メール機能のコードを見てみましょう.
まずはメールインタフェースISmsSupport
package proxy;

/**
 *       
 *
 * @author   
 * @since v1.0.0
 */
public interface ISmsSupport {

    boolean sendMsg(String content, String phoneNumber);
}


そしてメール機能の実装クラスです
package proxy;
/**
 *       
 *
 * @author   
 * @since v1.0.0
 */
public class SmsSupportImpl implements ISmsSupport {
    @Override
    public boolean sendMsg(String content, String phoneNumber) {
        //    
        int temp = 1 / 0;
        System.out.println(content + "," + phoneNumber);
        return true;
    }
}


実装クラスでは、ログや例外処理は行われていません.次に、動的エージェントで上記のニーズを実装します.なぜ動的エージェントで実装しますか.まず、エージェントクラスSmsProxyを構築します.
package proxy;

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

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

/**
 *        
 *
 * @author   
 * @since v1.0.0
 */
public class SmsProxy implements InvocationHandler {

    /**
     *     
     */
    private final static Logger LOGGER = LoggerFactory.getLogger(SmsProxy.class);
    
    /**
     *      
     */
    private Object realSubject;

    /**
     *    
     *
     * @param realSubject      
     */
    public SmsProxy(Object realSubject) {
        this.realSubject = realSubject;
    }

    /**
     *          
     *
     * @return     
     */
    public Object getProxy() {
        Class> subjectClass = realSubject.getClass();
        return Proxy.newProxyInstance(subjectClass.getClassLoader(), subjectClass.getInterfaces(), this);
    }

    /**
     * @param proxy      
     * @param method        
     * @param args       
     * @return Object          
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //        
        String methodName = method.getName();

        //    (    )
        LOGGER.info("{}      :{}", methodName, args);
        try {

            //  Reflect      
            Object result = method.invoke(realSubject, args);

            //      (     )
            LOGGER.info("{}        :{}", methodName,result);

            return result;
        } catch (Throwable e) {

            //      (    )
            LOGGER.error("          ,  :", e);
            return Boolean.FALSE;
        } finally {

            //       (    )
            LOGGER.info("      ");
        }
    }
}


このエージェントクラスを簡単に説明します
1まずjdkダイナミックエージェントを使用してjavaが提供するInvocationHandlerインタフェースを実装し、invokeメソッドを実装します.以下はインタフェースのソースコードです.
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

2エージェントクラスでObjectタイプのプロパティを定義して、エージェントされたオブジェクトを参照し、コンストラクタによって初期化します.
③エージェントオブジェクトを取得するための関数getProxyをクラスに定義し、ここではProxyクラスの静的メソッドnewInstanceを使用し、以下はメソッドのソースコードの内容である.
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        //     handler    
        Objects.requireNonNull(h);
        //              
        final Class>[] intfs = interfaces.clone();
        //       
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            //          
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         *               ,      
         * Look up or generate the designated proxy class.
         */
        Class> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         *      invocation handler            
         */
        try {
            if (sm != null) {
                //      
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //           
            final Constructor> cons = cl.getConstructor(constructorParams);
            //   invocation handler
            final InvocationHandler ih = h;
            //     public ,     
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //                 
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

④最後に私たちのinvoke方法を実現します.まず方法の構造と私たちの実現を見てみましょう.
    /**
     * @param proxy      
     * @param method        
     * @param args       
     * @return Object          
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //        
        String methodName = method.getName();
        //    (    )
        LOGGER.info("{}      :{}", methodName, args);
        try {
            //  Reflect      
            Object result = method.invoke(realSubject, args);
            //      (     )
            LOGGER.info("{}        :{}", methodName,result);
            return result;
        } catch (Throwable e) {
            //      (    )
            LOGGER.error("          ,  :", e);
            return Boolean.FALSE;
        } finally {
            //       (    )
            LOGGER.info("      ");
        }
    }

簡単ではないでしょうか.次は入り口プログラムです.
package proxy;

/**
 *     
 *
 * @author   
 * @since v1.0.0
 */
public class Bootstrap {

    public static void main(String[] args){
        demo1();
    }

    public static void demo1(){
        ISmsSupport smsSupport = new SmsSupportImpl();
        ISmsSupport proxy = (ISmsSupport) new SmsProxy(smsSupport).getProxy();
        proxy.sendMsg("hello world", "110");
    }

}


まず、運転結果を見てみましょう.まず、SmsSupportImplint i = 1/ 0;を正常な運転に注釈します.
17:12:26.976 [main] INFO proxy.SmsProxy - sendMsg      :[hello world, 110]
hello world,110
17:12:26.980 [main] INFO proxy.SmsProxy - sendMsg        :true
17:12:26.980 [main] INFO proxy.SmsProxy -       

コメントを取り消し、例外が発生しました.
17:13:31.169 [main] INFO proxy.SmsProxy - sendMsg      :[hello world, 110]
17:13:31.175 [main] ERROR proxy.SmsProxy -           ,  :
java.lang.reflect.InvocationTargetException: null
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at proxy.SmsProxy.invoke(SmsProxy.java:63)
	at com.sun.proxy.$Proxy0.sendMsg(Unknown Source)
	at proxy.Bootstrap.demo1(Bootstrap.java:19)
	at proxy.Bootstrap.main(Bootstrap.java:12)
Caused by: java.lang.ArithmeticException: / by zero
	at proxy.SmsSupportImpl.sendMsg(SmsSupportImpl.java:13)
	... 8 common frames omitted
17:13:31.175 [main] INFO proxy.SmsProxy -       

上記の実行結果から、インパラメータと戻り結果が印刷され、異常が正しくブロックされ、異常情報が印刷され、完了したログが呼び出される方法がわかります.よし!ここでダイナミックエージェントの実装は終わります.次に、ダイナミックエージェントを使用する理由を見てみましょう.
このケースでは動的エージェントの優位性が現れません.なぜでしょうか.ログを印刷するクラスが少なすぎて、ハードコーディングで実現することができます.では、1万のクラスがあります.各クラスには10の方法があります.各方法の前にログを打っていません.今、各方法のために入参と戻り結果の印刷を実現しなければなりません.ハードコーディングもできますか?このとき、ダイナミックエージェントが機能します.ログエージェントクラスを書くだけで、この印刷機能を完了するために使用されます.コードを入力します.
package proxy;

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

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

/**
 *        
 *
 * @author   
 * @since v1.0.0
 */
public class LogProxy<T> implements InvocationHandler {

    /**
     *     
     */
    private final static Logger LOGGER = LoggerFactory.getLogger(LogProxy.class);

    /**
     *      
     */
    private T realSubject;

    /**
     *    
     *
     * @param realSubject
     */
    public LogProxy(T realSubject) {
        this.realSubject = realSubject;
    }

    /**
     *       
     *
     * @return   
     */
    public T getProxy() {
        Class> subjectClass = realSubject.getClass();
        return (T) Proxy.newProxyInstance(subjectClass.getClassLoader(), subjectClass.getInterfaces(), this);
    }

    /**
     * @param proxy      
     * @param method        
     * @param args       
     * @return Object         
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //        
        String methodName = method.getName();
        //    (    )
        LOGGER.info("{}      :{}", methodName, args);
        try {
            //      
            Object result = method.invoke(realSubject, args);
            //      (     )
            LOGGER.info("{}        :{}", methodName,result);
            return result;
        } catch (Throwable e) {
            //      (    )
            LOGGER.error("          ,  :", e);
            return Boolean.FALSE;
        } finally {
            //       (    )
            LOGGER.info("      ");
        }
    }
}


実はこのクラスは前のクラスとあまり違いませんが、汎用型とgetProxyの戻り型を汎用型に変更しただけです.呼び出し方法は前と似ていますが、結果も同じです.エントリプログラム:
package proxy;

/**
 *     
 *
 * @author   
 * @since v1.0.0
 */
public class Bootstrap {
    public static void main(String[] args){
        demo2();
    }
    public static void demo2() {
        ISmsSupport smsSupport = new SmsSupportImpl();
        LogProxy proxy = new LogProxy<>(smsSupport);
        ISmsSupport smsProxy = proxy.getProxy();
        smsProxy.sendMsg("hello world", "110");
    }
}


実際には、書くのが便利になったが、呼び出し方法のコードを修正するのも大きな仕事であり、AOPで処理することができ、AOPでは呼び出し方法のコードを変更することなくエージェントを使用してログを印刷することができる.AOPについては次の記事に残しておきます.
はい、生成されたエージェントクラスの長さを見てみましょう.どう思いますか.もちろん、メモリ内のオブジェクトをハードディスクに保存します.Proxy.newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)で次のコードが呼び出されたことを覚えています.
    /*
     * Look up or generate the designated proxy class.
     */
    Class> cl = getProxyClass0(loader, intfs);

この方法のソースコードを見てみましょう.
    /**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class> getProxyClass0(ClassLoader loader,
                                           Class>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }
    /**
     * a cache of proxy classes
     */
    private static final WeakCache[], Class>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

Proxyクラスには、WeakCacheタイプの静的変数である属性proxyClassCacheがあります.クラス・ローダとエージェント・クラス間のマッピングを示します.したがってproxyClassCacheのgetメソッドは、クラスローダとインタフェース配列に基づいてProxyクラスを取得するために使用され、すでに存在する場合はcacheから直接戻り、存在しない場合はマッピングを作成してcacheテーブルを更新します.
エージェントクラスの作成プロセスについて説明します.Factoryクラスのgetメソッドを呼び出し、ProxyClassFactoryクラスのapplyメソッドを呼び出し、最終的に次のコードを見つけます.
    public static byte[] generateProxyClass(String var0, Class>[] var1) {
        return generateProxyClass(var0, var1, 49);
    }

次のコードを使用して、プロキシクラスのclassファイルをディスクに生成できます.
public static void main(String[] args) throws IOException {
    /**
     * @param s        
     * @param classes        
     * @return byte[]          
     */
    byte[] classFile = ProxyGenerator.generateProxyClass("SmsSupportProxy",
            SmsSupportImpl.class.getInterfaces());
    File file = new File("D:/SmsSupportProxy.class");
    FileOutputStream fos = new FileOutputStream(file);
    fos.write(classFile);
    fos.flush();
    fos.close();
}

SmsSupportProxy.classファイルの生成を実行し、IDEAで逆コンパイルされたコンテンツを開きます.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.ISmsSupport;

public final class SmsSupportProxy extends Proxy implements ISmsSupport {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public SmsSupportProxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean sendMsg(String var1, String var2) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m3, new Object[]{var1, var2})).booleanValue();
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("proxy.ISmsSupport").getMethod("sendMsg", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

このように,上記のhandlerと組み合わせると,エージェントクラスがターゲットメソッドを呼び出すときにinvokeを実行する理由がよくわかるだろう.
cglibサブクラスエージェント
前回の記事の末尾では、3つのエージェントの使用条件と使用制限について紹介しました.
1静的エージェントは、同じインタフェースを実装する必要があるクラスオブジェクトをエージェントすることができるので、その使用条件は、被エージェントクラスがインタフェースを実装することである.上記の各シーンメールはISmsServiceインタフェースを実現しているので,エージェントクラスはすべてのシーンメール実装クラスをエージェントし,真のメール送信方法を呼び出して正しいシーンメールを送信することができる.
①動的エージェントの使用条件も被エージェントクラスがインタフェースを実現しなければならないが、動的エージェントはインタフェースを実現したすべてのクラスをエージェントすることができ、強大であることは必然的に欠点もある:動的エージェントはJava反射メカニズムに依存し、反射は性能に影響を与える機能であるため、動的代理理性は静的エージェントよりも低い.
③cglibサブクラスエージェントは、まずサードパーティライブラリに依存し、次にバイトコードに基づいてサブクラスエージェントを生成する必要があり、特定の使用条件がないため、インタフェースを実現する必要もなく、すべてのクラスをエージェントすることができるので、性能は静的エージェントに及ばない.
上記から分かるように、前の2つのエージェントはインタフェースを実装する必要がありますが、CGLIBは使用しません.
面接で発生した質問をここに残します.
jdkエージェントではなぜインタフェースを実現しなければならないのか,抽象クラスではいけないのか.心を込めて考えて、私と同じように頭のいいあなたはきっと答えを得ることができると信じています.
はい、次の記事に質問を残して答えます.
OK、次に私达はやはり同じ需要で、CGLIBエージェントの方式を使って実现して、もしあなたが上のダイナミックエージェントの内容を理解したら、CGLIBの内容は简単に理解することができて、ここで私达はただ使い方を学んで、原理について彼はバイトコードによって動的に生成した子类で、豚の弟のレベルはまだ达しないで具体的に解釈しません.後日、私の豚のアーチ神功が完成した時、原理を話しに来ました.
まずメール送信クラスを見てみましょう.インタフェースはありません.
package xin.sun.proxy.cglib;

/**
 *       
 *
 * @author   
 * @since v1.0.0
 */
public class SmsSupport {

    public boolean sendMsg(String content, String phoneNumber) {
        //    
        //int temp = 1 / 0;
        System.out.println(content + "," + phoneNumber);
        return true;
    }
}

代理工場類をもう一度見てみましょう.
springのmaven依存性が導入されました:
<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-coreartifactId>
    <version>4.3.10.RELEASEversion>
dependency>
package xin.sun.proxy.cglib;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * @author   
 */
public class ProxyFactory implements MethodInterceptor {
    /**
     *      
     */
    private Object target;
    /**
     *    
     *
     * @param target
     */
    public ProxyFactory(Object target) {
        this.target = target;
    }
    /**
     *         
     *
     * @return Object       
     */
    public Object getProxyInstance() {
        /**
         *    
         */
        Enhancer enhancer = new Enhancer();
        /**
         *     
         */
        enhancer.setSuperclass(target.getClass());
        /**
         *       
         */
        enhancer.setCallback(this);
        /**
         *         ,   
         */
        return enhancer.create();
    }

    /**
     *       
     *
     * @param o               
     * @param method           
     * @param objects         
     * @param methodProxy      MethodProxy  
     * @return Object         
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //        
        String methodName = method.getName();
        //    (    )
        LOGGER.info("{}      :{}", methodName, objects);
        try {
            //      
            Object result = method.invoke(realSubject, objects);
            //      (     )
            LOGGER.info("{}        :{}", methodName, result);
            return result;
        } catch (Throwable e) {
            //      (    )
            LOGGER.error("          ,  :", e);
            return Boolean.FALSE;
        } finally {
            //       (    )
            LOGGER.info("      ");
        }
    }
}

このエージェントクラスを簡単に説明します
1まず、cglibエージェントを使用して、フレームワークが提供するMethodInterceptorインタフェースを実装し、interceptメソッドを実装する.
2エージェントクラスでObjectタイプのプロパティを定義して、エージェントされたオブジェクトを参照し、コンストラクタによって初期化します.
③クラスに定義された関数getProxyInstanceは、エージェントオブジェクトを取得するために使用されます.ここではjdk動的エージェントとは少し異なります.ここではEnhancerというクラスを使用しています.方法の実装を見てみましょう.
    public Object getProxyInstance() {
        /**
         * Enhancer   
         */
        Enhancer enhancer = new Enhancer();
        /**
         *     
         */
        enhancer.setSuperclass(realSubject.getClass());
        /**
         *       ,                  ,           
         */
        enhancer.setCallback(this);
        /**
         *         ,   
         */
        return enhancer.create();
    }

④最後に私たちのintercept方法を実現します.まず方法の構造と私たちの実現を見てみましょう.
    /**
     *       
     *
     * @param o               
     * @param method           
     * @param objects         
     * @param methodProxy      MethodProxy  
     * @return Object         
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //        
        String methodName = method.getName();
        //    (    )
        LOGGER.info("{}      :{}", methodName, objects);
        try {
            //      
            Object result = method.invoke(realSubject, objects);
            //      (     )
            LOGGER.info("{}        :{}", methodName, result);
            return result;
        } catch (Throwable e) {
            //      (    )
            LOGGER.error("          ,  :", e);
            return Boolean.FALSE;
        } finally {
            //       (    )
            LOGGER.info("      ");
        }
    }

同じように、エントリプログラムを書いて検出します.
package xin.sun.proxy.cglib;

/**
 * @author   
 */
public class Bootstrap {

    public static void main(String[] args) {
        demo();
    }

    public static void demo(){
        ProxyFactory proxyFactory = new ProxyFactory(new SmsSupport());
        SmsSupport proxyInstance = (SmsSupport) proxyFactory.getProxyInstance();
        proxyInstance.sendMsg("CGLIB   ","4008008820");
    }
}


正常な動作の結果を確認します.
22:06:26.439 [main] INFO xin.sun.proxy.cglib.ProxyFactory - sendMsg      :[CGLIB   , 4008008820]
CGLIB   ,4008008820
22:06:26.445 [main] INFO xin.sun.proxy.cglib.ProxyFactory - sendMsg        :true
22:06:26.445 [main] INFO xin.sun.proxy.cglib.ProxyFactory -       

同様に、異常が捕獲されるかどうかを確認するために、異常int temp = 1 / 0;を人為的に製造しました.
22:10:36.454 [main] INFO xin.sun.proxy.cglib.ProxyFactory - sendMsg      :[CGLIB   , 4008008820]
22:10:36.460 [main] ERROR xin.sun.proxy.cglib.ProxyFactory -           ,  :
java.lang.reflect.InvocationTargetException: null
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at xin.sun.proxy.cglib.ProxyFactory.intercept(ProxyFactory.java:80)
	at xin.sun.proxy.cglib.SmsSupport$$EnhancerByCGLIB$$8593c815.sendMsg()
	at xin.sun.proxy.cglib.Bootstrap.demo(Bootstrap.java:15)
	at xin.sun.proxy.cglib.Bootstrap.main(Bootstrap.java:9)
Caused by: java.lang.ArithmeticException: / by zero
	at xin.sun.proxy.cglib.SmsSupport.sendMsg(SmsSupport.java:13)
	... 8 common frames omitted
22:10:36.461 [main] INFO xin.sun.proxy.cglib.ProxyFactory -       

結果から、完璧に異常を捕獲したことがわかります
cglibでのメール送信クラスにはインタフェースが実装されておらず、同様にエージェントの機能も実装されているため、cglibこそ本当にすべてのクラスをエージェントすることができ、これもjdk動的エージェントに対する補完であるだろうが、SpringのAOPではデフォルトでjdk動的エージェントが使用され、被エージェントクラスにインタフェースが実装されていない場合、Springは自動的にcglibサブクラスエージェントを切り替え、Springは人間的だと思っているのではないでしょうか.実はSpring AOPは私たちにもっと便利な方法を提供してくれました.次の文章ではSpringのAOPプログラミングについて話します.
Spring AOPはエージェントを一度パッケージ化して使いやすくなったのはもちろんですが、java大神になるには、やはりその実質を追及しなければなりません.これも私がこの2つの文章を書く理由で、すべてのフレームワークはJava基礎+プログラミング思想に基づいて実現されています.実はフレームワークの設計が分かり、私たち自身もSpring IOC DI AOPを実現することができます.機会があればみんなのためにSpringの枠組みを手に入れることができて、もちろんSpringは高度な抽象で、だからソースコードは読むのがそんなに簡単ではありませんて、しかしその思想はやはり分かりやすくて、思想をマスターするのは本当にくわえて、結局思想の言語は関係ありません.
磊おじさんはSpringの専门家で、兴味のあるのは多く磊おじさんのブログを见ます!!! , ...