1.AOPの概要
1.AOPの基本的な紹介
春のもう一つの重要なポイントはAOPです。AOPのフルネームは「アスペクト指向プログラミング」です。つまり、アスペクト指向プログラミングは、ビジネスロジックのさまざまな部分を分離し、開発者がビジネスロジックを作成するときにコアビジネスに集中できるようにして、開発効率を向上させます。簡単に言えば、ソースコードを変更せずにメイン関数に新しい関数を追加することを意味します。
従来のビジネス処理コードでは、通常、トランザクション処理やロギングなどの操作を実行します。以前は、OOPのアイデアを使用することで、コードの再利用を組み合わせたり継承したりすることができましたが、特定の機能(ロギングなど)を実装する場合、この時点で同じコードが各メソッドに分散されます。 。関数を閉じたり変更したりする場合は、関連するすべてのメソッドを変更する必要があります。これにより、開発者の作業負荷が増えるだけでなく、コードのエラー率も上がります。
AOPは、従来の垂直継承システムの反復コードを置き換えるために、水平抽出メカニズムを採用しています。この水平抽出メカニズムは、複数のクラスに影響を与える一般的な動作を再利用可能なモジュールにカプセル化し、「アスペクト」という名前を付けることができます。これをアスペクトと呼びます。アスペクトとは、ビジネスに関連しないが、一般にビジネスモジュールによって呼び出されるロジックまたは責任を指します。これらはカプセル化されているため、システム内の反復コードが削減され、モジュール間の結合が削減されます。これにより、後続の操作性と保守性が向上します。 。
例:システムでは、ログ記録は基本的な機能であり、システム内のすべてのビジネスプロセスをログに記録する必要があります。ロギング機能を各ビジネスプロセスに書き込むと、コードの冗長性が生じ、メンテナンスが難しくなります。この時点でロギングロジックが変更されたと仮定すると、各ビジネスプロセスのロギングコードを変更する必要がありますが、これは明らかにお勧めできません。逆に、ログレコード機能を独立したモジュールとして抽出した場合、ビジネスプロセスで必要な場合は、ログレコード機能を自動的にビジネスプロセスに分割するため、メンテナンスが容易になります。そして、この正式なAOPは達成できます。以下に示すように:
2.AOP関連の用語
用語 | 説明 |
---|---|
横断的関心事 |
どの方法が傍受され、傍受後に何をすべきか、これらの懸念は横断的関心事と呼ばれます |
側面 |
これは通常、エントリポイントと通知を定義できるクラスです。アプリケーションには、さまざまな側面があります。 |
JointPoint |
プログラム実行プロセスの明確なポイント、つまり、操作プロセス中にアスペクトに挿入する必要があるビジネスプロセスの特定の場所。これは通常、メソッドの呼び出しです。Springはメソッドタイプの接続ポイントのみをサポートするため、Springの接続ポイントはインターセプトされたメソッドを参照します。実際、接続ポイントはフィールドまたはコンストラクターにすることもできます。 |
助言 |
アスペクトの特定の実装方法、ジョインポイントをインターセプトした後に何をするか、つまり、ポイントカットの拡張の内容 |
ポイントカット |
これは通知付きの接続ポイントであり、通知がどの接続ポイントにカットするかを定義するために使用されます。通常、異なる通知は異なる接続ポイントにカットする必要があります。プログラムでは、主にエントリポイント式の記述に反映されます。 |
織り(織り) |
カットインを示します。ウィービングとも呼ばれます。ターゲットオブジェクトにアスペクトを適用して、新しいプロキシオブジェクトを作成するプロセス。このプロセスは、コンパイル、クラスのロード、および実行時に発生する可能性があります |
前書き |
コードを変更しないという前提の下で、イントロダクションは実行時にクラスにいくつかのメソッドまたはフィールドを動的に追加できます |
プロキシ |
プロキシオブジェクトを表します。通知がターゲットオブジェクトに適用された後に動的に作成されるオブジェクト。プロキシオブジェクトは、ターゲットオブジェクトのビジネスロジック機能と切断面によって形成されるオブジェクトであることが簡単に理解できます。SpringのAOPプロキシは、JDKの動的プロキシまたはCGLIBプロキシにすることができます。前者はインターフェイスに基づいており、後者はサブクラスに基づいています。 |
ターゲットオブジェクト |
接続ポイントを含むオブジェクトは、1つ以上のアスペクトによって通知されたオブジェクトです。通知オブジェクトまたはプロキシオブジェクトとも呼ばれます |
3.AOPでの通知の種類
通知タイプ | 説明 |
---|---|
事前通知(前) |
ターゲットメソッドが呼び出される前に拡張処理を実行します。@ Beforeはポイントカット式を指定するだけで済みます。 |
返品後の通知(AfterReturning) |
ターゲットメソッドが正常に完了した後、拡張されます。ポイントカット式を指定するだけでなく、@ AfterReturningは、ターゲットメソッドの戻り値を表す戻り値パラメーター名returningを指定することもできます。 |
例外がスローされた後の通知(AfterThrowing) |
これは主に、プログラムで未処理の例外を処理するために使用されます。@ AfterThrowingは、ポイントカット式を指定するだけでなく、ターゲットメソッドでスローされた例外オブジェクトにアクセスするために使用できるスロー戻り値パラメーター名を指定することもできます。 |
通知後(後) |
エンハンスメントは、ターゲットメソッドが正常に完了したかどうかに関係なく、ターゲットメソッドが完了した後に実行されます。@Afterはエントリポイント式を指定できます |
アラウンド通知(アラウンド) |
ターゲットメソッドの完了前後の処理を強化します。サラウンド通知は、トランザクションやログなどの最も重要なタイプの通知です。サラウンド通知です。 |
4.エントリポイント式
ポイントカットインジケータは、ポイントカット式の目的を示すために使用されます。現在、SpringAOPには実行メソッドの接続ポイントが1つしかありません。ポイントカット式の形式は次のとおりです。
実行([可視性]戻り値の型[宣言型]。メソッド名(パラメーター)[例外])、[]はオプションですが、その他もワイルドカードの使用をサポートしています。
*:すべての文字に一致
..:通常、複数のパッケージ、複数のパラメーターに一致するために使用されます
+:クラスとそのサブクラスを示します
演算子は、&&、||、!などのポイントカット式でも使用できます。
ポイントカット式の使用法を説明する例を次に示します。
(1)標準式の記述:public void com.yht.controller.UserController.findUser()
(2)すべてのワイルドカードの書き込み:* .. *。*(..)
(3)アクセス修飾子を省略します:void com.yht.controller.UserController.findUser()
(4)戻り値はワイルドカードを使用します*:* com.yht.controller.UserController.findUser()
(5)パッケージ名はワイルドカードを使用します*:**。*。*。UserController.findUser()//パッケージのレベルが複数ある場合は、いくつか記述します*
(6)クラス名とメソッド名にはワイルドカード*を使用します:* .. *。*()
(7)パラメータリスト
パラメータがある場合は、メソッドの()に*-(*)を追加します
パラメータの有無にかかわらず:メソッドの()を追加します。.——(..)
(8)通常書かれている:* com.yht.controller.UserController。*(..)
ポイントカット式に関するその他の紹介については、次のブログを参照してください。
https://blog.51cto.com/lavasoft/172292
https://www.cnblogs.com/sjqq/p/10241781.html
第二に、Springのプロキシモード
以前に紹介したもの:SpringのAOPプロキシは、JDKの動的プロキシまたはCGLIBプロキシにすることができます。前者はインターフェイスに基づいており、後者はサブクラスに基づいています。次に、これら2つのプロキシメソッドを実装します。
1.JDK動的プロキシ
(1)IOderDaoインターフェイスとそのサブクラスOrderDaoImplを作成します。
public interface IOrderDao {
void addOrder();
void deleteOrder();
void updateOrder();
void searchOrder();
}
public class OrderDaoImpl implements IOrderDao {
@Override
public void addOrder() {
System.out.println("添加订单");
}
@Override
public void deleteOrder() {
System.out.println("删除订单");
}
@Override
public void updateOrder() {
System.out.println("更新订单");
}
@Override
public void searchOrder() {
System.out.println("查询订单");
}
}
(2)アスペクトクラスMyAspectを作成します
public class MyAspect {
public void before(){
System.out.println("方法执行之前");
}
public void after(){
System.out.println("方法执行之后");
}
}
(3)プロキシクラスMyBeanFactoryを作成します
public class MyBeanFactory {
public static IOrderDao getBean() {
// 准备目标类
final IOrderDao customerDao = new OrderDaoImpl();
// 创建切面类实例
final MyAspect myAspect = new MyAspect();
// 使用代理类,进行增强
//Proxy 的 newProxyInstance() 方法的第一个参数是当前类的类加载器,第二参数是所创建实例的实现类的接口,第三个参数就是需要增强的方法
return (IOrderDao) Proxy.newProxyInstance(
MyBeanFactory.class.getClassLoader(),
new Class[] { IOrderDao.class }, new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
myAspect.before(); // 前增强
Object obj = method.invoke(customerDao, args);
myAspect.after(); // 后增强
return obj;
}
});
}
}
(4)テストを実行します。
@Test
public void test() {
// 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
IOrderDao orderDao = MyBeanFactory.getBean();
// 执行方法
orderDao.updateOrder();
System.out.println("=====================");
orderDao.searchOrder();
}
結果は次のとおりです。
2.cglib動的プロキシ
(1)jarパッケージをインポートします
<!-- https://mvnrepository.com/artifact/cglib/cglib-nodep -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.1</version>
</dependency>
(2)ターゲットクラスGoodsを作成します。
public class Goods{
public void addGoods() {
System.out.println("添加货物");
}
public void deleteGoods() {
System.out.println("删除货物");
}
public void updateGoods() {
System.out.println("更新货物");
}
public void searchGoods() {
System.out.println("查询货物");
}
}
(3)プロキシクラスGoodsBeanFactoryを作成します
public class GoodsBeanFactory {
public static Goods getBean() {
// 准备目标类
final Goods goodsDao = new Goods();
// 创建切面类实例
final MyAspect myAspect = new MyAspect();
// 生成代理类,CGLIB在运行时,生成指定对象的子类,增强
Enhancer enhancer = new Enhancer();
// 确定需要增强的类
enhancer.setSuperclass(goodsDao.getClass());
// 添加回调函数
enhancer.setCallback(new MethodInterceptor() {
// intercept 相当于 jdk invoke,前三个参数与 jdk invoke—致
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
myAspect.before(); // 前增强
Object obj = method.invoke(goodsDao, args); // 目标方法执行
myAspect.after(); // 后增强
return obj;
}
});
// 创建代理类
Goods goodsDaoProxy = (Goods) enhancer.create();
return goodsDaoProxy;
}
}
(4)テスト
@Test
public void testGoods() {
// 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
Goods goods = GoodsBeanFactory.getBean();
// 执行方法
goods.deleteGoods();
System.out.println("=====================");
goods.searchGoods();
}
結果は次のとおりです。