preface
An in-depth understanding of Spring source code is divided into seven sections. This section is the fourth section of Spring source code.
- The scanning process
- Bean creation process
- Container extension
4. AOP source code analysis
- Transactional source code analysis
- Spring JDBC source code analysis
- Spring common design patterns
Dig into the @Configuration agent
The BeanDefinition interface implements the AttributeAccessor interface, which sets or retrieves an attribute. In other words, each Bean can have many attributes, and when configuring class resolution, if the class has the @Configuration annotation, And proxyBeanMethodss is true, which sets a property of the bean, This property is called org. Springframework. Context. The annotation. ConfigurationClassPostProcessor. ConfigurationClass, value is full, The value of this property for other beans is Lite.
This property is used to indicate that a proxy class will be generated for this class in the future and put into the container.
Spring inside there will be a Spring BeanFactoryPostProcessor implementation class ConfigurationClassPostProcessor, when all the beans, after the completion of information collection is called, He has a method called internal enhanceConfigurationClasses (), can be understood as enhanced configuration class.
Inside the main do is select attribute called org. Springframework. Context. The annotation. ConfigurationClassPostProcessor. ConfigurationClass, and value is full of beans, To prepare the proxy Class for it, use Enhancer, which will generate a proxy Class for the original Class and override the new Class.
TestConfig$$EnhancerBySpringCGLIB$$com.xxx.TestConfig$$
So our primary concern is what happens when we generate a proxy for this class, like what does Spring do when we call a method?
SetCallbackFilter (); setCallbackTypes (); setCallbackTypes ();
private Enhancer newEnhancer(Class<? > configSuperClass,@Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(configSuperClass);
enhancer.setInterfaces(newClass<? >[]{EnhancedConfiguration.class}); enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
Copy the code
CALLBACKS are the interceptors that will be used when the method is called in the future, but these three interceptors will not be applied to all methods, so they need to be sorted out by setCallbackFilter. The first two interceptors both implement ConditionalCallback interfaces to determine whether they can apply to this method.
private static final Callback[] CALLBACKS = new Callback[]{
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);
Copy the code
We start with the BeanMethodInterceptor. The methods it intercepts are not required to be in the Object class and the setBeanFactory method of the BeanFactoryAware interface and have Bean annotations, so you might guess, He may be interested in intercepting methods with @bean annotations.
@Override
public boolean isMatch(Method candidateMethod) {
/** * is not an Object class && is not a setBeanFactory method in the BeanFactoryAware interface, && has Bean annotations ** /
booleanb = candidateMethod.getDeclaringClass() ! = Object.class && ! BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod) && BeanAnnotationHelper.isBeanAnnotated(candidateMethod);return b;
}
Copy the code
The post-intercept logic is mainly in Intercept (),
Here’s a rundown of what’s going on. Since @Configuration might have an @bean method, the return value of that method will eventually be included in Spring’s container, globally unique. If we treat it like a normal method, would we get a new object every time we call it? Spring doesn’t allow this, so the interceptor’s main purpose is to fetch and return from the container, call it multiple times, and the instance is the same, but there are other cases that I’ll talk about later.
Watching BeanFactoryAwareMethodInterceptor, he intercepted BeanFactoryAware setBeanFactory method under the interface, that we may also use less than, is the Spring for internal use, The main job is to assign a value to the $$beanFactory field in the proxy class.
If you want to view the generated classes of CGLIB, you can use the following statement to save the generated classes to the specified path.
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/home/HouXinLin/test/cglib");
Copy the code
The final noop.instance means that the parent class is called without interception.
In-depth @ Aspect
Before we get to that, let’s familiarize ourselves with a few concepts.
-
Advice:
Class A has A getUser() method, and if you want to execute some code Before or After calling getUser(), you define A method with @before or @after annotations, and when you call getUser(), it gets executed here, and that’s what notification means, and when the original method is called, it gets notified somewhere else, Abstract in the Spring as Advice, specific implementation AspectJMethodBeforeAdvice, AspectJAfterAdvice, etc. The word notice is used in this sense.
-
Join Point & Pointcut Point
These two are not easy to understand, but I found a good example in StackOverflow, when you go to a restaurant and you look at the menu and there are a lot of your candidates, you can choose any one or more of the items on the menu, so the join point is the menu candidate, and the entry point is the item you choose.
Take a practical example.
class Employee{
public String getName(a){....}
public void setName(String name){...}
}
Copy the code
These methods are called join points, and when we intercept only the getName() method, that method can be called a pointcut, which can be represented by a re, such as all methods in a particular package, all methods that return void, and all methods that begin with a set.
- Notice:
Notifier, not an AOP concept, is a specific term in Spring that combines advice and pointcuts into a unit and passes it to ProxyFactory, whose interface is PointcutAdvisor, as shown below.
public interface PointcutAdvisor {
Advice getAdvice(a);
Pointcut getPointcut(a);
}
Copy the code
All notifiers are instances of PointcutAdvisor.
As an example, this code is at the heart of Spring AOP. It intercepts the print method in TestLog, doing something Before the method. MethodBeforeAdvice is equivalent to @before, But Spring internally AspectJMethodBeforeAdvice instantiation.
@Configuration
public class TestLog {
public void print(a) {
}
}
MethodBeforeAdvice advice = new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("before"); }};final NameMatchMethodPointcutAdvisor nameMatchMethodPointcutAdvisor = new NameMatchMethodPointcutAdvisor();
nameMatchMethodPointcutAdvisor.setMappedName("print");
nameMatchMethodPointcutAdvisor.setAdvice(advice);
final ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(nameMatchMethodPointcutAdvisor);
proxyFactory.setTarget(new TestLog());
TestLog proxy = (TestLog) proxyFactory.getProxy();
proxy.print();
Copy the code
Take a look at another key piece of code that addresses the question of whether this pointcut can be applied to a method, which is important later on.
boolean matches = nameMatchMethodPointcutAdvisor.getPointcut().getMethodMatcher().matches(log, TestLog.class);
System.out.println(matches);
Copy the code
Let’s start with AOP for Spring.
With AOP, you’ll always start with these two annotations, @aspect, @enableAspectJAutoProxy, and then @enableAspectJAutoProxy, which is defined as follows.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
Copy the code
See the @ Import, said earlier, in the Spring for the second time in the process of collecting bean parses all @ Import annotations, and collect new beans from the inside, so the AspectJAutoProxyRegistrar, He achieved ImportBeanDefinitionRegistrar interface, means that the Spring will at some point the inside of the call registerBeanDefinitions () method to collect.
And AspectJAutoProxyRegistrar classes encapsulate AnnotationAwareAspectJAutoProxyCreator for BeanDifinition added to the container, this class is critical.
He implements the BeanPostProcessor interface, which means you can change an object after it has been created.
He implements the BeanFactoryAware interface, which means Spring tells him about the BeanFactory, and the bean holding the BeanFactory has a lot of control over the container.
He achieved InstantiationAwareBeanPostProcessor interface, means that can be instantiated in the Spring before an object, Julio cruz instantiated, block the subsequent object creation process.
We focus on his BeanPostProcessor postProcessAfterInitialization () method.
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if(bean ! =null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) ! = bean) {return wrapIfNecessary(bean, beanName, cacheKey);// Create a proxy object if the bean is qualified to be proxied}}return bean;
}
Copy the code
The wrapIfNecessary() method is used to generate the proxy object and return it if the object needs to be proxied.
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if(specificInterceptors ! = DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);
/** * creates a proxy object and returns */
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return bean;
}
Copy the code
The key here is getAdvicesAndAdvisorsForBean () and createProxy (), getAdvicesAndAdvisorsForBean () will get notice of information about the specified bean, if the result is null, Means that at least one notification can be applied to the bean, and then the proxy object is created with createProxy().
Enter getAdvicesAndAdvisorsForBean (after), no specific logic call specific logic all the way down to the following this approach, this method was already pared-down, such logic more clearly.
But when you call it in the order that we’re talking about, it doesn’t go into if, because it’s already been called somewhere before, and aspectNames are null when it’s called, and then it’s resolved, and then it’s cached, and then it gets directly from the cache when it’s called, This place is under the InstantiationAwareBeanPostProcessor interface, the implementation class or AnnotationAwareAspectJAutoProxyCreator said above
public List<Advisor> buildAspectJAdvisors(a) {
/** * All beans with Aspect annotations */
List<String> aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
synchronized (this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
List<Advisor> advisors = new ArrayList<>();
aspectNames = new ArrayList<>();
/** * All bean names */
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Object.class, true.false);
for (String beanName : beanNames) {
if(! isEligibleBean(beanName)) {continue;
}
/** * if this class is an aspect class */
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
/** * get all enhancements */ from here
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory); advisors.addAll(classAdvisors); }}}this.aspectBeanNames = aspectNames;
returnadvisors; }}}if (aspectNames.isEmpty()) {
return Collections.emptyList();
}
List<Advisor> advisors = new ArrayList<>();
for (String aspectName : aspectNames) {
List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
if(cachedAdvisors ! =null) {
advisors.addAll(cachedAdvisors);
}
else {
MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
advisors.addAll(this.advisorFactory.getAdvisors(factory)); }}return advisors;
}
Copy the code
This method is used to obtain Advisor information for all pointcuts and advice combinations in the bean. Multiple advice can be defined in a class, which may have multiple @before or @after.
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
Copy the code
GetPointcut () is used to extract the @pointcut, @around, @before, @after, @afterreturning, @Afterthrowing annotations on the method. If there is.
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
int declarationOrderInAspect, String aspectName) {
/** * From here the method attempts to extract pointcut information */
AspectJExpressionPointcut expressionPointcut = getPointcut(
candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
if (expressionPointcut == null) {
return null;
}
Find words encapsulate InstantiationModelAwarePointcutAdvisorImpl / * * * * * InstantiationModelAwarePointcutAdvisorImpl constructor will build notice * /
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
Copy the code
Return InstantiationModelAwarePointcutAdvisorImpl after extraction, but the object constructor also do one thing.
/** * more annotations to produce different Advice */
this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
Copy the code
InstantiateAdvice will be called here.
@Override
@Nullable
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
/ * * *, there is only one place call in InstantiationModelAwarePointcutAdvisorImpl * /Class<? > candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();// Get the type of the annotation on the methodAspectJAnnotation<? > aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);/** * Produce different Advice */ based on different annotations
AbstractAspectJAdvice springAdvice;
switch (aspectJAnnotation.getAnnotationType()) {
case AtPointcut:
if (logger.isDebugEnabled()) {
logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
}
return null;
case AtAround:
springAdvice = new AspectJAroundAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtBefore:
springAdvice = new AspectJMethodBeforeAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfter:
springAdvice = new AspectJAfterAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfterReturning:
springAdvice = new AspectJAfterReturningAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterReturningAnnotation.returning())) {
springAdvice.setReturningName(afterReturningAnnotation.returning());
}
break;
case AtAfterThrowing:
springAdvice = new AspectJAfterThrowingAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
}
break;
default:
throw new UnsupportedOperationException(
"Unsupported advice type on method: " + candidateAdviceMethod);
}
// Now to configure the advice...
springAdvice.setAspectName(aspectName);
springAdvice.setDeclarationOrder(declarationOrder);
String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
if(argNames ! =null) {
springAdvice.setArgumentNamesFromStringArray(argNames);
}
springAdvice.calculateArgumentBindings();
return springAdvice;
}
Copy the code
To meet over here, and now the results are all marked with @ Aspect class under the notifier information, notifiers are realized Advisor interface, here are InstantiationModelAwarePointcutAdvisorImpl implementation class.
InstantiationModelAwarePointcutAdvisorImpl contains information such as breakthrough point, notification, but it can’t return directly, because this information may be didn’t work for the current class, don’t need agent, so less step is extracted from the information in this kind of use, this part of the said later.
Now it is time to create a proxy class for the object. Assuming that you know Enhancer, the most important thing to do when retrieving an object from getProxy() is to set the Callback to it. By default, there are 7 callbacks. So through ProxyCallbackFilter filter, if the call is our custom method, then eventually enter DynamicAdvisedInterceptor, and the remaining six Callback temporarily not to see what is specific.
// It has been deleted
public Object getProxy(@Nullable ClassLoader classLoader) {
try {
/ / create Enhancer
Enhancer enhancer = createEnhancer();
// The generated proxy class integrates this class
enhancer.setSuperclass(proxySuperClass);
// The generated proxy class implements this interface
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(newClassLoaderAwareGeneratorStrategy(classLoader)); Callback[] callbacks = getCallbacks(rootClass); Class<? >[] types =newClass<? >[callbacks.length];for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
// Set the Callback filter
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
return createProxyClassAndInstance(enhancer, callbacks);
}
catch (CodeGenerationException | IllegalArgumentException ex) {
}
catch (Throwable ex) {
}
}
Copy the code
Came to the last point, the implementation process of Spring AOP is a chain, so there will be a way to get all the chain, see first DynamicAdvisedInterceptor# intercept method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
try {
retVal = methodProxy.invoke(target, argsToUse);
}
catch(CodeGenerationException ex) { CglibMethodInvocation.logFastClassGenerationFailure(method); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); }}else {
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
Copy the code
GetAdvisors () returns all notifications that apply to the current method. GetInterceptorsAndDynamicInterceptionAdvice () is used to convert all notifications to Interceptor, and then in order to invoke, Such as @ Before will be converted into MethodBeforeAdviceInterceptor, translation, the inner is to extract the information in the Advisor do packaging.
Note that the Spring will be in the first position in all chain add ExposeInvocationInterceptor chain, so entered the first chain is him.
Control chain in the direction of the ReflectiveMethodInvocation proceed method, simple, is not here.