SpringAOP関数の詳細な使用法
TSMYKJavaテクノロジープログラミング
序文
AOPは、なじみのあるものでもなじみのないものでもあります。Springを知っている人なら誰でも、AOPの概念、つまり、ロギングやトランザクション管理など、本業とは関係のないいくつかの周辺サービスを管理するために使用できるアスペクト指向プログラミングを知っています。 、など;基本的には動作しているため、これまで使用したことがなく、AOPの関連概念はまだクラウドにあります。最近、Springの関連ソースコードを調べているので、最初にAOPの使用法を調べてみましょう。春に。
関連する概念
Spring AOPの使用法を学ぶ前に、AOPの関連する概念を見てみましょう。
Spring AOPの詳細な紹介については、公式Webサイトhttps://docs.spring.io/spring/docs/2.5.x/reference/aop.htmlを参照してください。
-
ジョインポイント:プログラム実行中のポイントを表す接続ポイント。SpringAOPでは、メソッドを表します。つまり、メソッドはジョインポイントと見なすことができます。
-
ポイントカット:カットポイントは接続ポイントに一致する述語です。どういう意味ですか?アドバイスを実行する必要があるカットポイントはカットポイントです。
-
アドバイス:拡張、接続ポイントで実行される操作、5つのタイプに分けられます:事前、事後、異常、最終、およびサラウンド拡張
-
アスペクト:ポイントカットとアドバイスで構成されるアスペクト。@ Aspectアノテーション付きクラスはアスペクトであると単純に考えることができます。
-
ターゲットオブジェクト:ターゲットオブジェクト、つまりアドバイスに織り込まれたターゲットオブジェクト
-
AOPプロキシ:プロキシクラス。SpringAOPでは、AOPプロキシはJDK動的プロキシオブジェクトまたはCGLIBプロキシオブジェクトです。
Weaving
:織り、アスペクトをターゲットオブジェクトに適用
注:上記の概念の中で、このように理解できるジョインポイントとポイントカットを混同しやすくなります。SpringAOPでは、すべての実行可能なメソッドはジョインポイントであり、すべてのジョインポイントをアドバイスに埋め込むことができます。どのジョインポイントに関するアドバイスの実行を確認するために使用される、ジョインポイントを変更する一種の説明情報と見なされます。
くるみ
AOPの概念を理解した後、SpringAopの使用方法を見てみましょう。
- Spring AOPを使用するには、最初にSpring構成ファイルで次のタグを構成する必要があります。
1<aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"/>
このタグには、expose-proxyとproxy-target-classの2つの属性があり、デフォルト値はfalseです。
Exposure-proxy:ThreadLocalを使用して現在のプロキシオブジェクトを保存する必要がありますか?これはどういう意味ですか?たとえば、Aopはインターフェイスの下のすべてのメソッドをインターセプトする必要がありますが、以下に示すように、一部のメソッドは内部で自身を呼び出します。
1 public void test_1()
2 {
3 this.test_2();
4 }
5 public void test_2()
6 {
7 }
test_1を呼び出すと、現時点では拡張のためにtest_2がインターセプトされません。これは、現在のオブジェクトの代わりにAOPプロキシオブジェクトが呼び出され、これがtest_1メソッド内で使用されるため、test_2がインターセプトおよび拡張されないため、この属性Expose-proxyは、この問題、つまりAOPプロキシの取得を解決するために使用されます。
proxy-target-class:Spring AOPの基盤となるテクノロジーがJDKプロキシとCGLIBプロキシに分割された動的プロキシを使用するため、プロキシにCGLIBを使用するかどうか。この属性のデフォルト値はfalseです。つまり、基盤となるAOPはJDKプロキシのデフォルトでは、プロキシを必要とするクラスがインターフェイスを実装していない場合にのみ、CGLIBがプロキシに使用されます。プロキシにCGLIBを使用する場合は、このプロパティをtrueに設定できます。
2.ユーザーの追加、削除、および変更をシミュレートするためにaopによってインターセプトされる必要があるメソッドを定義します。
インターフェース:
1public interface IUserService {
2 void add(User user);
3 User query(String name);
4 List<User> qyertAll();
5 void delete(String name);
6 void update(User user);
7}
インターフェイスの実装:
1@Service("userServiceImpl")
2public class UserServiceImpl implements IUserService {
3
4 @Override
5 public void add(User user) {
6 System.out.println("添加用户成功,user=" + user);
7 }
8
9 @Override
10 public User query(String name) {
11 System.out.println("根据name查询用户成功");
12 User user = new User(name, 20, 1, 1000, "java");
13 return user;
14 }
15
16 @Override
17 public List<User> qyertAll() {
18 List<User> users = new ArrayList<>(2);
19 users.add(new User("zhangsan", 20, 1, 1000, "java"));
20 users.add(new User("lisi", 25, 0, 2000, "Python"));
21 System.out.println("查询所有用户成功, users = " + users);
22 return users;
23 }
24
25 @Override
26 public void delete(String name) {
27 System.out.println("根据name删除用户成功, name = " + name);
28 }
29
30 @Override
31 public void update(User user) {
32 System.out.println("更新用户成功, user = " + user);
33 }
34}
.
3. AOPアスペクトを定義
します。SpringAOPでは、@ Aspectアノテーションで識別されるクラスがアスペクトであり、ポイントカットとアドバイスがアスペクトで定義されます。
3.1ターゲットメソッドが実行される前に実行される事前拡張@Before()
1@Component
2@Aspect
3public class UserAspectj {
4
5 // 在方法执行之前执行
6 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))")
7 public void before_1(){
8 System.out.println("log: 在 add 方法之前执行....");
9 }
10}
上記のメソッドbefore_1()は、インターフェイスのadd()メソッドの事前拡張です。つまり、add()メソッドが実行される前に実行され、次の
テストが行われます。
1@RunWith(SpringJUnit4Cla***unner.class)
2@ContextConfiguration("/resources/myspring.xml")
3public class TestBean {
4
5 @Autowired
6 private IUserService userServiceImpl;
7
8 @Test
9 public void testAdd() {
10 User user = new User("zhangsan", 20, 1, 1000, "java");
11 userServiceImpl.add(user);
12 }
13}
14// 结果:
15// log: 在 add 方法之前执行....
16// 添加用户成功,user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
ターゲットメソッド実行のパラメーターなどの情報を取得する場合は、パラメーターJoinPointをポイントカットメソッドに追加して、ターゲットオブジェクトに関する関連情報を取得できます。
1 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))")
2 public void before_2(JoinPoint joinPoint){
3 Object[] args = joinPoint.getArgs();
4 User user = null;
5 if(args[0].getClass() == User.class){
6 user = (User) args[0];
7 }
8 System.out.println("log: 在 add 方法之前执行, 方法参数 = " + user);
9 }
上記のテストコードを再実行すると、結果は次のようになります。
1log: 在 add 方法之前执行, 方法参数 = User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
2添加用户成功,user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
3.2ターゲットメソッドの実行後に実行される拡張後@After()は、通常の終了であろうと例外であろうと、実行されます。
1 // 在方法执行之后执行
2 @After("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))")
3 public void after_1(){
4 System.out.println("log: 在 add 方法之后执行....");
5 }
3.1のテストコードを実行すると、結果は次のようになります。
1添加用户成功,user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
2log: ==== 方法执行之后 =====
3.3戻り値の拡張@AfterReturning()は、ターゲットメソッドが正常に戻った後に実行されます。例外が発生した場合、例外は実行されず、戻り値を取得できます。
1@AfterReturning(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", returning="object")
2public void after_return(Object object){
3 System.out.println("在 query 方法返回后执行, 返回值= " + object);
4}
テスト:
1@Test
2public void testQuery() {
3 userServiceImpl.query("zhangsan");
4}
5// 结果:
6// 根据name查询用户成功
7// 在 query 方法返回后执行, 返回值= User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
メソッドが@After()と@AfterReturning()の両方によって拡張される場合、どちらが最初に実行されますか?
1@AfterReturning(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", returning="object")
2public void after_return(Object object){
3 System.out.println("===log: 在 query 方法返回后执行, 返回值= " + object);
4}
5
6@After("execution(* main.tsmyk.mybeans.inf.IUserService.query(..))")
7public void after_2(){
8 System.out.println("===log: 在 query 方法之后执行....");
9}
テスト:
1根据name查询用户成功
2===log: 在 query 方法之后执行....
3===log: 在 query 方法返回后执行, 返回值= User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
ご覧のとおり、@ After()が@AfterReturning()の後に配置されている場合でも、最初に実行されます。つまり、@ After()は@AfterReturning()の前に実行されます。
3.4例外拡張@AfterThrowingは、例外がスローされたときに実行され、例外をスローせずに実行されません。
1@AfterThrowing(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", throwing = "ex")
2public void after_throw(Exception ex){
3 System.out.println("在 query 方法抛异常时执行, 异常= " + ex);
4}
次に、拡張されたquery()メソッドを変更して、例外をスローするようにします。
1@Override
2public User query(String name) {
3 System.out.println("根据name查询用户成功");
4 User user = new User(name, 20, 1, 1000, "java");
5 int a = 1/0;
6 return user;
7}
テスト:
1@Test
2public void testQuery() {
3 userServiceImpl.query("zhangsan");
4}
5// 结果:
6// 在 query 方法抛异常时执行, 异常= java.lang.ArithmeticException: / by zero
7// java.lang.ArithmeticException: / by zero ...........
3.5ターゲットメソッドの実行の前後に実行されるサラウンド拡張@Around
1@Around("execution(* main.tsmyk.mybeans.inf.IUserService.delete(..))")
2public void test_around(ProceedingJoinPoint joinPoint) throws Throwable {
3 Object[] args = joinPoint.getArgs();
4 System.out.println("log : delete 方法执行之前, 参数 = " + args[0].toString());
5 joinPoint.proceed();
6 System.out.println("log : delete 方法执行之后");
7}
テスト:
1@Test
2public void test5(){
3 userServiceImpl.delete("zhangsan");
4}
5
6// 结果:
7// log : delete 方法执行之前, 参数 = zhangsan
8// 根据name删除用户成功, name = zhangsan
9// log : delete 方法执行之后
上記は、SpringAOPのいくつかの拡張機能です。
上記の栗では、各メソッドの上のポイントカット式を再度記述する必要があります。@ Pointcutを使用して再利用可能なポイントカット式を宣言し、各メソッドの上のポイントカット式を引用できます。
1// 声明 pointcut
2@Pointcut("execution(* main.tsmyk.mybeans.inf.IUserService.query(..))")
3public void pointcut(){
4}
5
6@Before("pointcut()")
7public void before_3(){
8 System.out.println("log: 在 query 方法之前执行");
9}
10@After("pointcut()")
11public void after_4(){
12 System.out.println("log: 在 query 方法之后执行....");
13}
インジケータ
上記の栗では、メソッド実行の接続ポイントを照合するために使用される実行インジケーターが使用されます。これは、Spring AOPで使用されるメインインジケーターでもあります。ポイントカット式では、ワイルドカード()および(..)が使用されます。それら、()は任意のメソッド、任意の戻り値、(..)はメソッドの任意のパラメーターを表すことができます。他のインジケーターを見てみましょう。
1.内
すべてのクラスは、サブパケットを含む特定のパケットの下のジョインポイント(メソッド)に一致します。インターフェイスが記述されている場合、インターフェイスではなくすべてのクラスが(main.tsmyk.mybeans内などで)有効にならないことに注意してください。 IMPL。すべての参加一致するポイントmain.tsmyk.mybeans.implパッケージのすべてのクラスのを、(main.tsmyk.mybeans.impl内... 2点が一致するすべての参加パッケージとそのサブ内のすべてのクラスのポイントをパッケージ。
栗:
1@Pointcut("within(main.tsmyk.mybeans.impl.*)")
2public void testWithin(){
3}
4
5@Before("testWithin()")
6public void test_within(){
7 System.out.println("test within 在方法执行之前执行.....");
8}
パッケージの下にあるクラスUserServiceImplのdeleteメソッドを実行すると、結果は次のようになります。
1@Test
2public void test5(){
3 userServiceImpl.delete("zhangsan");
4}
5
6// 结果:
7// test within 在方法执行之前执行.....
8// 根据name删除用户成功, name = zhangsan
2. @ within
@within(Secure)など、指定されたアノテーションタイプを保持するすべてのメソッドに一致し、任意のターゲットオブジェクトがSecureアノテーションクラスメソッドを保持します。このアノテーションはターゲットオブジェクトで宣言する必要があり、インターフェイスでの宣言は影響しません。
3.ターゲット
一致するのはターゲットオブジェクトです。ターゲット(main.tsmyk.mybeans.inf.IUserService)は、このインターフェイスの下のすべてのジョインポイントに一致します。
1@Pointcut("target(main.tsmyk.mybeans.inf.IUserService)")
2public void anyMethod(){
3}
4
5@Before("anyMethod()")
6public void beforeAnyMethod(){
7 System.out.println("log: ==== 方法执行之前 =====");
8}
9
10@After("anyMethod()")
11public void afterAnyMethod(){
12 System.out.println("log: ==== 方法执行之后 =====");
13}
その後、このインターフェースで任意のメソッドを実行すると、拡張されます。
4. @target
ターゲットオブジェクトと一致させるには、このオブジェクトに特定のアノテーションが必要です。たとえば、@ target(org.springframework.transaction.annotation.Transactional)は、@ Transactionalアノテーションが付けられたメソッドと一致します。
5.これ
現在のAOPプロキシオブジェクトタイプthis(service.IPointcutService)の実行メソッドと一致し、現在のAOPオブジェクトはIPointcutServiceインターフェイスの任意のメソッドを実装します
6.引数
マッチングパラメータ、
1 // 匹配只有一个参数 name 的方法
2 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name)")
3 public void test_arg(){
4
5 }
6
7 // 匹配第一个参数为 name 的方法
8 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name, ..)")
9 public void test_arg2(){
10
11 }
12
13 // 匹配第二个参数为 name 的方法
14 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(*, name, ..)")
15 public void test_arg3(){
16
17 }
7. @arg
一致するパラメーター、パラメーターには特定のアノテーション@args(Anno))があり、メソッドパラメーターはAnnoアノテーションでマークされています。
8. @ annotation
特定のアノテーションに
一致@annotation(org.springframework.transaction.annotation.Transactional)は、@ Transactionalアノテーションが付けられたすべてのメソッドに一致します。
9.豆
特定のBean名を照合する方法
1 // 匹配 bean 的名称为 userServiceImpl 的所有方法
2 @Before("bean(userServiceImpl)")
3 public void test_bean(){
4 System.out.println("===================");
5 }
6
7 // 匹配 bean 名称以 ServiceImpl 结尾的所有方法
8 @Before("bean(*ServiceImpl)")
9 public void test_bean2(){
10 System.out.println("+++++++++++++++++++");
11 }
テスト:
Beanの下でメソッドを実行します:
1@Test
2public void test5(){
3 userServiceImpl.delete("zhangsan");
4}
5//结果:
6// ===================
7// +++++++++++++++++++
8// 根据name删除用户成功, name = zhangsan
上記は、SpringAOPのすべてのインジケーターの使用方法です。
春のAOPの原則
Spring AOPの最下層は動的プロキシを使用します。動的プロキシを実装する方法は2つあります。1つはJDKの動的プロキシ、もう1つはCGLIBの動的プロキシです。上記の機能を実現するには、次の2つの方法を使用します。 、呼び出しでUserServiceImplクラスメソッドの場合、メソッドの実行の前後にログを追加します。
JDK動的プロキシ
JDK動的プロキシを実装するには、InvocationHandlerインターフェースを実装し、invokeメソッドを書き直す必要があります。
1public class UserServiceInvocationHandler implements InvocationHandler {
2
3 // 代理的目标对象
4 private Object target;
5
6 public UserServiceInvocationHandler(Object target) {
7 this.target = target;
8 }
9
10 @Override
11 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
12
13 System.out.println("log: 目标方法执行之前, 参数 = " + args);
14
15 // 执行目标方法
16 Object retVal = method.invoke(target, args);
17
18 System.out.println("log: 目标方法执行之后.....");
19
20 return retVal;
21 }
22}
テスト:
1public static void main(String[] args) throws IOException {
2
3 // 需要代理的对象
4 IUserService userService = new UserServiceImpl();
5 InvocationHandler handler = new UserServiceInvocationHandler(userService);
6 ClassLoader classLoader = userService.getClass().getClassLoader();
7 Class[] interfaces = userService.getClass().getInterfaces();
8
9 // 代理对象
10 IUserService proxyUserService = (IUserService) Proxy.newProxyInstance(classLoader, interfaces, handler);
11
12 System.out.println("动态代理的类型 = " + proxyUserService.getClass().getName());
13 proxyUserService.query("zhangsan");
14
15 // 把字节码写到文件
16 byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{UserServiceImpl.class});
17 FileOutputStream fos =new FileOutputStream(new File("D:/$Proxy.class"));
18 fos.write(bytes);
19 fos.flush();
20
21}
結果:
1动态代理的类型 = com.sun.proxy.$Proxy0
2log: 目标方法执行之前, 参数 = [Ljava.lang.Object;@2ff4acd0
3根据name查询用户成功
4log: 目标方法执行之后.....
ターゲットメソッドの実行の前後にログが出力されていることがわかります。上記のメインメソッドで、プロキシオブジェクトのバイトコードをファイルに書き込んだので、分析してみましょう。
&Proxy.classファイルを次のように逆コンパイルします。
インターフェイスを実装することで実現されていることがわかります。
JDKは、インターフェースを実装するクラスのみをプロキシできます。クラスがインターフェースを実装しない場合、これらのクラスのプロキシを作成することはできません。現時点では、CGLIBをプロキシに使用できます。
CGLIB動的プロキシ
次に、CGLIBがどのように実装されているかを見てみましょう。
まず、プロキシを必要とする新しいクラスを作成します。これはインターフェイスを実装していません。
1public class UserServiceImplCglib{
2 public User query(String name) {
3 System.out.println("根据name查询用户成功, name = " + name);
4 User user = new User(name, 20, 1, 1000, "java");
5 return user;
6 }
7}
次に、CGLIBを使用して、メソッドクエリの実行前後にログを追加する必要があります。
CGLIBを使用して動的プロキシを実装するには、インターフェイスMethodInterceptorを実装し、インターセプトメソッドをオーバーライドする必要もあります。
1public class CglibMethodInterceptor implements MethodInterceptor {
2
3 @Override
4 public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
5
6 System.out.println("log: 目标方法执行之前, 参数 = " + args);
7
8 Object retVal = methodProxy.invokeSuper(obj, args);
9
10 System.out.println("log: 目标方法执行之后, 返回值 = " + retVal);
11 return retVal;
12 }
13}
テスト:
1public static void main(String[] args) {
2
3 // 把代理类写入到文件
4 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\");
5
6 Enhancer enhancer = new Enhancer();
7 enhancer.setSuperclass(UserServiceImplCglib.class);
8 enhancer.setCallback(new CglibMethodInterceptor());
9
10 // 创建代理对象
11 UserServiceImplCglib userService = (UserServiceImplCglib) enhancer.create();
12 System.out.println("动态代理的类型 = " + userService.getClass().getName());
13
14 userService.query("zhangsan");
15}
結果:
1动态代理的类型 = main.tsmyk.mybeans.impl.UserServiceImplCglib$$EnhancerByCGLIB$$772edd85
2log: 目标方法执行之前, 参数 = [Ljava.lang.Object;@77556fd
3根据name查询用户成功, name = zhangsan
4log: 目标方法执行之后, 返回值 = User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
結果はJDK動的プロキシを使用した場合と同じであることがわかります。さらに、プロキシクラスのタイプがサブクラスであるmain.tsmyk.mybeans.impl.UserServiceImplCglib $$ EnhancerByCGLIB $$ 772edd85であることがわかります。 UserServiceImplCglibの、つまりCGLIBは継承によって実現されます。
総括する
-
JDKの動的プロキシは、プロキシインターフェイスのプロキシクラスを生成するリフレクションとインターセプタのメカニズムを介して実装されます。
-
CGLIBの動的プロキシは継承によって実現され、プロキシクラスのクラスファイルは、そのバイトコードを変更してサブクラスを生成することによってロードおよび処理されます。
-
JDK動的プロキシは、クラスではなく、インターフェースを実装するクラスのプロキシのみを生成できます。
-
CGLIBはクラスのプロキシを実装し、主に指定されたクラスのサブクラスを生成し、その中のメソッドをオーバーライドしますが、継承を使用するため、最終的なクラスまたはメソッドをプロキシすることはできません。
- Spring AOPでは、インターフェースが実装されている場合、デフォルトでJDKプロキシが使用されるか、CGLIBプロキシが必須になります。プロキシとなるクラスがインターフェースを実装していない場合、CGLIBがプロキシに使用され、Springは自動的に切り替わります。
上記のSpringAOP栗の実装は、アノテーション方式で実現されています。また、AOP機能は設定ファイルからも実現できます。上記はSpringAOPの詳細な使用プロセスです。