[spring6: @EnableLoadTimeWeaving]-使用案例

源码

EnableLoadTimeWeaving

@EnableLoadTimeWeaving 注解用于启用加载时织入(LTW),可通过配置 aspectjWeaving 属性控制是否启用或自动探测 AspectJ 织入。

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LoadTimeWeavingConfiguration.class)
public @interface EnableLoadTimeWeaving {

	AspectJWeaving aspectjWeaving() default AspectJWeaving.AUTODETECT;
	
	enum AspectJWeaving {
		ENABLED, DISABLED, AUTODETECT
	}
}

LoadTimeWeavingConfiguration

LoadTimeWeavingConfiguration 根据 @EnableLoadTimeWeaving 注解配置加载时织入环境,提供 LoadTimeWeaver Bean 并根据策略启用 AspectJ 织入。

java 复制代码
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class LoadTimeWeavingConfiguration implements ImportAware, BeanClassLoaderAware {

	@Nullable
	private AnnotationAttributes enableLTW;

	@Nullable
	private LoadTimeWeavingConfigurer ltwConfigurer;

	@Nullable
	private ClassLoader beanClassLoader;


	@Override
	public void setImportMetadata(AnnotationMetadata importMetadata) {
		this.enableLTW = AnnotationConfigUtils.attributesFor(importMetadata, EnableLoadTimeWeaving.class);
		if (this.enableLTW == null) {
			throw new IllegalArgumentException(
					"@EnableLoadTimeWeaving is not present on importing class " + importMetadata.getClassName());
		}
	}

	@Autowired(required = false)
	public void setLoadTimeWeavingConfigurer(LoadTimeWeavingConfigurer ltwConfigurer) {
		this.ltwConfigurer = ltwConfigurer;
	}

	@Override
	public void setBeanClassLoader(ClassLoader beanClassLoader) {
		this.beanClassLoader = beanClassLoader;
	}


	@Bean(name = ConfigurableApplicationContext.LOAD_TIME_WEAVER_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public LoadTimeWeaver loadTimeWeaver() {
		Assert.state(this.beanClassLoader != null, "No ClassLoader set");
		LoadTimeWeaver loadTimeWeaver = null;

		if (this.ltwConfigurer != null) {
			// The user has provided a custom LoadTimeWeaver instance
			loadTimeWeaver = this.ltwConfigurer.getLoadTimeWeaver();
		}

		if (loadTimeWeaver == null) {
			// No custom LoadTimeWeaver provided -> fall back to the default
			loadTimeWeaver = new DefaultContextLoadTimeWeaver(this.beanClassLoader);
		}

		if (this.enableLTW != null) {
			AspectJWeaving aspectJWeaving = this.enableLTW.getEnum("aspectjWeaving");
			switch (aspectJWeaving) {
				case DISABLED -> {
					// AJ weaving is disabled -> do nothing
				}
				case AUTODETECT -> {
					if (this.beanClassLoader.getResource(AspectJWeavingEnabler.ASPECTJ_AOP_XML_RESOURCE) == null) {
						// No aop.xml present on the classpath -> treat as 'disabled'
						break;
					}
					// aop.xml is present on the classpath -> enable
					AspectJWeavingEnabler.enableAspectJWeaving(loadTimeWeaver, this.beanClassLoader);
				}
				case ENABLED -> {
					AspectJWeavingEnabler.enableAspectJWeaving(loadTimeWeaver, this.beanClassLoader);
				}
			}
		}

		return loadTimeWeaver;
	}
}

重点:AspectJWeavingEnabler

AspectJWeavingEnabler 是一个 BeanFactoryPostProcessor,用于在容器启动时注册 AspectJ 的类文件转换器以启用加载时织入,并屏蔽对自身(org.aspectj)类的处理。

java 复制代码
public class AspectJWeavingEnabler implements BeanFactoryPostProcessor, BeanClassLoaderAware, LoadTimeWeaverAware, Ordered {

	public static final String ASPECTJ_AOP_XML_RESOURCE = "META-INF/aop.xml";

	@Nullable
	private ClassLoader beanClassLoader;

	@Nullable
	private LoadTimeWeaver loadTimeWeaver;

	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		this.beanClassLoader = classLoader;
	}

	@Override
	public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
		this.loadTimeWeaver = loadTimeWeaver;
	}

	@Override
	public int getOrder() {
		return HIGHEST_PRECEDENCE;
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		enableAspectJWeaving(this.loadTimeWeaver, this.beanClassLoader);
	}

	public static void enableAspectJWeaving(@Nullable LoadTimeWeaver weaverToUse, @Nullable ClassLoader beanClassLoader) {
		if (weaverToUse == null) {
			if (InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) {
				// 说明 JVM 是通过 -javaagent:spring-instrument.jar 启动的
				weaverToUse = new InstrumentationLoadTimeWeaver(beanClassLoader);
			}
			else {
				throw new IllegalStateException("No LoadTimeWeaver available");
			}
		}
		
		weaverToUse.addTransformer(
			// AspectJClassBypassingClassFileTransformer 是一个包装器
			// 跳过 org.aspectj 自身的类避免递归织入
			new AspectJClassBypassingClassFileTransformer(
				// ClassPreProcessorAgentAdapter 是用于将 AspectJ 的 ClassPreProcessor 接入 Java 1.5 的 ClassFileTransformer 接口,
				// 实现类加载时的字节码织入,并支持类的热更新(reweaving)。
				new ClassPreProcessorAgentAdapter()
			)
		);
	}

	private static class AspectJClassBypassingClassFileTransformer implements ClassFileTransformer {

		private final ClassFileTransformer delegate;

		public AspectJClassBypassingClassFileTransformer(ClassFileTransformer delegate) {
			this.delegate = delegate;
		}

		@Override
		public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
				ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

			if (className.startsWith("org.aspectj") || className.startsWith("org/aspectj")) {
				return classfileBuffer;
			}
			return this.delegate.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
		}
	}
}

LoadTimeWeavingConfigurer

LoadTimeWeavingConfigurer 是一个用于提供自定义 LoadTimeWeaver 实例的回调接口。

java 复制代码
public interface LoadTimeWeavingConfigurer {

	LoadTimeWeaver getLoadTimeWeaver();
}

LoadTimeWeaver

LoadTimeWeaver 接口定义了在类加载时添加字节码转换器的能力,并提供可用于织入操作的类加载器。

java 复制代码
public interface LoadTimeWeaver {

	void addTransformer(ClassFileTransformer transformer);

	ClassLoader getInstrumentableClassLoader();

	ClassLoader getThrowawayClassLoader();
}

1. DefaultContextLoadTimeWeaver

DefaultContextLoadTimeWeaver 是 Spring 默认的加载时织入适配器,会根据当前环境(Tomcat、GlassFish、JBoss 或是否存在 spring-instrument 的 JVM agent)自动选择合适的 LoadTimeWeaver 实现,用于支持 AspectJ 等织入机制。

java 复制代码
public class DefaultContextLoadTimeWeaver implements LoadTimeWeaver, BeanClassLoaderAware, DisposableBean {

	protected final Log logger = LogFactory.getLog(getClass());

	@Nullable
	private LoadTimeWeaver loadTimeWeaver;


	public DefaultContextLoadTimeWeaver() {
	}

	public DefaultContextLoadTimeWeaver(ClassLoader beanClassLoader) {
		setBeanClassLoader(beanClassLoader);
	}


	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		LoadTimeWeaver serverSpecificLoadTimeWeaver = createServerSpecificLoadTimeWeaver(classLoader);
		if (serverSpecificLoadTimeWeaver != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Determined server-specific load-time weaver: " +
						serverSpecificLoadTimeWeaver.getClass().getName());
			}
			this.loadTimeWeaver = serverSpecificLoadTimeWeaver;
		}
		else if (InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) {
			logger.debug("Found Spring's JVM agent for instrumentation");
			this.loadTimeWeaver = new InstrumentationLoadTimeWeaver(classLoader);
		}
		else {
			try {
				this.loadTimeWeaver = new ReflectiveLoadTimeWeaver(classLoader);
				if (logger.isDebugEnabled()) {
					logger.debug("Using reflective load-time weaver for class loader: " +
							this.loadTimeWeaver.getInstrumentableClassLoader().getClass().getName());
				}
			}
			catch (IllegalStateException ex) {
				throw new IllegalStateException(ex.getMessage() + " Specify a custom LoadTimeWeaver or start your " +
						"Java virtual machine with Spring's agent: -javaagent:spring-instrument-{version}.jar");
			}
		}
	}

	@Nullable
	protected LoadTimeWeaver createServerSpecificLoadTimeWeaver(ClassLoader classLoader) {
		String name = classLoader.getClass().getName();
		try {
			if (name.startsWith("org.apache.catalina")) {
				return new TomcatLoadTimeWeaver(classLoader);
			}
			else if (name.startsWith("org.glassfish")) {
				return new GlassFishLoadTimeWeaver(classLoader);
			}
			else if (name.startsWith("org.jboss.modules")) {
				return new JBossLoadTimeWeaver(classLoader);
			}
		}
		catch (Exception ex) {
			if (logger.isInfoEnabled()) {
				logger.info("Could not obtain server-specific LoadTimeWeaver: " + ex.getMessage());
			}
		}
		return null;
	}

	@Override
	public void destroy() {
		if (this.loadTimeWeaver instanceof InstrumentationLoadTimeWeaver iltw) {
			if (logger.isDebugEnabled()) {
				logger.debug("Removing all registered transformers for class loader: " +
						this.loadTimeWeaver.getInstrumentableClassLoader().getClass().getName());
			}
			iltw.removeTransformers();
		}
	}


	@Override
	public void addTransformer(ClassFileTransformer transformer) {
		Assert.state(this.loadTimeWeaver != null, "Not initialized");
		this.loadTimeWeaver.addTransformer(transformer);
	}

	@Override
	public ClassLoader getInstrumentableClassLoader() {
		Assert.state(this.loadTimeWeaver != null, "Not initialized");
		return this.loadTimeWeaver.getInstrumentableClassLoader();
	}

	@Override
	public ClassLoader getThrowawayClassLoader() {
		Assert.state(this.loadTimeWeaver != null, "Not initialized");
		return this.loadTimeWeaver.getThrowawayClassLoader();
	}

}

2. InstrumentationLoadTimeWeaver

InstrumentationLoadTimeWeaver 是基于 JVM Instrumentation 的加载时织入器(LoadTimeWeaver),用于在运行时向类加载器注册 ClassFileTransformer,从而支持 AOP、JPA 等框架进行字节码增强。必须使用 -javaagent:<path>/spring-instrument-{version}.jar 启动 JVM,否则 Instrumentation 不可用。

java 复制代码
public class InstrumentationLoadTimeWeaver implements LoadTimeWeaver {

	private static final boolean AGENT_CLASS_PRESENT = ClassUtils.isPresent(
			"org.springframework.instrument.InstrumentationSavingAgent",
			InstrumentationLoadTimeWeaver.class.getClassLoader());

	@Nullable
	private final ClassLoader classLoader;

	@Nullable
	private final Instrumentation instrumentation;

	private final List<ClassFileTransformer> transformers = new ArrayList<>(4);
	
	public InstrumentationLoadTimeWeaver() {
		this(ClassUtils.getDefaultClassLoader());
	}

	public InstrumentationLoadTimeWeaver(@Nullable ClassLoader classLoader) {
		this.classLoader = classLoader;
		this.instrumentation = getInstrumentation();
	}
	
	@Override
	public void addTransformer(ClassFileTransformer transformer) {
		Assert.notNull(transformer, "Transformer must not be null");
		FilteringClassFileTransformer actualTransformer =
				new FilteringClassFileTransformer(transformer, this.classLoader);
		synchronized (this.transformers) {
			Assert.state(this.instrumentation != null,
					"Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.");
			this.instrumentation.addTransformer(actualTransformer);
			this.transformers.add(actualTransformer);
		}
	}

	@Override
	public ClassLoader getInstrumentableClassLoader() {
		Assert.state(this.classLoader != null, "No ClassLoader available");
		return this.classLoader;
	}

	@Override
	public ClassLoader getThrowawayClassLoader() {
		return new SimpleThrowawayClassLoader(getInstrumentableClassLoader());
	}

	public void removeTransformers() {
		synchronized (this.transformers) {
			if (this.instrumentation != null && !this.transformers.isEmpty()) {
				for (int i = this.transformers.size() - 1; i >= 0; i--) {
					this.instrumentation.removeTransformer(this.transformers.get(i));
				}
				this.transformers.clear();
			}
		}
	}

	public static boolean isInstrumentationAvailable() {
		return (getInstrumentation() != null);
	}

	@Nullable
	private static Instrumentation getInstrumentation() {
		if (AGENT_CLASS_PRESENT) {
			return InstrumentationAccessor.getInstrumentation();
		}
		else {
			return null;
		}
	}

	private static class InstrumentationAccessor {
		public static Instrumentation getInstrumentation() {
			return InstrumentationSavingAgent.getInstrumentation();
		}
	}

	private static class FilteringClassFileTransformer implements ClassFileTransformer {

		private final ClassFileTransformer targetTransformer;

		@Nullable
		private final ClassLoader targetClassLoader;

		public FilteringClassFileTransformer(
				ClassFileTransformer targetTransformer, @Nullable ClassLoader targetClassLoader) {

			this.targetTransformer = targetTransformer;
			this.targetClassLoader = targetClassLoader;
		}

		@Override
		@Nullable
		public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
				ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

			if (this.targetClassLoader != loader) {
				return null;
			}
			return this.targetTransformer.transform(
					loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
		}

		@Override
		public String toString() {
			return "FilteringClassFileTransformer for: " + this.targetTransformer.toString();
		}
	}

}

3. ReflectiveLoadTimeWeaver

ReflectiveLoadTimeWeaver 使用反射调用 ClassLoader 提供的 addTransformer() 和可选的 getThrowawayClassLoader() 方法,实现类加载时的字节码增强功能,适用于底层类加载器不可直接访问或跨类加载器场景。

java 复制代码
public class ReflectiveLoadTimeWeaver implements LoadTimeWeaver {

	private static final String ADD_TRANSFORMER_METHOD_NAME = "addTransformer";

	private static final String GET_THROWAWAY_CLASS_LOADER_METHOD_NAME = "getThrowawayClassLoader";

	private static final Log logger = LogFactory.getLog(ReflectiveLoadTimeWeaver.class);

	private final ClassLoader classLoader;

	private final Method addTransformerMethod;

	@Nullable
	private final Method getThrowawayClassLoaderMethod;

	public ReflectiveLoadTimeWeaver() {
		this(ClassUtils.getDefaultClassLoader());
	}

	public ReflectiveLoadTimeWeaver(@Nullable ClassLoader classLoader) {
		Assert.notNull(classLoader, "ClassLoader must not be null");
		this.classLoader = classLoader;

		Method addTransformerMethod = ClassUtils.getMethodIfAvailable(
				this.classLoader.getClass(), ADD_TRANSFORMER_METHOD_NAME, ClassFileTransformer.class);
		if (addTransformerMethod == null) {
			throw new IllegalStateException(
					"ClassLoader [" + classLoader.getClass().getName() + "] does NOT provide an " +
					"'addTransformer(ClassFileTransformer)' method.");
		}
		this.addTransformerMethod = addTransformerMethod;

		Method getThrowawayClassLoaderMethod = ClassUtils.getMethodIfAvailable(
				this.classLoader.getClass(), GET_THROWAWAY_CLASS_LOADER_METHOD_NAME);
		// getThrowawayClassLoader method is optional
		if (getThrowawayClassLoaderMethod == null) {
			if (logger.isDebugEnabled()) {
				logger.debug("The ClassLoader [" + classLoader.getClass().getName() + "] does NOT provide a " +
						"'getThrowawayClassLoader()' method; SimpleThrowawayClassLoader will be used instead.");
			}
		}
		this.getThrowawayClassLoaderMethod = getThrowawayClassLoaderMethod;
	}

	@Override
	public void addTransformer(ClassFileTransformer transformer) {
		Assert.notNull(transformer, "Transformer must not be null");
		ReflectionUtils.invokeMethod(this.addTransformerMethod, this.classLoader, transformer);
	}

	@Override
	public ClassLoader getInstrumentableClassLoader() {
		return this.classLoader;
	}

	@Override
	public ClassLoader getThrowawayClassLoader() {
		if (this.getThrowawayClassLoaderMethod != null) {
			ClassLoader target = (ClassLoader)
					ReflectionUtils.invokeMethod(this.getThrowawayClassLoaderMethod, this.classLoader);
			return (target instanceof DecoratingClassLoader ? target :
					new OverridingClassLoader(this.classLoader, target));
		}
		else {
			return new SimpleThrowawayClassLoader(this.classLoader);
		}
	}
}

例子

配置

pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.5.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>xyz.idoly</groupId>
	<artifactId>ltw-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>ltw-demo</name>

	<properties>
		<java.version>24</java.version>
	</properties>
	
	<dependencies>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.9.24</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-instrument</artifactId>
		</dependency> 

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>dev.aspectj</groupId>
				<artifactId>aspectj-maven-plugin</artifactId>
				<version>1.14.1</version>
				<dependencies>
                    <dependency>
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjtools</artifactId>
						<version>1.9.24</version>
                    </dependency>
                </dependencies>
				<configuration>
					<complianceLevel>24</complianceLevel>
				</configuration>
				<executions>
					<execution>
						<goals>
							<goal>compile</goal>
							<goal>test-compile</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

resources\META-INF\aop.xml

xml 复制代码
<aspectj>
    <weaver>
        <include within="xyz.idoly.demo.entity.*"/>
    </weaver>
    <aspects>
        <aspect name="xyz.idoly.demo.aspectj.CustomAspect"/>
    </aspects>
</aspectj>

代码

CustomEntity

java 复制代码
package xyz.idoly.demo.entity;

import org.springframework.stereotype.Component;

@Component
public class CustomEntity {

    public void customMethod() {
        System.out.println("customMethod");
    }    
}

CustomAspect

java 复制代码
package xyz.idoly.demo.aspectj;

import org.aspectj.lang.ProceedingJoinPoint;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public aspect CustomAspect{

    private static final Log log = LogFactory.getLog(CustomAspect.class);

    pointcut customMethodExecution(): execution(void xyz.idoly.demo.entity.CustomEntity.customMethod());

    Object around(): customMethodExecution() {
        String methodName = ((ProceedingJoinPoint)thisJoinPoint).getSignature().toShortString();
        Object result = null;

        log.info("AJ Aspect: Around customMethod - Before execution of " + methodName);

        try {
            result = proceed();
            log.info("AJ Aspect: Around customMethod - After successful execution of " + methodName);
        } catch (Throwable e) {
            log.error("AJ Aspect: Around customMethod - Exception in " + methodName + " with error: " + e.getMessage());
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            } else {
                throw new RuntimeException("AspectJ caught checked exception in " + methodName, e); 
            }
        }

        return result;
    }
}

Application

java 复制代码
package xyz.idoly.demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.EnableLoadTimeWeaving;

import xyz.idoly.demo.entity.CustomEntity;

@EnableLoadTimeWeaving
@SpringBootApplication
public class Application {

    @Bean
    public CommandLineRunner commandLineRunner(CustomEntity customEntity) {
        return args -> customEntity.customMethod();
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

结果

powershell 复制代码
PS D:\workspace\java\ltw-demo> mvnd clean package
PS D:\workspace\java\ltw-demo> java -javaagent:.\spring-instrument-6.2.8.jar  -jar .\target\ltw-demo-0.0.1-SNAPSHOT.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.3)

2025-07-11T23:33:19.886+08:00  INFO 15172 --- [ltw-demo] [           main] xyz.idoly.demo.Application               : Starting Application v0.0.1-SNAPSHOT using Java 24.0.1 with PID 15172 (D:\workspace\java\redis-demo\target\ltw-demo-0.0.1-SNAPSHOT.jar started by idoly in D:\workspace\java\redis-demo)
2025-07-11T23:33:19.888+08:00  INFO 15172 --- [ltw-demo] [           main] xyz.idoly.demo.Application               : No active profile set, falling back to 1 default profile: "default"
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by org.aspectj.weaver.loadtime.ClassLoaderWeavingAdaptor (jar:nested:/D:/workspace/java/ltw-demo/target/ltw-demo-0.0.1-SNAPSHOT.jar/!BOOT-INF/lib/aspectjweaver-1.9.24.jar!/)
WARNING: Please consider reporting this to the maintainers of class org.aspectj.weaver.loadtime.ClassLoaderWeavingAdaptor
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
2025-07-11T23:33:20.512+08:00  INFO 15172 --- [ltw-demo] [           main] xyz.idoly.demo.Application               : Started Application in 1.001 seconds (process running for 1.322)
2025-07-11T23:33:20.517+08:00  INFO 15172 --- [ltw-demo] [           main] xyz.idoly.demo.aspectj.CustomAspect      : AJ Aspect: Around customMethod - Before execution of customMethod
customMethod
2025-07-11T23:33:20.518+08:00  INFO 15172 --- [ltw-demo] [           main] xyz.idoly.demo.aspectj.CustomAspect      : AJ Aspect: Around customMethod - After successful execution of customMethod