написать впереди
В этой статье мы не будем пошагово объяснять этот процесс до исходного кода, а будем разбирать функции некоторых классов и методов, используемых в этом процессе. шаг. , это будет беспрепятственно. Это также мое ощущение в процессе чтения исходного кода, потому что весь набор исходного кода Spring линкуется один за другим, и процесс очень долгий, и за ним может запутаться. Но если мы шаг за шагом разобьем длинный ряд процессов, то вдруг осознаем момент, когда, наконец, соединим их вместе.
связанная информация
Время улучшения прокси-сервера AOP
Постобработка (postProcessAfterInitialization) фазы инициализации в процессе создания bean-компонента будет выполнять расширение AOP для bean-компонента, если выполняются условия. Основной реализацией является метод wrapIfNecessary класса AbstractAutoProxyCreator. Основная логическая реализация этого метода заключается в поиске аспекта, который можно применить к текущему созданному компоненту в контейнере, и использовании этого аспекта для создания прокси-объекта для компонента.
Некоторые концепции АОП
Прежде чем по-настоящему представить цепочку перехватчиков, давайте проясним некоторые неясные понятия, когда я впервые посмотрел на исходный код.
раздел: Улучшение основной бизнес-логики. И Advice, и Advisor in spring являются реализацией аспектов, но Advisor может реализовывать более сложную логику, чем Advice.
ткать: процесс применения аспекта к целевому методу или классу. Например, метод перехвата cglib и метод вызова прокси-сервера jdk, завершенную логику можно назвать переплетением.
Соединение: Метод, который можно сплести гранями.
Точка отсечки: особый метод вплетения в грань.
В чем разница между точкой соединения и точкой отсечения? Один модификатор "может" и один модификатор "конкретно". Мне кажется, что pointcut диктует, какие методы будут усилены аспектом, и все ограничения, удовлетворяющие pointcut, будут усилены. Точка соединения представляет собой набор методов, удовлетворяющих сканированию pointcut, и некоторые из этих методов могут удовлетворять ограничениям pointcut, а некоторые — нет. Давайте посмотрим, что такое определение pointcut весной:
public interface Pointcut {
/**
* Return the ClassFilter for this pointcut.
* @return the ClassFilter (never {@code null})
*/
ClassFilter getClassFilter();
/**
* Return the MethodMatcher for this pointcut.
* @return the MethodMatcher (never {@code null})
*/
MethodMatcher getMethodMatcher();
/**
* Canonical Pointcut instance that always matches.
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}
Среди них ClassFilter и MethodMatcher имеют метод match, который определяет, какие классы или методы соответствуют требованиям pointcut. Объединив эти понятия, я пришел к следующему выводу:
AOP Spring — это процесс объединения аспектов (Advice, Advisor) в (Weaving) с точкой соединения (JoinPoint), которая удовлетворяет ограничениям pointcut (PointCut).
Цепочка перехвата АОП
Приступим к основному блюду: весной, как мы можем использовать аспект для усиления класса? это перехват! Мы перехватываем выполнение метода, чтобы добавить дополнительную логику. Как его перехватить? Конечно, это динамический прокси.
Во всей экологии Spring существует сильная зависимость от динамических прокси, поэтому самые базовые прокси cglib и jdk являются необходимой основой для изучения Spring. Насколько сильно Spring зависит от динамических агентов?Для агентов cglib Spring напрямую копирует исходный код агентов cglib в свою собственную структуру. Итак, вы ищете MethodInterceptor, и вы обнаружите, что есть два идентичных класса, один находится в пакете net.sf.cglib.proxy, а другой — в пакете org.springframework.cglib.proxy.
Как работают методы перехвата spring? Ответ через MethodInterceptor.
public interface MethodInterceptor extends Interceptor {
/**
* Implement this method to perform extra treatments before and
* after the invocation. Polite implementations would certainly
* like to invoke {@link Joinpoint#proceed()}.
* @param invocation the method invocation joinpoint
* @return the result of the call to {@link Joinpoint#proceed()};
* might be intercepted by the interceptor
* @throws Throwable if the interceptors or the target object
* throws an exception
*/
Object invoke(MethodInvocation invocation) throws Throwable;
}
То есть, когда метод расширенного класса выполняется, он фактически вызывается через MethodInterceptor#invoke.
Следует отметить, что MethodInterceptor, используемый для перехвата AOP, и MethodInterceptor, усиленный прокси-сервером cglib, имеют одинаковое имя класса, но это два совершенно разных класса. Здесь вы можете перетолковать название весны (называние весны — это искусство). Прокси-сервер cglib реализуется методом перехвата MethodInterceptor#, а прокси-сервер jdk реализуется методом InvocationHandler#invoke. Реализация перехвата Spring AOP основана на методе MethodInterceptor#invoke. Обратите внимание, что нет, это комбинация имени класса прокси-сервера cglib и имени метода прокси-сервера jdk.
Теперь мы создаем два новых перехватчика, LogInterceptor и TimeInterceptor, для регистрации и синхронизации соответственно. ЛогИнтерсептор
public class LogInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("开始执行");
Object retVal = invocation.proceed();
System.out.println("执行完毕");
return retVal;
}
}
TimeInterceptor
public class TimeInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("计时开始");
Object retVal = invocation.proceed();
System.out.println("计时结束,耗时:" + (System.currentTimeMillis() - start) / 1000);
return retVal;
}
}
В то же время мы создаем два новых аспекта Advice, предоставляемых самой Spring, а именно MethodBeforeAdvice и AfterReturningAdvice. AOPAfterReturningAdvice
public class AOPAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("AfterReturning ....");
}
}
AOPMethodBeforeAdvice
public class AOPMethodBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before...");
}
}
Окончательная реализация улучшения АОП для MethodBeforeAdvice и AfterReturningAdvice фактически основана на MethodInterceptor, соответствующем MethodBeforeAdviceInterceptor и AfterReturningAdviceInterceptor соответственно. Это преобразование достигается с помощью AdvisorAdapter. Логика преобразования такова:
В чем разница между Advice и MethodInterceptor?
Начните с наследования классовВидно, что MethodInterceptor является подинтерфейсом Advice. Spring предоставляет некоторые унифицированные расширенные интерфейсы, такие как BeforeAdvice, AfterReturningAdvice и т. д. Это то, что мы часто называем предварительным улучшением и последующим улучшением. Улучшение объемного звучания достигается с помощью MethodInterceptor. Для предварительного и последующего улучшения это в конечном итоге достигается за счет улучшения объемного звучания.
MethodBeforeAdviceInterceptor
public Object invoke(MethodInvocation mi) throws Throwable {
// 先执行MethodBeforeAdvice的before方法
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
// 后执行目标方法
return mi.proceed();
}
##################
AfterReturningAdviceInterceptor
public Object invoke(MethodInvocation mi) throws Throwable {
// 先执行目标方法,得到返回值
Object retVal = mi.proceed();
// 再执行AfterReturningAdvice的afterReturning方法
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}
То есть некоторые советы, предоставляемые Spring, определяют только момент времени, когда расширенный код врезается в целевой код, независимо от того, выполняется ли он до выполнения целевого метода или после выполнения и т. д., что относительно просто. Суть по-прежнему заключается в улучшении объемного звучания, которое достигается преобразованием Advice в MethodInterceptor.
Далее мы создаем новый интерфейс Animal и класс Dog, который нужно дополнить аспектом.
public interface Animal {
void bark();
}
public class Dog implements Animal {
@Override
public void bark() {
System.out.println("wang wang wang...");
}
}
Примените LogInterceptor, TimeInterceptor, AOPAfterReturningAdvice и AOPMethodBeforeAdvice к собаке.
public class Main01 {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvice(new LogInterceptor());
proxyFactory.addAdvice(new TimeInterceptor());
proxyFactory.addAdvice(new AOPAfterReturningAdvice());
proxyFactory.addAdvice(new AOPMethodBeforeAdvice());
proxyFactory.setTarget(new Dog());
proxyFactory.setInterfaces(Dog.class.getInterfaces());
Object proxy = proxyFactory.getProxy();
if (proxy instanceof Dog) {
((Dog) proxy).bark();
}
}
}
выходной результат
开始执行
计时开始
Before...
wang wang wang...
AfterReturning ....
计时结束,耗时:0
执行完毕
Видно, что до и после выполнения метода Dog#bark добавляются наши улучшения журнала и времени, а также предварительные и постулучшения.
Принцип создания прокси-объектов Spring AOP фактически основан на вышеизложенном. Разве это не просто? Но что делает весну такой хорошей, так это то, что в ней есть много дополнительной логики и деталей, которые нужно отполировать. Этот блог сначала выбирает два из них для анализа.
1. Когда для улучшения прокси-сервера используется прокси-сервер cglib, а когда — прокси-сервер jdk?
2. Когда имеется несколько перехватчиков MethodInterceptor, как выполнить их последовательно?
Когда использовать прокси-сервер cglib и когда использовать прокси-сервер jdk
кproxyFactory.getProxy()Для входа, шаг за шагом, чтобы исследовать.
public Object getProxy() {
return createAopProxy().getProxy();
}
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}
Первый — получить AopProxy через createAopProxy(), а затем получить прокси-объект с помощью метода getProxy() AopProxy.
AopProxy — это интерфейс делегата для настроенных прокси-серверов AOP, позволяющий создавать фактические прокси-объекты. Существует три реализации: JdkDynamicAopProxy, CglibAopProxy и ObjenesisCglibAopProxy. А ObjenesisCglibAopProxy является подклассом CglibAopProxy.Определение конкретной логики реализации AopProxy В методе createAopProxy для AopProxyFactory у AopProxyFactory есть только один класс реализации DefaultAopProxyFactory.
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);
}
}
Существуют в основном следующие логические суждения
| Логика суждения | иллюстрировать |
|---|---|
| config.isOptimize() | Включить ли оптимизацию |
| config.isProxyTargetClass() | true — прокси-сервер cglib, false — прокси-сервер jdk |
| hasNoUserSuppliedProxyInterfaces(config) | Существует ли интерфейс реализации, отличный от SpringProxy, который является маркерным интерфейсом, и все классы, проксируемые АОП, будут реализовывать этот интерфейс. Весной существует множество маркерных интерфейсов. |
| targetClass.isInterface() | Является ли целевой класс оправданием |
| Proxy.isProxyClass(targetClass) | Был ли целевой класс проксирован |
В нашем основном методе Dog реализует наш собственный определенный интерфейс Animal и не устанавливает значение proxyTargetClass (по умолчанию — false). Поэтому для этого будет использоваться прокси-сервер jdk. Введите метод getProxy для JdkDynamicAopProxy:
@Override
public Object getProxy() {
return getProxy(ClassUtils.getDefaultClassLoader());
}
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
Это простая логика для создания прокси-сервера JDK.Обратите внимание, что логика создания прокси-класса — это Proxy.newProxyInstance(classLoader, proxiedInterfaces, this), а последний параметр — this.
Напомним, что последним параметром для создания прокси-сервера JDK является InvocationHandler. Видно, что JdkDynamicAopProxy сам по себе является InvocationHandler, и последний вызов метода вернется к методу вызова JdkDynamicAopProxy.
Как выполнить, когда есть несколько перехватчиков MethodInterceptor?
Когда мы настраиваем несколько перехватчиков, как эти перехватчики работают?
Исходя из вышеизложенного, мы знаем, что вызов метода прокси-объекта в конечном итоге приведет к вызову JdkDynamicAopProxy, затем перейдите к этому методу и посмотрите:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 只截取了关键部分
// 获取到可应用于当前方法的拦截器链
// 拦截器链可以先理解为MethodInterceptor的集合,该集合为List,是有序的,与添加的顺序保持一致
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
// 拦截器链为空,直接调用目标方法,不做增强
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// 把拦截器链应用到方法调用
// We need to create a method invocation...
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
}
Есть только два основных шага:
1. Получите цепочку перехватчиковКак и в предыдущем анализе, здесь мы получаем все MethodInterceptors, соответствующие текущему методу. Добавленные AOPAfterReturningAdvice и AOPMethodBeforeAdvice упакованы в AfterReturningAdviceInterceptor и MethodBeforeAdviceInterceptor.
2. Применить цепочку перехватчиков к вызову метода
Войтиinvocation.proceed()
public Object proceed() throws Throwable {
// 拦截器链执行完了,直接调用目标方法
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// 调用拦截器的invoke
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
Подводя итог, можно сказать, что метод MethodInterceptor#invoke вызывается с начала цепочки перехватчиков, а когда достигается конец цепочки перехватчиков, целевой метод вызывается напрямую. Теперь давайте взглянем на ранее определенный LogInterceptor:
public class LogInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("开始执行");
Object retVal = invocation.proceed();
System.out.println("执行完毕");
return retVal;
}
}
Уведомлениеinvocation.proceed(). После печати «начать выполнение» мы продолжаем выполнять MethodInvocation#proceed, чтобы убедиться, что MethodInterceptor, расположенный после LogInterceptor в цепочке перехватчиков, выполняется, если мы удалимinvocation.proceed(). Ошибок не будет, но цепочка перехватчиков обрывается до тех пор, пока не появится LogInterceptor.
Вся схема выполнения логики:
Суммировать
В этой статье кратко анализируются MethodInterceptor и ProxyFactory, используемые в реализации цепочки перехватчиков AOP, и иллюстрируется, как выполняется цепочка перехватчиков один за другим. Как запись изучения весны самостоятельно.
Последующий анализ продолжится:
1. Познакомить с принципом расширения DynamicIntroductionAdvice
2. Spring AOP генерирует логику исходного кода прокси.