Java面接問題設計モード編エージェントモード


エージェントとは
プロキシオブジェクトは、通常、既存のオブジェクトに機能を追加または変更するために使用されます.ソースオブジェクトのコピーです.一般的にはソースオブジェクトのすべてのメソッドがあり、javaでは元のオブジェクトを継承するのが一般的です.元の対象を修正しない前提で、私たちが期待している行為を実現することができます.
実際の応用では、単一の職責原則に基づいて、エージェントオブジェクトは一般的にエージェントを担当するだけで、他の処理ロジックを担当しないで、処理ロジックは一般的にhandlerが完成します
プロキシの作成方法
  • 静的エージェント:組合せ関係を用いて静的エージェントを実現し、組合せ関係はソースオブジェクトに注入され、その後、外層オブジェクトに他の論理的注入を行うことができる.静的エージェントの問題は,異なる論理注入が異なるエージェントオブジェクトを必要とし,エージェントオブジェクトを増加させ続けるのは明らかに現実的ではないことである.
  • 動的エージェント:javaではjavaを直接利用することができる.lang.refect.Proxyクラスは直接プロキシクラスまたはプロキシオブジェクトを作成します.

  • スタティツクエージェント
    静的エージェントは主に2点を把握します.
  • エージェントクラスとソースオブジェクトは、同じインタフェース
  • を実装する必要がある.
  • ソースオブジェクトメソッドを組み合わせて呼び出し、カスタムロジックの注入
  • を行う.
    例を次に示します.
    ソースオブジェクト:
    public class UserManagerImpl implements UserManager {
     
    	@Override
    	public void addUser(String userId, String userName) {
    		System.out.println("UserManagerImpl.addUser");
    	}
     
    	@Override
    	public void delUser(String userId) {
    		System.out.println("UserManagerImpl.delUser");
    	}
     
    	@Override
    	public String findUser(String userId) {
    		System.out.println("UserManagerImpl.findUser");
    		return "  ";
    	}
     
    	@Override
    	public void modifyUser(String userId, String userName) {
    		System.out.println("UserManagerImpl.modifyUser");
     
    	}
    }
    

    エージェントクラス(同じインタフェースを実装し、リレーションシップリファレンスを組み合わせる):
    public class UserManagerImplProxy implements UserManager {
     
    	//     
    	private UserManager userManager;
    	//             
    	public UserManagerImplProxy(UserManager userManager){
    		this.userManager=userManager;
    	}
    	@Override
    	public void addUser(String userId, String userName) {
    		try{
    				//         
    				//      
    				System.out.println("start-->addUser()");
    				userManager.addUser(userId, userName);
    				//      
    				System.out.println("success-->addUser()");
    			}catch(Exception e){
    				//      
    				System.out.println("error-->addUser()");
    			}
    	}
     
    	@Override
    	public void delUser(String userId) {
    		userManager.delUser(userId);
    	}
     
    	@Override
    	public String findUser(String userId) {
    		userManager.findUser(userId);
    		return "  ";
    	}
     
    	@Override
    	public void modifyUser(String userId, String userName) {
    		userManager.modifyUser(userId,userName);
    	}
     
    }
    

    クライアントコール:
    public class Client {
     
    	public static void main(String[] args){
    		//UserManager userManager=new UserManagerImpl();
    		UserManager userManager=new UserManagerImplProxy(new UserManagerImpl());
    		userManager.addUser("1111", "  ");
    	}
    }
    

    静的エージェントモードは簡単で使いやすいですが、問題があります.エージェントクラスはソースオブジェクトのインタフェースを実現する必要があります.複数のインタフェースをエージェントするには、多くのエージェントオブジェクトが必要です.クラスの数が多すぎて、最適化する方法はありませんか.動的エージェントを参照
    ダイナミックエージェント
    JDKダイナミックエージェント
    Jdkダイナミックエージェントjdkが所有するProxyクラスを使用して、次のようなエージェントオブジェクトを作成します.
    package com.puhui.goosecard.bank.utils;
    
    import com.puhui.goosecard.bank.service.AccountService;
    import com.puhui.goosecard.bank.service.impl.AccountServiceImpl;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 2 * @Author: kerry
     * 3 * @Date: 2018/8/24 19:48
     * 4
     */
    public class Test {
    
    
        private static AccountService accountService = new AccountServiceImpl();
    
        public static void main(String[] args) {
    
            AccountService accountServiceProxy =
                    (AccountService) Proxy.newProxyInstance(AccountService.class.getClassLoader(), new Class[]{AccountService.class}, new AccountHandler());
    
            accountServiceProxy.openAccount(null);
    
        }
    
        static class AccountHandler implements InvocationHandler {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("before");
                method.invoke(accountService, args);
                System.out.println("after");
                return null;
            }
        }
    }
    

    上記のコードは以下の点に注意してください.
  • ProxyクラスのnewProxyInstanceメソッドを使用してエージェントオブジェクトを作成します.最初のパラメータはclassloaderで、2番目はインタフェースクラスの配列(jdkエージェントが複数のインタフェースをサポートする鍵)で、3番目はinvocationhandler
  • です.
  • InvocationHander実装クラスでは、invokeメソッドのパラメータが、1つ目は生成されたエージェントオブジェクトであり、2つ目はメソッドオブジェクトであり、3つ目はパラメータであるため、hadlerにはソースオブジェクトを注入する必要があり、handlerはアクティブオブジェクトの参照がない.
  • jdkダイナミックエージェントは、反射により被エージェントオブジェクトを呼び出す方法であり、cglibはfastclassのインデックス方式
  • である.
    インタフェースクラス配列が複数のインタフェースに転送された場合?これはjdkダイナミックエージェントが解決する問題です.コードを見てみましょう.
    package com.puhui.goosecard.bank.utils;
    
    import com.puhui.goosecard.bank.service.AccountService;
    import com.puhui.goosecard.bank.service.TradeService;
    import com.puhui.goosecard.bank.service.impl.AccountServiceImpl;
    import com.puhui.goosecard.bank.service.impl.TradeServiceImpl;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 2 * @Author: kerry
     * 3 * @Date: 2018/8/24 19:48
     * 4
     */
    public class Test {
    
    
        private static AccountService accountService = new AccountServiceImpl();
    
    
        private static TradeService tradeService = new TradeServiceImpl();
    
        public static void main(String[] args) {
    
            Object proxyInstance =
                    Proxy.newProxyInstance(AccountService.class.getClassLoader(), new Class[]{AccountService.class, TradeService.class}, new AccountHandler());
    //        if (proxyInstance instanceof AccountService) {
    //            AccountService accountService = (AccountService) proxyInstance;
    //            accountService.openAccount(null);
    //        }
            if (proxyInstance instanceof TradeService) {
                TradeService tradeService = (TradeService) proxyInstance;
                tradeService.processRefund(null, null);
            }
    
    
        }
    
        static class AccountHandler implements InvocationHandler {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (proxy instanceof AccountService) {
                    System.out.println("before");
                    method.invoke(accountService, args);
                    System.out.println("after");
                }
                if (proxy instanceof TradeService) {
                    System.out.println("before");
                    method.invoke(tradeService, args);
                    System.out.println("after");
                }
                return null;
            }
        }
    }
    

    上のコードは以下の点に注意してください.
  • インタフェース配列はaccouontサービスとtradeサービスの2つのインタフェースに渡され、返されるエージェントオブジェクトはobjectのみです.処理時にinstanceOf判定
  • を行う
  • handlerの最初のパラメータproxyは、1つのインタフェースが使用されていないが、複数のインタフェースクラスの場合、proxyは、現在どのインタフェースであるかを判断することができる
  • .
  • の第1および第2のifは、生成されたエージェントオブジェクトがインタフェース配列内のすべてのインタフェースを実現するためにアクセスすることができる.

  • まとめ:
    jdkダイナミックエージェントは静的エージェントの各インタフェースのエージェントクラスの問題を解決することを発見したが,肝心な点はjdkエージェントがインタフェース向けであり,エージェントオブジェクトの作成がインタフェース配列に伝達され,インタフェースがなければどのようにエージェントクラスを作成するか,引き続き下を見ることである.
     
    CGLibダイナミックエージェント
    インタフェースがない場合、エージェントオブジェクトをどのように生成するかがCGlibの問題です.CGLibはASMバイトコード操作フレームワークをカプセル化し,実行時にエージェントオブジェクトを生成する.多くのオープンソースフレームワークは、spring AOP、hibernateなどのcglibを使用してエージェントオブジェクトを生成します.
    まず例を見てみましょう.
    サービスを定義し、インタフェースがないことに注意します.これもcglibを利用する理由です.
    package com.puhui.goosecard.web.cglib;
    
    public class UserServiceImpl {
    
    
        public void add() {
            System.out.println("This is add service");
        }
    
        public void delete(int id) {
            System.out.println("This is delete service:delete " + id);
        }
    }
    

    コールバックメソッド、すなわちmethodInterceptorを定義し、カスタムロジックを注入します.つまり、エージェントを使用する理由です.例えば、権限チェックなどです.
    package com.puhui.goosecard.web.cglib;
    
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    public class MyMethodInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {
            System.out.println("Before:" + method);
            Object object = proxy.invokeSuper(obj, arg);
            System.out.println("After:" + method);
            return object;
        }
    }
    

    プロキシオブジェクトを生成し、テストを呼び出す
    package com.puhui.goosecard.web.cglib;
    
    import org.springframework.cglib.proxy.Enhancer;
    
    /**
     * 2 * @Author: kerry
     * 3 * @Date: 2018/8/29 19:41
     * 4
     */
    public class Test {
    
    
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(UserServiceImpl.class);
            enhancer.setCallback(new MyMethodInterceptor());
            UserServiceImpl userService = (UserServiceImpl) enhancer.create();
            userService.add();
    
        }
    }
    

    以上のコードにより、次のことがわかります.
  • Enhancerクラスによりプロキシオブジェクトが生成され、プロキシクラスの設定(setSuperClass)、コールバックメソッドの設定(setCallBack)
  • が含まれる.
  • setSuperClassにより、プロキシクラスが被プロキシクラス、すなわちUserServiceImplを継承しており、finalキーワードによって修飾されたメソッドを処理できないことが分かる
  • .
  • jdkは反射メカニズムを用いて依頼クラスを呼び出す方法を採用し、cglibは類似インデックスの方法で直接依頼クラスを呼び出す方法を採用する.