Order :%d indicates the order in which the code is read
1. A simple example takes you through a different approach to Spring AOP
Background: There is a requirement to annotate method execution times
1.1 Custom annotations
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @Interface TimeRecord{
}
Copy the code
1.2 define Advice
public class TimeRecordMethodInterceptor implements MethodInterceptor{
public Object invoke(MethodInvocation invocation) throws Throwable{
// Get the start time
long startTime = System.currentTimeMillis()
invocation.proceed();
// Get the end time
long endTime = System.currentTimeMillis()
System.out.println(String.format("Method %s execution time %d milliseconds" , invocation.getMethod().getName , endTime - startTime ))
}
}
Copy the code
1.3 define Pointcut
public TimeRecordPointcut implements Pointcut{
@Override
public ClassFilter getClassFilter(a) {
// Return a permanent class matcher
return ClassFilter.TRUE;
}
@Override
public MethodMatcher getMethodMatcher(a) {
return new MethodMatcher() {
@Override
public boolean matches(Method method, Class
targetClass) {
// Intercepts when a method has @timerecord annotations on it
return method.isAnnotationPresent(TimeRecord.class);
}
@Override
public boolean isRuntime(a) {
return false;
}
@Override
public boolean matches(Method method, Class
targetClass, Object... args) {
return false; }}; }}Copy the code
1.4 define the Advisor
public class TimeRecordAdvisor implements PointcutAdvisor{
private TimeRecordMethodInterceptor advice = new TimeRecordMethodInterceptor();
private TimeRecordPointcut pointcut = new TimeRecordPointcut();
// Returns the matching criteria for the notification
@Override
public Pointcut getPointcut(a) {
return this.pointcut;
}
// Return notification
@Override
public Advice getAdvice(a) {
return this.advice ;
}
// Return false by default
@Override
public boolean isPerInstance(a) {
return false; }}Copy the code
1.5 registered Advisor
@Configuration
public class TimeRecordConfiguration{
@Bean
public PointcutAdvisor timeRecordAdvisor(a){
return newTimeRecordAdvisor(); }}Copy the code
1.6 the use of
public interface UserService{
// Get the user ID
long getUserId(a);
}
@Service
public class UserServiceImpl implements UserService{
@TimeRecord
public long getUserId(a){
try {
// Sleep for 1 second
Thread.sleep(1000);
}catch (Exception e){
throw newIllegalStateException(e); }}}@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest{
@Autowired
private UserService userService;
@Test
public void getUserId(a){ userService.getUserId(); }}Copy the code
2. Reintroduce yourself to the characters of Spring AOP
2.1 Advice
-
Notification, a place where intercepting code is executed, of which there are several types
The interface name instructions BeforeAdvice Pre-notification, used before the target method is executed AfterAdvice Post-notification, used after target method execution (including method return notification and method execution exception notification) MethodInterceptor The most special type of notification has all notification type capabilities -
MethodInterceptor(MethodInterceptor)
-
Execute the code
public MyMethodInterceptor implements MethodInterceptor{ Object invoke(MethodInvocation invocation) throws Throwable{ // Intercept operations.Execute the next notification in the notification chain, or target method invocation.proceed(); // Intercept operations. }}Copy the code
-
MethodInvocation (MethodInvocation)
- MethodInvocation#proceed: Executes the next notification in the notification chain, or the target method
- MethodInvocation#getThis: Gets the original object currently propped
- MethodInvocation#getArguments: Get a list of incoming values for the currently executing methods
- MethodInvocation#getMethod: Gets the object of the currently intercepted method
-
2.2 Pointcut(Pointcut, preconditions for notification execution)
-
Two important attribute types
type instructions ClassFilter Used for target class matching to determine whether the proxied target class meets the condition MethodMatcher It is used for target method matching to judge whether the proxied target method meets the conditions -
ClassFilter
public interface ClassFilter { ... Matches (class <?) matches(class <?) matches(class <? > clazz); . }Copy the code
-
MethodMatcher
public interface MethodMatcher { // Matches whether the proxied target method meets the condition boolean matches(Method method, Class targetClass); Matches (Method, Class , Object... ) Method takes effect. False is returned by default boolean isRuntime(a); // Match according to the state of run-time method execution, where args parameter is the list of input parameters when the propped target method executes boolean matches(Method method, Class targetClass, Object... args); } Copy the code
2.3 Advisor(notifier, used to provide Advice)
-
Provides the ability to get Advice
-
Code instructions
public interface Advisor {...// Get the notification object (can be pre, post, surround, exception post, and so on) Advice getAdvice(a); . }Copy the code
2.4 IntroductionAdvisor
-
Have Avisor ability
-
And added preconditions for getting Advice
-
Code instructions
public interface IntroductionAdvisor extends Advisor.IntroductionInfo { ClassFilter getClassFilter(a); } Copy the code
2.5 PointcutAdvisor
-
Have Avisor ability
-
And added preconditions for getting Advice
-
Code instructions
public interface PointcutAdvisor extends Advisor { // Get the preconditions Pointcut getPointcut(a); } Copy the code
2.6 Differences between IntroductionAdvisor and PointcutAdvisor
The name of the interface | Target method matching | Target method runtime match | Target class matching |
---|---|---|---|
PointcutAdvisor | support | support | support |
IntroductionAdvisor | Does not support | Does not support | support |
How does Spring Boot activate Spring AOP?
3.1 Activation Method 1: @enableAspectJAutoProxy
-
Spring boot reads @ EnableAspectJAutoProxy annotations on @ Import (AspectJAutoProxyRegistrar. Class) and AspectJAutoProxyRegistrar parsed into bean definition
-
AspectJAutoProxyRegistrar AnnotationAwareAspectJAutoProxyCreator registered to BeanDefinitionRegistry
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {...// Register to create an AOP creatorAopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); . }Copy the code
-
Performs AnnotationAwareAspectJAutoProxyCreator in spring bean lifecycle callback methods to match and the creation of aop proxy objects, the process to click for details
3.2 Activation Mode 2: AopAutoConfiguration
- Spring Boot is implemented by default via the spring SPI mechanism
AopAutoConfiguration
Registered toBeanDefinitionRegistry
- Spring Boot resolves
AopAutoConfiguration
On the inner class@EnableAspectJAutoProxy
- The process of executing method one
4. How does Spring AOP create proxy objects?
4.1 automatic aop proxy object creator (subclass AbstractAutoProxyCreator)
The name of the class | instructions |
---|---|
InfrastructureAdvisorAutoProxyCreator | Generate proxy objects by matching the list of Advisors to beans that meet the criteria |
AnnotationAwareAspectJAutoProxyCreator | Proxy objects are generated by matching the list of Advisors to beans that meet the criteria, and the case for registering notifications via @AspectJ is provided |
DefaultAdvisorAutoProxyCreator | Match qualified beans by filling the list of Advisors with the beanName prefix and generate proxy objects |
4.2 AbstractAutoProxyCreator which Lifecycle spring Bean has
- BeanFactoryAware: the BeanFactory callback injection, by default is injected DefaultListableBeanFactory instance
- Rear SmartInstantiationAwareBeanPostProcessor: bean processors
4.3 Creating proxy Objects
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor.BeanFactoryAware {
// The bean lifecycle callback method, which is used to instantiate objects ahead of time to resolve loop dependencies, is mainly used for methodFactory callbacks. See the IOC section on the Spring website for details
// order:1
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
// Determine whether the current bean meets the criteria to be used as an AOP proxy object, and create a proxy object if so
return wrapIfNecessary(bean, beanName, cacheKey);
}
// Bean lifecycle callback method, instantiate the leading callback
//order:2
@Override
public Object postProcessBeforeInstantiation(Class
beanClass, String beanName) {
Object cacheKey = getCacheKey(beanClass, beanName);
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
// If TargetSource is specified manually, the proxy object is created using TargetSource. Otherwise, nothing is done. By default, TargetSource is null
if(targetSource ! =null) {... }return null;
}
// Initialize the post-operation of the initialization
//order:3
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if(bean ! =null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// The getEarlyBeanReference method put earlyProxyReferences, so the condition is generally valid
if (this.earlyProxyReferences.remove(cacheKey) ! = bean) {// Determine whether the current bean meets the criteria to be used as an AOP proxy object, and if so, create a proxy object and return a substitute for the original bean object
returnwrapIfNecessary(bean, beanName, cacheKey); }}return bean;
}
//order:4
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// If the match is successful, the proxy object is created. The matching logic is explained in the next chapter
if(specificInterceptors ! = DO_NOT_PROXY) { ...// Create a proxy object for the current bean
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, newSingletonTargetSource(bean)); .return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
//order:5
protected Object createProxy(Class<? > beanClass,@Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {...// The proxy object creates the factory, and the AOP core process code is in this class, which we will discuss later
ProxyFactory proxyFactory = new ProxyFactory();
// Copy the configuration of the current object (ProxyConfig) to ProxyFactory
proxyFactory.copyFrom(this);
//
if(! proxyFactory.isProxyTargetClass()) {if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else{ evaluateProxyInterfaces(beanClass, proxyFactory); }}// Build a list of advisors
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
// Set the Advisor list to the ProxyFactory Advisor, as described in a later section
proxyFactory.addAdvisors(advisors);
// Set the target object, that is, the proxied original object
proxyFactory.setTargetSource(targetSource);
// A proxy factory extension entry, not implemented by default
customizeProxyFactory(proxyFactory);
// Sets whether elements in the Advisor list are allowed to be deleted
proxyFactory.setFrozen(this.freezeProxy); .// Create a proxy object
returnproxyFactory.getProxy(getProxyClassLoader()); }}Copy the code
4.4 Match beans that satisfy the requirement to generate AOP proxy objects
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor.BeanFactoryAware {
// order:1
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// Match the Advisor list by bean
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
// Create an AOP proxy object if the bean matches the Advisor list, otherwise return the original bean object
if(specificInterceptors ! = DO_NOT_PROXY) { ... }this.advisedBeans.put(cacheKey, Boolean.FALSE);
returnbean; }}public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {
// order:2
protected List<Advisor> findEligibleAdvisors(Class
beanClass, String beanName) {
Get a list of beans of type Advisor from the Spring IOC container
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// Match the Advisor list with the beanClass to filter out the list of advisors that meet the criteria
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if(! eligibleAdvisors.isEmpty()) { eligibleAdvisors = sortAdvisors(eligibleAdvisors); }returneligibleAdvisors; }}public abstract class AopUtils {
// order:3
public static List<Advisor> findAdvisorsThatCanApply(List
candidateAdvisors, Class
clazz)
{
// Match Advisor in the list of IntroductionAdvisor types that meet the requirements
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceofIntroductionAdvisor && canApply(candidate, clazz)) { eligibleAdvisors.add(candidate); }}booleanhasIntroductions = ! eligibleAdvisors.isEmpty();// Match advisors in the Advisor list that are of type PointcutAdvisor and meet the criteria
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
if(canApply(candidate, clazz, hasIntroductions)) { eligibleAdvisors.add(candidate); }}return eligibleAdvisors;
}
// order:4
public static boolean canApply(Advisor advisor, Class<? > targetClass,boolean hasIntroductions) {
// In the case of the IntroductionAdvisor class, only match the target class
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
// Match the target class with the target aspect if PointcutAdvisor
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
// If neither IntroductionAdvisor nor PointcutAdvisor returns a matching pass
else {
return true; }}// order:5
public static boolean canApply(Pointcut pc, Class<? > targetClass,boolean hasIntroductions) {
// Perform ClassFilter#matches, returning false if the match fails
if(! pc.getClassFilter().matches(targetClass)) {return false;
}
// getMethodMatcher in PointcutAdvisor#getPointcut#getMethodMatcher
MethodMatcher methodMatcher = pc.getMethodMatcher();
Return true if the method matcher is the default matcher
if (methodMatcher == MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...
return true; }...for(Class<? > clazz : classes) {// Get all public methods of the target class
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
/ / to iterate through all methods If the methodMatcher for IntroductionAwareMethodMatcher type the call
/ / IntroductionAwareMethodMatcher# matches match, otherwise the call
Returns true when either method matches successfully
for (Method method : methods) {
if(introductionAwareMethodMatcher ! =null ?
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
methodMatcher.matches(method, targetClass)) {
return true; }}}return false; }}Copy the code
5. Is spring AOP implementation flow what you think it is?
Take JDK dynamic proxy implementation as an example
final class JdkDynamicAopProxy implements AopProxy.InvocationHandler.Serializable {
//order:1
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {...// Set the current proxy object to AopContext. Using AopContext#getCurrentProxy# XXX to call the method in the same class can avoid Aop invalidation in this case, but exposeProxy must be enabled first. You can enable this function using ProxyConfig
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get notifications that meet the criteria by matching the target class and target method
// 1. Use ClassFilter to match
// 2. Match with MethodMatcher
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// If no notification matches, the target method is called directly
if (chain.isEmpty()) {
...
}
// If a notification is matched
else {
// Build the method caller
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// make the notification chain callretVal = invocation.proceed(); }...return retVal;
}
public class ReflectiveMethodInvocation implements ProxyMethodInvocation.Cloneable {
//order:2
public Object proceed(a) throws Throwable {
// If the number of offsets executed by the notification chain is equal to the maximum offsets of the notification chain, the notification chain is complete, and the target method Method#invoke is executed
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// Get a notification of the current execution
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// A notification to determine whether a dynamic method matches. We said above that when methodmate #isRuntime is true, dynamic matching at runtime meets the condition for advice
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
...
// Dynamic matching
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
/ / execution MethodInterceptor# invoke
return dm.interceptor.invoke(this);
}
// If the dynamic match fails, the next notification in the notification chain is executed
else {
returnproceed(); }}MethodInterceptor#invoke is called directly if dynamic method matching is not required
else {
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); }}}Copy the code
reference
- Spring Boot 2.2.0-RELEASE source code