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

  1. Spring boot reads @ EnableAspectJAutoProxy annotations on @ Import (AspectJAutoProxyRegistrar. Class) and AspectJAutoProxyRegistrar parsed into bean definition

  2. 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
  3. 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

  1. Spring Boot is implemented by default via the spring SPI mechanismAopAutoConfigurationRegistered toBeanDefinitionRegistry
  2. Spring Boot resolvesAopAutoConfigurationOn the inner class@EnableAspectJAutoProxy
  3. 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

  1. Spring Boot 2.2.0-RELEASE source code

The first time to write an article, if there is any wrong to write welcome everyone to correct