春のソースコード解析6:春AOPの概要

ORIGINAL:月Changjei

なぜ我々はAOPを使用したくありません

はじめに
一年半前の記事Spring3を書いた:AOPは、比較の基準を記述するために春AOPの時間の使い方を学んでいました。私は誰もが多くのコメントを持っていますが、今、ビューの私の個人的な観点から支援するために書いたものを信じて、この記事及び返信の最終勧告は、この記事ではよく書かれていない、とさえあまりないが、持っていると言うことができますコンテンツなので、これらの勧告やコメントは私が値すると感じました。

これらの理由から、最も基本的なソースコードから、記事を更新 - >デザインパターン(デコレータとプロキシ)の使用 - 私たちはAOPを利用したい理由を説明するために3つのレベルを使用して> AOP、友達にこの記事を願っています友人役立ちます。

元のコードの文言は、
それがコードを証明したい場合、それは一例である必要があり、ここに私の例です。

1は
印刷を呼び出すために呼び出す前にインターフェイスダオインサートは3つのメソッドを有している、削除、更新、更新が呼び出される前及び後に挿入している、ミリ秒と数ミリ秒の数は、
最初のダオ・インタフェースを定義します。

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public interface Dao {
 
    public void insert();
     
    public void delete();
     
    public void update();
     
}

そして、実装クラスDaoImplを定義します。

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class DaoImpl implements Dao {
 
    @Override
    public void insert() {
        System.out.println("DaoImpl.insert()");
    }
 
    @Override
    public void delete() {
        System.out.println("DaoImpl.delete()");
    }
 
    @Override
    public void update() {
        System.out.println("DaoImpl.update()");
    }
     
}

、最も独創的文言は、私が(前と後のインサートを呼びたい)とupdate()メソッドは、毎回印刷し、あなただけの更新前と後の()メソッドを挿入するために、コールを新しいクラスのパケット層を定義することができます(別途、それを処理するための)方法新しいクラス私はServiceImplと名付け、その実装は次のようになります。

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class ServiceImpl {
 
    private Dao dao = new DaoImpl();
     
    public void insert() {
        System.out.println("insert()方法开始时间:" + System.currentTimeMillis());
        dao.insert();
        System.out.println("insert()方法结束时间:" + System.currentTimeMillis());
    }
     
    public void delete() {
        dao.delete();
    }
     
    public void update() {
        System.out.println("update()方法开始时间:" + System.currentTimeMillis());
        dao.update();
        System.out.println("update()方法结束时间:" + System.currentTimeMillis());
    }
     
}

これは、ほとんどの元言葉遣い、このようなアプローチの欠点も明らかです。

このロジックは再び記述する必要があります高めるために他の場所がある場合、メソッド呼び出し時間前後のロジック出力は、再利用することができない
、他のDAO実装クラスが存在する場合、あなたが実装クラスをラップするクラスを追加する必要がありますが、クラスの数になりますこれは、拡大
Decoratorパターンを
し、我々は解決することができますどのように多くの問題を確認するために最初のDecoratorパターンで、デザインパターンを使用しています。コアDecoratorパターンは、インターフェイスを実装することで、ダオダオ・インターフェースへの参照を保持している、私はLogDaoという名前のクラスを追加しますが、その実装は次のようになります。

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class LogDao implements Dao {
 
    private Dao dao;
     
    public LogDao(Dao dao) {
        this.dao = dao;
    }
 
    @Override
    public void insert() {
        System.out.println("insert()方法开始时间:" + System.currentTimeMillis());
        dao.insert();
        System.out.println("insert()方法结束时间:" + System.currentTimeMillis());
    }
 
    @Override
    public void delete() {
        dao.delete();
    }
 
    @Override
    public void update() {
        System.out.println("update()方法开始时间:" + System.currentTimeMillis());
        dao.update();
        System.out.println("update()方法结束时间:" + System.currentTimeMillis());
    }
 
}

使用時に、「ダオDAO =新しいLogDao(新しいDaoImpl())」方法を使用することができる場合、このアプローチの利点は以下のとおりです。

呼び出し側だけダオを知るための透明、知らないプラスのログ
だけ異なるダオでコンストラクタLogDaoを渡したい、他のクラスのダオは、出力ログを実装する必要がある場合のクラスは、無限に膨張しません実装クラスは可能
しかし、このアプローチはまた、重大な欠点と短所がありました。

ロジック出力ログがまだ再利用することはできません
私は前と後の()メソッドを削除したい場合は、ロジックとコードの出力ログが同じ出力時に結合されている、あなたはLogDaoを変更する必要がある
しかし、このアプローチは、コードの文言に比べて最も原始的で、すでに非常にを持っています大きな改善。

プロキシモードを使用
して、我々は、プロキシモードを使用し、最も原始的な機能を実現しようとするプロキシモードを使用し、その後、私たちは、私はそれをLogInvocationHandlerと名付け、のInvocationHandlerを定義しますが、その実装は次のようになります。

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class LogInvocationHandler implements InvocationHandler {
 
    private Object obj;
     
    public LogInvocationHandler(Object obj) {
        this.obj = obj;
    }
     
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if ("insert".equals(methodName) || "update".equals(methodName)) {
            System.out.println(methodName + "()方法开始时间:" + System.currentTimeMillis());
            Object result = method.invoke(obj, args);
            System.out.println(methodName + "()方法结束时间:" + System.currentTimeMillis());
             
            return result;
        }
         
        return method.invoke(obj, args);
    }
     
}

そのコールは、私は、main関数を記述し、非常に簡単です:

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public static void main(String[] args) {
    Dao dao = new DaoImpl();
         
    Dao proxyDao = (Dao)Proxy.newProxyInstance(LogInvocationHandler.class.getClassLoader(), new Class<?>[]{Dao.class}, new LogInvocationHandler(dao));
         
    proxyDao.insert();
    System.out.println("----------分割线----------");
    proxyDao.delete();
    System.out.println("----------分割线----------");
    proxyDao.update();
}

このアプローチの利点を実証していない結果は以下のとおりです。

<?>あなたは配列の内容をすることができたときに限り、クラスnewProxyInstanceの増加として出力ログ用の別のインターフェース・ロジック、2番目のパラメータを過ごしたい場合は、ロジック出力ログが一緒に多重化され、
この方法の欠点は、以下のとおりです。

JDKの動的プロキシは、クラスのために行うことができないエージェントエージェントのためのインタフェースを提供することができます
にLogInvocationHandlerで判断の削除方法増加する必要があり、あなたが前とdeleteメソッドを呼び出した後の時間を印刷する場合のコードは、まだカップルで
利用CGLIBを
、その後CGLIBを使用する方法を見てMethodInterceptorのCGLIBを使用するだけにインタフェースを実装する必要があります。

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class DaoProxy implements MethodInterceptor {
 
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
        String methodName = method.getName();
         
        if ("insert".equals(methodName) || "update".equals(methodName)) {
            System.out.println(methodName + "()方法开始时间:" + System.currentTimeMillis());
            proxy.invokeSuper(object, objects);
            System.out.println(methodName + "()方法结束时间:" + System.currentTimeMillis());
             
            return object;
        }
         
        proxy.invokeSuper(object, objects);
        return object;
    }
 
}

コードの呼び出しのために:

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public static void main(String[] args) {
    DaoProxy daoProxy = new DaoProxy();
     
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(DaoImpl.class);
    enhancer.setCallback(daoProxy);
         
    Dao dao = (DaoImpl)enhancer.create();
    dao.insert();
    System.out.println("----------分割线----------");
    dao.delete();
    System.out.println("----------分割线----------");
    dao.update();
}

DecoratorパターンはDecoratorパターンのために言うことができるJavaプロキシを使用してネイティブコードの使用を超える改善であると言うことができます:クラスのプロキシを行うことができないJDKプロキシの問題を解決するが、ここでは、具体的な問題を説明するためにCGLIBを使用しますCGLIBは、Javaプロキシの使用を使用するための改善された改善が、ありません。

これは、改善の前に言うことができる起因DecoratorパターンにJavaプロキシモードを使用してネイティブコードを使用すると、デコレータよりオフ優れているが、Java剤およびコントラストCGLIB及び使用は必ずしもCGLIB以下であるため、改善すると言うことができないよりも優れていますJavaエージェント良く、両方が両方のJavaプロキシとCGLIB二つの方法をサポートするために、Springフレームワークのように、長所と短所を持っています。

現時点では、コードには、いくつかのより良いを持っているが、私は2つの欠点があると思います。

Javaプロキシを使用するかCGLIBを使用するかどうか、コードのこの部分が多少面倒である書き込み
)(ロジックコードのこの部分を追加する方法を削除するに対するように変更する必要があり、コードの間の結合が解消されていません

AOPを使用してください

最後に、途中でAOP、クラスの定義に対処するための最初の時間の使用を見て、私はそれTimeHandlerを呼び出します。

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class TimeHandler {
     
    public void printTime(ProceedingJoinPoint pjp) {
        Signature signature = pjp.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature)signature;
            Method method = methodSignature.getMethod();
            System.out.println(method.getName() + "()方法开始时间:" + System.currentTimeMillis());
             
            try {
                pjp.proceed();
                System.out.println(method.getName() + "()方法结束时间:" + System.currentTimeMillis());
            } catch (Throwable e) {
                 
            }
        }
    }
     
}

ライン12ライン8上のコードでコードにそれぞれ印刷方法を開始し、この方法は、実行時間の実行時間を終了します。私が使用して、もう少し複雑な書き込みをするためにここにいます 文言は、実際には、に分割することができます この二つのタイプは、個人の好みに依存します。

ここでは、パラメータを定義することはできませんprintTime方法自体をカットし、もう一つのことを言うが、いくつかのシーンは、クラス、メソッド、メソッドシグネチャやその他の情報を呼び出す取得する必要がありますし、その後、あなたはJointPointは、春が自動的printTimeのアプローチでパラメータを注入します定義することができ、ジョインポイントのメソッドは、クラス、メソッド、署名および他の情報によって呼び出されます。ここで私が使用しているため、 ジョインポイントは、メソッド呼び出しを保証されませんので、したがって、そのメソッドを確保するために呼び出す、メソッド呼び出しの時間の前後を出力するように、とは、ジョインポイントを直接使用することはできません。この時点で、続行()メソッドは、メソッド呼び出しを保証する、しかし、そのProceedingJoinPointしか認識することができProceedingJoinPoint、ProceedingPointPointを使用することができますし、 言い換えると、構成がaop.xmlであれば そして、printTimeメソッドのパラメータは、ProceedingJoinPoint、Springコンテナの起動エラーです。

そして、aop.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:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 
 
http://www.springframework.org/schema/aop
 
 
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
 
    <bean id="daoImpl" class="org.xrq.spring.action.aop.DaoImpl" />
    <bean id="timeHandler" class="org.xrq.spring.action.aop.TimeHandler" />
 
    <aop:config>
        <aop:pointcut id="addAllMethod" expression="execution(* org.xrq.spring.action.aop.Dao.*(..))" />
        <aop:aspect id="time" ref="timeHandler">
            <aop:before method="printTime" pointcut-ref="addAllMethod" />
            <aop:after method="printTime" pointcut-ref="addAllMethod" />
        </aop:aspect>
    </aop:config>
     
</beans>

...私は、ダオのすべての方法を横取りので、ここでは、百度に気にしない、式を記述しないでください。テストコードは非常に簡単です:
=
/ **
* @author月Changjei http://www.cnblogs.com/xrq730/p/7003082.html
* /
publicクラスAopTest {

    @Test
    @SuppressWarnings("resource")
    public void testAop() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring/aop.xml");
         
        Dao dao = (Dao)ac.getBean("daoImpl");
        dao.insert();
        System.out.println("----------分割线----------");
        dao.delete();
        System.out.println("----------分割线----------");
        dao.update();
    }
     
}

AOPの概要

結果は示しません。私は、AOPのこの使用にはいくつかの利点を要約したものです。

セクションでは、printTime方法のTimeHandlerを使用することができ、あなたがメソッドの実行前に、時間や方法後の時間を印刷する必要がどこにも、このようprintTime方法のTimeHandlerとして、再利用できるコンテンツ
、プロキシの使用を回避CGLIBプロキシを生成、このエリア内のすべてのフレームワーク達成するために、開発者は、コンテンツ自体に集中することができない切断
設定ファイルを修正インターセプトする方法の変更がある場合、コードとコードの間に結合を
AOPのどのような役割を表現するために図を用いて以下の:

プログラミングモードの伝統的なプログラミングがそう完全ロジック後、上に行くA-追加の期間> B-> C-> Dを実行するために、すなわち論理垂直です。行動のビジネスコードのが、AOPの下で機能を強化するには、別のアイデアを提供しています、その役割は知識がなくても、ビジネスロジックである(つまり、ビジネスロジックは変更する必要はありません)場合、この利用シナリオ、多くのプログラミングのアイデアがあり、トランザクションがコミットなど、権限の検出方法を実行する前に、その上の印刷メソッド呼び出しのイベントをログに記録して。

AOP使用シナリオは、例えば
順に純粋にデモ用の上記の例では、あなたのAOP、一例として、実際のシーンの役割の理解を得ました。

最初の例では、我々はデフォルトMyBatisのトランザクションが自動的に提出されていないことを知っているので、我々は非常に面倒であるトランザクションを、提出する方法)(コミットSQLSESSIONへの追加や削除が完了した後にプログラムされた時間を呼び出す必要があり、単純に以下の使用AOPを書きます自動的にトランザクションをコミットするために私たちを助けるためにコードの一部(私は個人的に利用できるこのコードをテストしました):

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class TransactionHandler {
 
    public void commit(JoinPoint jp) {
        Object obj = jp.getTarget();
        if (obj instanceof MailDao) {
            Signature signature = jp.getSignature();
            if (signature instanceof MethodSignature) {
                SqlSession sqlSession = SqlSessionThrealLocalUtil.getSqlSession();               
                 
                MethodSignature methodSignature = (MethodSignature)signature;
                Method method = methodSignature.getMethod();
                  
                String methodName = method.getName();
                if (methodName.startsWith("insert") || methodName.startsWith("update") || methodName.startsWith("delete")) {
                    sqlSession.commit();
                }
                 
                sqlSession.close();
            }
        }
    }
     
}

このシナリオでは、我々はAOPラベルを使用したいです メソッド呼び出しの後にカットという。

ここで私はSqlSessionThreadLocalUtilを作った、あなたがセッションを開くたびに、ThreadLocalのにSqlSessionThreadLocalUtil SQLSESSION通じ、現在のセッションでは、我々はTransactionHandlerを通して見る、二つの機能を実現することができます。

挿入、更新、自動的に提出された操作トランザクション削除
SQLSESSIONには近いもの()ので、私たちは、ビジネスコードは時々 SQLSESSIONをオフにすることを忘れます書くとき、これは、拡張メモリハンドルにつながる可能性があるため、ビジネスコードするセッションをクローズする必要がないので、したがって、セクションのこの部分も一緒に行われる
プロセスを通じて、ビジネス・コードは知られていないが、TransactionHandlerコンテンツは、複数のシナリオの下で完全に再多重化することができます。

2番目の例では、それはセキュリティの観点から、またはビジネスの観点からであるかどうか、アクセス制御の例であり、我々はすべての要求は、Webシステムの開発期間内のすべてのユーザーに開放されていないので、ここでは、アクセス制御の層を行う必要があります、我々はAOPの役割を見たときにも、確かにAOPは、アクセス制御を行うことができ、ここで私は、アクセス制御を行うためにAOPを使用する方法を紹介します見なければなりません。我々は(このコードは、いくつか例えば、純粋でこれに基づいてSpring MVCの、コントローラのインタフェースを実装するJavaクラスのネイティブは、AOPの使用は、大きく以下のコードをアクセス制御を行うことをことを知って、私はMavenプロジェクトは、通常のJavaプロジェクトで構築されたので、ノーあり検証):

/**
 * @author 五月的仓颉http://www.cnblogs.com/xrq730/p/7003082.html
 */
public class PermissionHandler {
 
    public void hasPermission(JoinPoint jp) throws Exception {
        Object obj = jp.getTarget();
         
        if (obj instanceof Controller) {
            Signature signature = jp.getSignature();
            MethodSignature methodSignature = (MethodSignature)signature;
             
            // 获取方法签名
            Method method = methodSignature.getMethod();
            // 获取方法参数
            Object[] args = jp.getArgs();
             
            // Controller中唯一一个方法的方法签名ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
            // 这里对这个方法做一层判断
            if ("handleRequest".equals(method.getName()) && args.length == 2) {
                Object firstArg = args[0];
                if (obj instanceof HttpServletRequest) {
                    HttpServletRequest request = (HttpServletRequest)firstArg;
                    // 获取用户id
                    long userId = Long.parseLong(request.getParameter("userId"));
                    // 获取当前请求路径
                    String requestUri = request.getRequestURI();
                     
                    if(!PermissionUtil.hasPermission(userId, requestUri)) {
                        throw new Exception("没有权限");
                    }
                }
            }
        }
         
    }
     
}

私たちは、このシナリオAOPラベルを使用することは間違いありませんです ここで私は非常にシンプルを書き、ユーザーがリクエストへのアクセス権を持っているかどうかを判断するの両方によると、現在のユーザIDとリクエストパスを取得、我々は意味を理解することができます。

あとがき
記事はAOPプロセスを使用するネイティブコードから実証し、少しずつでは各進化の長所と短所を紹介し、最後に実用的な例は、AOPが何かを行うことができます分析しました。

JAVAに特化したアリゴールドのドレスJAVAエンジニアのマイクロチャネル公共番号[黄色]小さなランプ
バックエンドテクノロジー・スタック:SpringBoot、SSMの家族のバケットは、MySQL、分散、ミドルウェア、サービス、だけでなく、金融や投資のポイントを理解し、研究に準拠書き込みは、生涯学習の力を信じて!国民の関心「建築家」の後には返信が受信しないように
、Javaベースの、高度、プロジェクトの建築家や他の無料の学習教材と、より多くのデータベース、分散、サービス及びその他の人気のマイクロ学習ビデオ技術、豊富なコンテンツ、理論と実践の両方をまた、オリジナルの研究ガイドのジャワの著者、Javaプログラマのインタビューガイドおよびその他のリソース乾燥品になります提示

おすすめ

転載: www.cnblogs.com/xll1025/p/11407773.html