この記事では、aop のプロセス ソース コードの分析も開始します。IOC とは異なり、aop は比較的単純です。今後の主な内容は、aop 関連の構成情報の分析、エージェントの作成、およびエージェント プロセスの呼び出しです。この 3 つの部分で、aop のソース コード分析について簡単に説明します。 。
AOP の過去と現在を簡単に説明する
まず、連載の冒頭は相変わらずのルーチンですが、aopの概念的な知識についてお話しさせていただきますが、主な情報源はインターネットと公式です。aop についてある程度理解している場合は、このセクションを飛ばしてください。
- AOP (アスペクト指向プログラミング): アスペクト指向プログラミング。
- 目的: ログ管理やトランザクション管理など、システム内の横断的な問題に使用されます。
- 実装: プロキシ モードを使用して、プロキシ オブジェクトを通じてプロキシされたオブジェクトに関数を追加します。したがって、重要なのは、AOP フレームワークが AOP プロキシ オブジェクトを自動的に作成し、プロキシ モードが静的プロキシと動的プロキシに分けられることです。
- フレームワーク:
AspectJ は、コンパイル時に拡張された静的プロキシを使用し、コンパイル時にプロキシ オブジェクトを生成します。SpringAOP は、実行時に
拡張された動的プロキシを使用し、実行時にプロキシ オブジェクトを動的に生成します。
Spring AOP の設定方法は現在 3 つありますが、Spring は下位互換性が高いので安心して使用できます。
- Spring 1.2 インターフェースベースの構成: 初期の Spring AOP は完全に複数のインターフェースに基づいていました
- Spring 2.0 スキーマベースの構成: Spring 2.0 以降は、XML を使用して構成し、名前空間を使用します。
- Spring 2.0 @AspectJ 設定: アノテーションを使用して設定します。この方法が最も便利です。ここでは @AspectJ と呼ばれていますが、AspectJ とは何の関係もありません。
ここで紹介する Spring AOP は純粋な Spring コードであり、AspectJ とは何の関係もありませんが、Spring は AspectJ が提供する jar パッケージ内のアノテーションの使用を含め、AspectJ の概念を拡張しますが、その実装には依存しないことに注意してください。機能。@Aspect、@Pointcut、@Before、@After などのアノテーションはすべて AspectJ からのものですが、関数の実装は依然として Spring AOP 自体によって実装されています。
現在、Spring AOP の基盤となる実装メカニズムは、JDK 動的プロキシと CGLIB 動的バイトコード生成の 2 つです。ソース コードを読む前にこれら 2 つのメカニズムの使用法を理解しておくと、ソース コードをより深く理解できます。
ソースコード分析
ソースコードは以前IOCを解析した際に使用したソースコードを利用し、テストクラスを作成します。
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("classpath:applicationContext-aop.xml");
UserService userService = (UserService) applicationContext.getBean("userServiceImpl");
userService.findAll();
}
今日のソース コード分析の主な目的は、クラス内の applicationContext (コンテナー起動時のコンテンツ) の作成をテストすることです。特定のメソッドの呼び出しについては後ほど説明します。
IOC はすでにすべてのコア メソッドのリフレッシュ メソッドを以前に一度分析しているため、IOC について知らない友人は、IOC の 2 番目の記事「IOC プロセス分析 - BeanFactory作成」から読み始めることができます。ここまで見てきたので、具体的な方法に直接進みましょう。
AOP構成情報の分析
まず、aop のタグ設定は Bean タグとは少し異なるため、それを確認する場合は、タグの内容の解析、つまり BeanDefinition の構築から始めます。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans"
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd>
<context:component-scan base-package="com.itheima"/>
<!--@EnableAspectJAutoProxy-->
<aop:aspectj-autoproxy />
</beans>
コードはここ、parseBeanDefinitions メソッドに直接あります (ここではグローバル検索で十分です。見つける方法は、IOC の 2 番目の記事を参照してください)。ここでわかるのは、解析メソッドの 2 つの分岐があることです。 Spring のスキーマ メカニズムの前でも説明したため、aop タグは 2 番目のメソッド parseCustomElement メソッドである必要があります。
if (node instanceof Element) {
Element ele = (Element) node;
//下面有两个分支
if (delegate.isDefaultNamespace(ele)) {
// 1.1 默认命名空间节点的处理,例如: <bean id="test" class="" />
//分支1:代表解析标准元素 <import />、<alias />、<bean />、<beans /> 这几个
//标准节点
parseDefaultElement(ele, delegate);
}
else {
// 1.2 自定义命名空间节点的处理,例如:<context:component-scan/>、<aop:aspectj-autoproxy/>
//分支2:代表解析 <mvc />、<task />、<context />、<aop /> 、<component-scan />等
//特殊节点
delegate.parseCustomElement(ele);
}
}
フォローアップ方法。
ここでの最初の焦点は、名前空間プロセッサを取得することです。以前にスキーマ メカニズムを紹介したときに、このパーサーは対応するラベル設定によって提供されるとすでに述べました。そのため、対応する「http://www.springframework」を見つける必要があります。 .org/schema/aop」ロゴを検索し、ロゴに従って対応する spring.handlers ファイルを見つけます。
//解析命名空间,得到一个命名空间处理器
//重点
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
//开始解析
//主线 重点
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
aop を導入した構成ファイルが多数ある可能性があるため、グローバル検索で見つけるのが難しい場合は、xml 内のロゴを直接クリックして、対応する jar パッケージにジャンプし、次のディレクトリで対応する spring.handlers ファイルを見つけることができます。 infフォルダー
この aop タグのパーサーはこのファイルに保存されます。
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
次に、AopNamespaceHandler クラスを直接実行します。
タグごとに異なるパーサーの生成が確認できます。上で設定したのは aop の基本タグであるspectj-autoproxyタグなので、対応するAspectJAutoProxyBeanDefinitionParserクラスを直接見つけます。この init メソッドが呼び出されるタイミングについては、引き続き下を見てみましょう。
public class AopNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// In 2.0 XSD as well as in 2.5+ XSDs
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace in 2.5+
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
}
parseCustomElement メソッドに戻り、そのsolveメソッドを続けます。
この時点で init メソッドが呼び出されていることがわかります。これは、さまざまなパーサー オブジェクトがここで作成されて返されることを意味します。
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
もう一度 parseCustomElement メソッドに戻ります。
現在のパーサーが AspectJAutoProxyBeanDefinitionParser オブジェクトであることがわかったので、引き続きその解析メソッドを追跡できます。
フォローアップ方法。注目すべきは、最初に登録された AspectJAnotationAutoProxyCreator メソッドです。
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 1.注册AspectJAnnotationAutoProxyCreator
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
// 2.对于注解中子节点的处理
extendBeanDefinition(element, parserContext);
return null;
}
ファローアップ。
ここでの目的は、AnnotationAwareAspectJAutoProxyCreator オブジェクトを BeanFactory に登録することです。これはシングルトン プールに登録されないことに注意してください。具体的な理由については、以下を参照してください。
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
// 1.注册AnnotationAwareAspectJAutoProxyCreator
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
// 2.对于proxy-target-class以及expose-proxy属性的处理
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
// 3.注册组件并通知,便于监听器做进一步处理
registerComponentIfNecessary(beanDefinition, parserContext);
}
AnnotationAwareAspectJAutoProxyCreator オブジェクトの構造を確認できます。
このオブジェクトの最上位インターフェイスは BeanPostProcessor インターフェイスです。これは、このオブジェクトが Bean ポストプロセッサ オブジェクトであることを意味します。したがって、BeanFactory に登録すると、単一の強制プールには登録されず、BeanPostProcessor のコレクションに登録されます。
まとめ
ここでの aop タグの解析は次のようになります。目的は、最初の Bean のポストプロセッサを BeanPostProcessor コレクションに登録することです。
AOPに対応したBeanPostProcessorの実行
上記では、AOP 関連の BeanPostProcessor が構築されていることがわかります。次のステップでは、これらの Bean ポストプロセッサの実行を確認するため、Bean の初期化コンテンツの doCreateBean メソッドを直接見つけます。
メソッドを見つけたら、対応する BeanPostProcessor の initializeBean メソッドの呼び出しを直接見つけて、関連するコンテンツを実行します。
preメソッドは、すべてのvisor-postProcessBeforeInitializationメソッドを解析します。
方法をフォローアップします。最初のプリメソッドの呼び出しは引き続き applyBeanPostProcessorsBeforeInitialization メソッドに続きます。ただし、このメソッドはこれまでに何度も見たことがありますが、それとは何の関係もなく、すべての BeanPostProcessor をループアウトし、対応する postProcessBeforeInitialization メソッドを実行するだけです。
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 2.在初始化前应用BeanPostProcessor的postProcessBeforeInitialization方法,允许对bean实例进行包装
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
上記で構築された AnnotationAwareAspectJAutoProxyCreator オブジェクトの Before メソッドを直接フォローアップします。このオブジェクトは Before メソッドを実装していないため、ここでのフォローアップは親クラス AbstractAutoProxyCreator オブジェクトの実装であることに注意してください。
コードをフォローアップします。キー ポイントを見つけて shouldSkip メソッドを呼び出します。Before メソッドがプロキシ オブジェクトを作成するかどうかに注意してください。ただし、すべての Advisor が読み込まれることになります。
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
ファローアップ。対応するサブクラスの書き換えを追跡して見つける必要があることに注意してください。次に、キーメソッド findCandidateAdvisors メソッド呼び出しを見つけます。
List<Advisor> candidateAdvisors = findCandidateAdvisors();
まだフォローアップします。または、書き換える対応するサブクラスを見つけます。
ここでは、親クラスのルールに従って見つかったすべてのアドバイザーが最初に見つかりますが、ここには何もない、つまり null であるため、buildAspectJAdvisors メソッドの呼び出しに依存します。
@Override
protected List<Advisor> findCandidateAdvisors() {
// Add all the Spring advisors found according to superclass rules.
// 1.添加根据父类规则找到的所有advisor
List<Advisor> advisors = super.findCandidateAdvisors();
// Build Advisors for all AspectJ aspects in the bean factory.
// 2.为bean工厂中的所有AspectJ方面构建advisor
if (this.aspectJAdvisorsBuilder != null) {
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
フォローアップ方法。
まず、すべてのアドバイザ オブジェクト キャッシュが 1 回取得されます。値がある場合は直接返されます。値がない場合は解析されます。すべての Bean 初期化でこれが使用されるため、解析するのは 1 回だけです。 BeanPostProcessor を使用するため、繰り返しの解析を避ける必要があります。
List<String> aspectNames = this.aspectBeanNames;
// 1.如果aspectNames为空,则进行解析
if (aspectNames == null) {
次に、すべての BeanName を取得し、ループします。
// 1.1 获取所有的beanName
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Object.class, true, false);
// 1.2 循环遍历所有的beanName,找出对应的增强方法
for (String beanName : beanNames) {
最初の重要な点は、Aspect アノテーションによって変更する必要があるインターフェイスがアドバイザー オブジェクトに構築されるということです。
// 1.4 如果beanType存在Aspect注解则进行处理
if (this.advisorFactory.isAspect(beanType)) {
2 番目のキー ポイントは、getAdvisors メソッドを呼び出して、現在のオブジェクトのすべてのメソッドによってカプセル化された Advisor オブジェクト コレクションを取得することです。
// 使用BeanFactory和beanName创建一个BeanFactoryAspectInstanceFactory,主要用来创建切面对象实例
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
// 1.5 解析标记AspectJ注解中的增强方法===》》》》
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
getAdvisors メソッドを実行して、特定のパッケージの内容を確認します。
ここでは、メソッドで変更されたさまざまなアノテーションに従って、さまざまな Advisor オブジェクトにカプセル化されます。
// 2.获取Aspect()标注的类名
String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
// 5.获取切面类中的方法(也就是我们用来进行逻辑增强的方法,被@Around、@After等注解修饰的方法,使用@Pointcut的方法不处理)==>
for (Method method : getAdvisorMethods(aspectClass)) {
// 6.处理method,获取增强器==>
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
if (advisor != null) {
// 7.如果增强器不为空,则添加到advisors
advisors.add(advisor);
}
}
引き続きフォローする必要はありませんので、一般的な文章を見てみましょう。
1 つ目は @Aspect アノテーション変更クラスで、このクラスがアスペクト クラスであり、aop で見つけることができることを示します。
次に、@Pointcut アノテーションによって最初のメソッド、つまりインターセプトする必要があるすべての特定のオブジェクトが変更されます。
最後に、@Before アノテーションはプレメソッドを変更し、@After アノテーションはポストメソッドを変更し、@Around アノテーションは周囲のメソッドを変更します。
@Component
@Aspect // 切面
public class AopAspect {
@Pointcut("execution(* com.itheima.service..*.*(..))")
public void pointcut() {
}
@Before("pointcut()")
public void before() {
System.out.println("before");
}
@After("pointcut()")
public void after() {
System.out.println("after");
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws InterruptedException {
System.out.println("around advice start");
try {
Object result = proceedingJoinPoint.proceed();
System.out.println("around advice end");
return result;
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
}
post メソッドはプロキシ オブジェクトを生成します - postProcessAfterInitialization メソッド
または、AbstractAutoProxyCreator オブジェクトに戻り、postProcessAfterInitialization メソッドを直接見つけます。
コードをフォローアップします。
1 つ目は、キャッシュ用のキーを生成してから、wrapIfNecessary メソッドを呼び出すことです。
//2.如果beanName为空,使用Class对象作为缓存的key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 1.判断当前bean是否需要被代理,如果需要则进行封装
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
ファローアップ。
ここでは、まず上記の解析キャッシュ内のすべての Advisor を取得します。まず getAdvicesAndAdvisorsForBean メソッドを確認します。
// 4.获取当前bean的Advices和Advisors===》
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
フォローアップ後、findCandidateAdvisors メソッドを直接呼び出すことが重要ですが、ここでは説明せず、再度フォローアップするだけです。
この部分でわかるように、最初のステップはすべての Advisor オブジェクトを取得し、次にこのオブジェクトをインターセプトする Advisor をフィルタリングして除外することです。まだ Bean オブジェクトの初期化プロセス中であるため、ここで参照されるオブジェクトは初期化中のオブジェクトであることに注意してください。最後に、修飾された Advisor オブジェクトを順番に並べ替えるだけです。ここでの並べ替えは、後続のプロキシ オブジェクトの実行時にその呼び出し順序を容易にするために、周囲のメソッド、前メソッド、および後メソッドを並べ替えることです。
findCandidateAdvisors メソッドに関しては、上記の呼び出しとまったく同じです。唯一の違いは、今回は再度解析する必要がなく、上記のキャッシュ コレクションを直接返すことです。
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
// 1.查找所有的候选Advisor
//将所有拥有@Aspect注解的类转换为advisors(aspectJAdvisorsBuilder.buildAspectJAdvisors)
// 2.从所有候选的Advisor中找出符合条件的
List<Advisor> candidateAdvisors = findCandidateAdvisors();
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
// 3.扩展方法,留个子类实现
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
// 4.对符合条件的Advisor进行排序
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
WrapIfNecessary メソッドに戻ります。
継続はプロキシの作成部分です。最後に、作成されたプロキシがキャッシュに保存され、結果が返されます。このメソッドについては後で説明することはありませんので、楽観視してください。
// 5.如果存在增强器则创建代理
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 5.1 创建代理对象:这边SingletonTargetSource的target属性存放的就是我们原来的bean实例(也就是被代理对象),
// 用于最后增加逻辑执行完毕后,通过反射执行我们真正的方法时使用(method.invoke(bean, args))
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
// 5.2 创建完代理后,将cacheKey -> 代理类的class放到缓存
this.proxyTypes.put(cacheKey, proxy.getClass());
// 返回代理对象
return proxy;
}
引き続き createProxy メソッドに従います。
ここにはたくさんのコードがあるので、心配する必要はありません。下の最後の文を読んでください。
proxyFactory.getProxy(getProxyClassLoader());
コードをフォローアップします。
急いで getProxy を見ようとせず、まず createAopProxy メソッドを見てください。
public Object getProxy(@Nullable ClassLoader classLoader) {
// 1.createAopProxy:创建AopProxy
// 2.getProxy(classLoader):获取代理对象实例
return createAopProxy().getProxy(classLoader);
}
ここでの呼び出しは比較的単純なので、ステップごとに実行することはせず、結果を見てください。
ここでCglibエージェントを使用するかJDKエージェントを使用するかを判断します。直接的な結論は、JDK プロキシがデフォルトで使用され、Cglib プロキシが設定されているということです。JDKプロキシ対応オブジェクト:JdkDynamicAopProxy、Cglibプロキシ対応オブジェクト:ObjenesisCglibAopProxy。ここで、2 つのプロキシに対応するオブジェクトを覚えておく必要があります。後でプロセスを実行するときに、プロキシを確認し、対応するオブジェクトの呼び出しメソッドを直接確認します。
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 1.判断使用JDK动态代理还是Cglib代理
// optimize:用于控制通过cglib创建的代理是否使用激进的优化策略。除非完全了解AOP如何处理代理优化,否则不推荐使用这个配置,目前这个属性仅用于cglib代理,对jdk动态代理无效
// proxyTargetClass:默认为false,设置为true时,强制使用cglib代理,设置方式:<aop:aspectj-autoproxy proxy-target-class="true" />
// hasNoUserSuppliedProxyInterfaces:config是否存在代理接口或者只有SpringProxy一个接口
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
// 拿到要被代理的对象的类型
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
// TargetSource无法确定目标类:代理创建需要接口或目标。
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 要被代理的对象是接口 || targetClass是Proxy class
// 当且仅当使用getProxyClass方法或newProxyInstance方法动态生成指定的类作为代理类时,才返回true。
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
// JDK动态代理,这边的入参config(AdvisedSupport)实际上是ProxyFactory对象
// 具体为:AbstractAutoProxyCreator中的proxyFactory.getProxy发起的调用,在ProxyCreatorSupport使用了this作为参数,
// 调用了的本方法,这边的this就是发起调用的proxyFactory对象,而proxyFactory对象中包含了要执行的的拦截器
return new JdkDynamicAopProxy(config);
}
// Cglib代理
return new ObjenesisCglibAopProxy(config);
}
else {
// JDK动态代理
return new JdkDynamicAopProxy(config);
}
}
設定がないので getProxy メソッドを振り返るのは簡単なので、ここでは jdk プロキシを使用します。
classLoader、インターフェース、InvocationHandler実装クラスを通じてプロキシオブジェクトを取得します。
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
// 1.拿到要被代理对象的所有接口
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
// 2.通过classLoader、接口、InvocationHandler实现类,来获取到代理对象
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
まとめ
以上でプロキシ生成の内容は終わりですが、メインはプロキシとしてcglibを使うかjdkを使うか、そしてアドバイザーの分析です。
要約する
今回は aop の概念を理解するためのものです。もちろん、私自身もインターネット上で情報を探しているので、もっと多くの情報を見つける必要があります。ここが重要ではありません。重要なのは、作成と実行の説明です。 AOP エージェントのタイミング。後で、リクエストの完全なプロセスを分析します。
付録 Spring ソースコード分析シリーズの記事
IOC
時間 | 記事 |
---|---|
2022-03-09 | Spring と IOC プロセスの基本概念の簡単な説明 |
2022-03-11 | IOCプロセス分析 - BeanFactoryの作成 |
2022-03-14 | IOC プロセス分析 - BeanFactoyPostProcessor および BeanPostProcessor |
2022-03-15 | IOC プロセス分析 - インスタンス化と初期化 |
2022-03-17 | IOC プロセス分析 - 循環依存関係 |
AOP
時間 | 記事 |
---|---|
2022-03-19 | Spring と IOC プロセスの基本概念の簡単な説明 |
2022-03-20 | AOP プロセスのソース コード分析 - プロセス全体の呼び出しを要求します |