preface

The concept of AOP is aspect oriented programming. In contrast to traditional oop, AOP focuses more on horizontal logic, such as logging, exception handling, performance monitoring, and so on in a large system. That means these operations will be scattered around the system, unmanageable and messy. Aop focuses on these. Aop separates these operations from business code and unites them into facets for which it can program. Spring AOP makes our AOP development work easier, this time I will tell you about the underlying principles and implementation of Spring AOP

use

To analyze the underlying principles of Spring AOP, you need to first use it. Create a normal Maven WebApp project with spring-Context dependencies, version 5.1.1RELEASE

<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> < version > 5.1.1. RELEASE < / version > < / dependency >Copy the code

I then used AspectJ as a syntactic implementation of AOP, integrating it with Spring

< the dependency > < groupId > org. Aspectj < / groupId > < artifactId > aspectjweaver < / artifactId > < version > 1.9.4 < / version > </dependency>Copy the code

Next I’ll simulate the use of Spring AOP in full form of annotations, creating a configuration class to scan packages and turning on aspectJ’s automatic proxy support

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.ww")
public class Wconfig {

}
Copy the code

Then create a new interface and its implementation class

public interface Dao {
    void query();
}

@Component
public class IndexDao implements Dao{

    @Override
    public void query() {
        System.out.println("query......");
    }
}
Copy the code

Create a plane

@component public class WAspect {/** * execution expression, Writing can baidu * / the @pointcut (" execution (* com. Ww. Dao. *. * (..) )") public void point(){} ** * Before("point()") public void beforeAd(){*/ @before ("point()") public void beforeAd(){ System.out.println("before-------------"); }}Copy the code

Create test methods

public class TestAspect { public static void main(String[] args) { AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(Wconfig.class); Dao dao = configApplicationContext.getBean(Dao.class); dao.query(); }}Copy the code

Execute the method and you can see the print query… I printed before———-

If you know a little about Spring AOP, you will know that spring implements aop using dynamic proxies and bytecode techniques, namely JDK dynamic proxies and Cglib dynamic proxies. How does Spring create the proxy object? When does spring create the proxy object? Let’s explore the source code to uncover the mystery

Source code analysis

First, let’s look beyond the appearance to the essence. I’ll put the breakpoint on the last line of the test method. Let’s look at the DAO object at this point

Then we will go to find when the dao object into a dynamic proxy objects, since at the time of the last line of object has become a proxy objects, so we guess is naturally in a code position on the spring performed to create a proxy object operation, we move the breakpoint to a line, debug it

Go down again

This line of code I see method name should be useful code, method meaning should be spring processing bean, follow in to see

@Nullable private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, Boolean nonUniqueAsNull) {// This is the most useful line of code for beanName beans. NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull); if (namedBean ! = null) { return namedBean.getBeanInstance(); } BeanFactory parent = getParentBeanFactory(); if (parent instanceof DefaultListableBeanFactory) { return ((DefaultListableBeanFactory) parent).resolveBean(requiredType, args, nonUniqueAsNull); } else if (parent ! = null) { ObjectProvider<T> parentProvider = parent.getBeanProvider(requiredType); if (args ! = null) { return parentProvider.getObject(args); } else { return (nonUniqueAsNull ? parentProvider.getIfUnique() : parentProvider.getIfAvailable()); } } return null; }Copy the code

@SuppressWarnings("unchecked") @Nullable private <T> NamedBeanHolder<T> resolveNamedBean( ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) throws BeansException { Assert.notNull(requiredType, "Required type must not be null"); Class<? > clazz = requiredType.getRawClass(); Assert.notNull(clazz, "Required type must have a raw Class"); String[] candidateNames = getBeanNamesForType(requiredType); String[] candidateNames = getBeanNamesForType(requiredType); if (candidateNames.length > 1) { List<String> autowireCandidates = new ArrayList<>(candidateNames.length); for (String beanName : candidateNames) { if (! containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) { autowireCandidates.add(beanName); } } if (! autowireCandidates.isEmpty()) { candidateNames = StringUtils.toStringArray(autowireCandidates); If (candidateNames. Length == 1) {String beanName = candidateNames[0]; return new NamedBeanHolder<>(beanName, (T) getBean(beanName, clazz, args)); } else if (candidateNames.length > 1) { Map<String, Object> candidates = new LinkedHashMap<>(candidateNames.length); for (String beanName : candidateNames) { if (containsSingleton(beanName) && args == null) { Object beanInstance = getBean(beanName); candidates.put(beanName, (beanInstance instanceof NullBean ? null : beanInstance)); } else { candidates.put(beanName, getType(beanName)); } } String candidateName = determinePrimaryCandidate(candidates, clazz); if (candidateName == null) { candidateName = determineHighestPriorityCandidate(candidates, clazz); } if (candidateName ! = null) { Object beanInstance = candidates.get(candidateName); if (beanInstance == null || beanInstance instanceof Class) { beanInstance = getBean(candidateName, clazz, args); } return new NamedBeanHolder<>(candidateName, (T) beanInstance); } if (! nonUniqueAsNull) { throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet()); } } return null; }Copy the code

After executing getBeanNamesForType(requiredType), the idea variable is displayed, and the name is IndexDao

The next thing I need to do is go to the code block where Length ==1, and then I debug it again, which is a getBean method

There are other beans in the Spring container that need to be created without a name, so I used a conditional breakpoint here. The breakpoint will only be entered when beanName equals indexDao, but when I finished running the line F8, something unexpected happened

Surprisingly, after the getSingleton line, the proxy object is created, so you need to debug into this line to see it

Protected Object getSingleton(String beanName, Boolean allowEarlyReference) {// Where all spring beans are placed in the IOC container, It's this singletonObjects, this is a concorrentHashMap. Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<? > singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory ! = null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }Copy the code

But I only see the get method here, so when did these beans go into singletonObjects

protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); }}Copy the code

In DefaultSingletonBeanRegistry registry, I found singletonObjects put method, this time on behalf of the bean is to be put into the map, I then conditional breakpoints in this line of code, and then we come to it call chain, Find out when the addSingleton method is executed. In fact, from this point I already know that the breakpoint on the penultimate line of the test method is not correct. The proxy object has already been generated before the getBean

If (mbd.issingleton ()) {sharedInstance = getSingleton(beanName, ()) -> {try { Return createBean(beanName, MBD, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }Copy the code

On the createBean method, I also add a conditional breakpoint, and debug enters

@Override protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { if (logger.isTraceEnabled()) { logger.trace("Creating instance of bean '" + beanName + "'"); } RootBeanDefinition mbdToUse = mbd; // Make sure bean class is actually resolved at this point, and // clone the bean definition in case of a dynamically resolved Class // which cannot be stored in the shared merged bean definition. Class<? > resolvedClass = resolveBeanClass(mbd, beanName); if (resolvedClass ! = null && ! mbd.hasBeanClass() && mbd.getBeanClassName() ! = null) { mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); } // Prepare method overrides. try { mbdToUse.prepareMethodOverrides(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), beanName, "Validation of method overrides failed", ex); } try { // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean ! = null) { return bean; } } catch (Throwable ex) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { // A previously detected exception with proper bean creation context already, // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry. throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); }}Copy the code

Next I debug into the doCreateBean method

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper.getWrappedInstance(); Class<? > beanType = instanceWrapper.getWrappedClass(); if (beanType ! = NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (! mbd.postProcessed) { try { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces  like BeanFactoryAware. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // exposedObject = bean; // populateBean(beanName, MBD, instanceWrapper). This method is also important, but not aop related. ExposedObject = initializeBean(beanName, exposedObject, MBD); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference ! = null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (! this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (! removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (! actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); } } } } // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }Copy the code

Debug follows the initializeBean method, the conditional breakpoint is on the two initializeBean methods, and I have a vague idea that the proxy object is generated from these methods. We’ll see

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { if (System.getSecurityManager() ! = null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { invokeAwareMethods(beanName, bean); return null; }, getAccessControlContext()); } else { invokeAwareMethods(beanName, bean); } // wrappedBean = bean; if (mbd == null || ! Mbd.issynthetic ()) {// Perform pre-initialization of the beanPostProcessor processor wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException( (mbd ! = null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } if (mbd == null || ! MBD. IsSynthetic ()) {/ / execution after initialization beanPostProcessor wrappedBean = applyBeanPostProcessorsAfterInitialization processor (wrappedBean,  beanName); } return wrappedBean; }Copy the code

After applyBeanPostProcessorsBeforeInitialization method, this time we see warppedBean or indexDao, did not produce a proxy object

I guess in the next post-processor, the proxy object will be created and I debug it

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : GetBeanPostProcessors ()) {// The bean processed by the processor, I first take a look at how many processors Object current = processor. PostProcessAfterInitialization (result, beanName); if (current == null) { return result; } result = current; } return result; }Copy the code

Seeing this processor, IT suddenly became clear to me that it should be the proxy object generated by the processing of this processor. I finished running this code to verify my conjecture

You can see that my guess is proven correct, and after running the post-processor, the proxy object is created. At this point we know where the proxy object comes from, but we still don’t know how the proxy object is created. At this point we need to debug to look inside the processor.

@Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean ! Object cacheKey = getCacheKey(bean.getClass(), beanName); if (! This. EarlyProxyReferences. The contains (cacheKey)) {/ / important method, need to check the debug in return wrapIfNecessary (bean, beanName cacheKey); } } return bean; }Copy the code

So I got inside the wrapIfNecessary method again

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } // Create proxy if we have advice. // Create proxy if we have advice. Notification type and so on the information of the Object [] specificInterceptors = getAdvicesAndAdvisorsForBean (bean. GetClass (), beanName, null); if (specificInterceptors ! = DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); // As the name implies, create a proxy, Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }Copy the code

protected Object createProxy(Class<? > beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) { AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); } ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); if (! proxyFactory.isProxyTargetClass()) { if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { evaluateProxyInterfaces(beanClass, proxyFactory); } } Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); proxyFactory.addAdvisors(advisors); proxyFactory.setTargetSource(targetSource); customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } // The important point here is that the proxy object is generated by this method: return proxyFactory.getProxy(getProxyClassLoader()); } public Object getProxy(@nullable ClassLoader ClassLoader) {return createAopProxy().getProxy(ClassLoader);  }Copy the code

protected final synchronized AopProxy createAopProxy() { if (! this.active) { activate(); Return getAopProxyFactory().createAopProxy(this); }Copy the code

We see there is an if statement, when the config isOptimize and isProxyTargetClass hasNoUserSuppliedProxyInterfaces three judgment conditions as long as there is a meet, The default value of the two Boolean variables in config is false, and the proxied object implements the interface. Therefore, Spring will use the JDK dynamic proxied form to implement dynamic proxied. We can also manually configure the config value in this case to have Spring select Cglib as the dynamic proxy implementation, as I’ll demonstrate later

@Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<? > targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else {return new JdkDynamicAopProxy(config); }}Copy the code

Let me show you how to modify config to let Spring choose Cglib as the dynamic proxy implementation with the interface

@enableAspectJAutoProxy (proxyTargetClass =true) @enableAspectJAutoProxy (proxyTargetClass =true)Copy the code

conclusion

I conclude with a call chain diagram for the Spring AOP implementation