Spring中AOP相关的API及源码解析

本系列文章:

读源码,我们可以从第一行读起

你知道Spring是怎么解析配置类的吗?

配置类为什么要添加@Configuration注解?

谈谈Spring中的对象跟Bean,你知道Spring怎么创建对象的吗?

这篇文章,我们来谈一谈Spring中的属性注入

推荐阅读:

Spring官网阅读 | 总结篇

Spring杂谈

本系列文章将会带你一行行的将Spring的源码吃透,推荐阅读的文章是阅读源码的基础!

因为本文会涉及到动态代理的相关内容,如果对动态代理不是很了解的话,参考文章:

动态代理学习(一)自己动手模拟JDK动态代理

动态代理学习(二)JDK动态代理源码分析

前言

之所以写这么一篇文章主要是因为下篇文章将结束Spring启动整个流程的分析,从解析配置到创建对象再到属性注入最后再将创建好的对象初始化成为一个真正意义上的Bean。因为下篇文章会设计到AOP,所以提前单独将AOP的相关API及源码做一次解读,这样可以降低阅读源码的障碍,话不多说,我们进入正文!

一个使用API创建代理的例子

在进入API分析前,我们先通过两个例子体会下如何使用API的方式来创建一个代理对象,对应示例如下:

  1. 定义通知
public class DmzAfterReturnAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {
System.out.println("after invoke method [" + method.getName() + "],aop afterReturning logic invoked");
}
} public class DmzAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("aroundAdvice invoked");
return invocation.proceed();
}
} public class DmzBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("before invoke method [" + method.getName() + "],aop before logic invoked");
}
} public class DmzIntroductionAdvice extends DelegatingIntroductionInterceptor implements Runnable {
@Override
public void run() {
System.out.println("running!!!!");
}
}
  1. 切点
public class DmzPointcut implements Pointcut {
@Override
@NonNull
public ClassFilter getClassFilter() {
// 在类级别上不进行拦截
return ClassFilter.TRUE;
} @Override
@NonNull
public MethodMatcher getMethodMatcher() {
return new StaticMethodMatcherPointcut() {
@Override
public boolean matches(@NonNull Method method, Class<?> targetClass) {
// 对于toString方法不进行拦截
return !method.getName().equals("toString");
}
};
}
}
  1. 目标类
public class DmzService {
@Override
public String toString() {
System.out.println("dmzService toString invoke");
return "dmzService";
} public void testAop(){
System.out.println("testAop invoke");
}
}
  1. 测试代码
public class Main {
public static void main(String[] args) { ProxyFactory proxyFactory = new ProxyFactory(); // 一个Advisor代表的是一个已经跟指定切点绑定了的通知
// 在这个例子中意味着环绕通知不会作用到toString方法上
Advisor advisor = new DefaultPointcutAdvisor(new DmzPointcut(), new DmzAroundAdvice()); // 添加一个绑定了指定切点的环绕通知
proxyFactory.addAdvisor(advisor); // 添加一个返回后的通知
proxyFactory.addAdvice(new DmzAfterReturnAdvice()); // 添加一个方法执行前的通知
proxyFactory.addAdvice(new DmzBeforeAdvice()); // 为代理类引入一个新的需要实现的接口--Runnable
proxyFactory.addAdvice(new DmzIntroductionAdvice()); // 设置目标类
proxyFactory.setTarget(new DmzService()); // 因为要测试代理对象自己定义的方法,所以这里启用cglib代理
proxyFactory.setProxyTargetClass(true); // 创建代理对象
Object proxy = proxyFactory.getProxy(); // 调用代理类的toString方法,通过控制台查看代理逻辑的执行情况
proxy.toString(); if (proxy instanceof DmzService) {
((DmzService) proxy).testAop();
} // 判断引入是否成功,并执行引入的逻辑
if (proxy instanceof Runnable) {
((Runnable) proxy).run();
}
}
}

这里我就不将测试结果放出来了,大家可以先自行思考这段程序将输出什么。接下来我们就来分析上面这段程序中所涉及到的API,通过这些API的学习相信大家可以彻底理解上面这段代码。

API介绍

Pointcut(切点)

对应接口定义如下:

public interface Pointcut {

    // ClassFilter,在类级别进行过滤
ClassFilter getClassFilter(); // MethodMatcher,在方法级别进行过滤
MethodMatcher getMethodMatcher(); // 一个单例对象,默认匹配所有
Pointcut TRUE = TruePointcut.INSTANCE; }

切点的主要作用是定义通知所要应用到的类跟方法,上面的接口定义也很明显的体现了这一点,我们可以将其拆分成为两个部分

  • ClassFilter,接口定义如下:
public interface ClassFilter {

	boolean matches(Class<?> clazz);

	ClassFilter TRUE = TrueClassFilter.INSTANCE;

}

ClassFilter的主要作用是在类级别上对通知的应用进行一次过滤,如果它的match方法对任意的类都返回true的话,说明在类级别上我们不需要过滤,这种情况下,通知的应用,就完全依赖MethodMatcher的匹配结果。

  • MethodMatcher,接口定义如下:
public interface MethodMatcher {

	boolean matches(Method method, @Nullable Class<?> targetClass);

	boolean isRuntime();

	boolean matches(Method method, @Nullable Class<?> targetClass, Object... args);

	MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}

MethodMatcher中一共有三个核心方法

  • matches(Method method, @Nullable Class<?> targetClass),这个方法用来判断当前定义的切点跟目标类中的指定方法是否匹配,它可以在创建代理的时候就被调用,从而决定是否需要进行代理,这样就可以避免每次方法执行的时候再去做判断
  • isRuntime(),如果这个方法返回true的话,意味着每次执行方法时还需要做一次匹配
  • matches(Method method, @Nullable Class<?> targetClass, Object... args),当之前的isRuntime方法返回true时,会调用这个方法再次进行一次判断,返回false的话,意味这个不对这个方法应用通知

Advice(通知)

环绕通知(Interception Around Advice)

接口定义如下:

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}

在上面接口定义的invoke方法中,MethodInvocation就是当前执行的方法,当我们调用invocation.proceed就是在执行当前的这个方法,基于此,我们可以在方法的执行前后去插入我们自定义的逻辑,比如下面这样

// 执行前的逻辑
doSomeThingBefore();
Object var = invocation.proceed;
doSomeThingAfter();
// 执行后的逻辑
retrun var;

前置通知(Before Advice)

public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}

跟环绕通知不同的是,这个接口中定义的方法的返回值是void,所以前置通知是无法修改方法的返回值的。

如果在前置通知中发生了异常,那么会直接终止目标方法的执行以及打断整个拦截器链的执行

后置通知(After Returning Advice)

public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}

后置通知相比较于前置通知,主要有以下几点不同

  • 后置通知可以访问目标方法的返回值,但是不能修改
  • 后置通知是在方法执行完成后执行

异常通知(Throws Advice)

public interface ThrowsAdvice extends AfterAdvice {

}

异常通知中没有定义任何方法,它更像一个标记接口。我们在定义异常通知时需要实现这个接口,同时方法的签名也有要求

  1. 方法名称必须是afterThrowing
  2. 方法的参数个数必须是1个或者4个,如下:
public class OneParamThrowsAdvice implements ThrowsAdvice {

    // 如果只有一个参数,那么这个参数必须是要进行处理的异常
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
} public class FourParamThrowsAdvice implements ThrowsAdvice { // 如果定义了四个参数,那么这四个参数分别是
// 1.m:目标方法
// 2.args:执行目标方法所需要的参数
// 3.target:目标对象
// 4.ex:具体要处理的异常
// 并且参数类型必须按照这个顺序定义
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}

我们可以在一个异常通知中定义多个方法,在后续的源码分析中我们会发现,这些方法最终会被注册成对应的异常的handler,像下面这样

public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
} public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}

引入通知(Introduction Advice)

引入通知的主要作用是可以让生成的代理类实现额外的接口。例如在上面的例子中,我们为DmzService创建一个代理对象,同时为其定义了一个引入通知

public class DmzIntroductionAdvice extends DelegatingIntroductionInterceptor implements Runnable {
@Override
public void run() {
System.out.println("running!!!!");
}
}

在这个引入通知中,我们为其引入了一个新的需要实现的接口Runnable,同时通知本身作为这个接口的实现类。

通过这个引入通知,我们可以将生成的代理类强转成Runnable类型然后执行其run方法,同时,run方法也会被前面定义的前置通知,后置通知等拦截。

为了更好的了解引入通知,我们来需要了解下DelegatingIntroductionInterceptor这个类。见名知意,这个类就是一个委托引入拦截器,因为我们要为代理类引入新的接口,因为着我们要提供具体的实现的逻辑,而具体的实现的逻辑就可以被委托给这个DelegatingIntroductionInterceptor

我们可以看看它的源码

public class DelegatingIntroductionInterceptor extends IntroductionInfoSupport
implements IntroductionInterceptor { // 实际实现了引入逻辑的类
@Nullable
private Object delegate; // 对外提供了一个带参的构造函数,通过这个构造函数我们可以传入一个
// 具体的实现类
public DelegatingIntroductionInterceptor(Object delegate) {
init(delegate);
}
// 对子类暴露了一个空参的构造函数,默认将自身作为实现了引入逻辑的委托类
// 我们上面的例子中就是使用的这种方法
protected DelegatingIntroductionInterceptor() {
init(this);
} // 对这个类进行初始化,要通过实际的实现类来找到具体要实现的接口
private void init(Object delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate; // 找到delegate所有实现的接口
implementInterfacesOnObject(delegate); // 因为我们可能会将DelegatingIntroductionInterceptor本身作为委托者
// Spring的设计就是不对外暴露这两个接口
// 如果将其暴露,意味着我们可以将代理类强转成这种类型
suppressInterface(IntroductionInterceptor.class);
suppressInterface(DynamicIntroductionAdvice.class);
} // 引入通知本身也是基于拦截器实现的,当执行一个方法时需要判断这个方法
// 是不是被引入的接口中定义的方法,如果是的话,那么不能调用目标类的方法
// 而要调用委托类的方法
public Object invoke(MethodInvocation mi) throws Throwable {
if (isMethodOnIntroducedInterface(mi)) {
Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());
// 这里是处理一种特殊情况,方法的返回值是this的时候
// 这里应该返回代理类
if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {
Object proxy = ((ProxyMethodInvocation) mi).getProxy();
if (mi.getMethod().getReturnType().isInstance(proxy)) {
retVal = proxy;
}
}
// 其余情况下直接将委托类的执行结果返回
return retVal;
}
// 执行到这里说明不是引入的方法,这是Spring提供了一个扩展逻辑
// 正常来说这个类只会处理引入的逻辑,通过这个方法可以对目标类中的方法做拦截
// 不常用
return doProceed(mi);
} protected Object doProceed(MethodInvocation mi) throws Throwable {
return mi.proceed();
} }

通过查看这个类的源码我们可以发现,所谓的引入其实就是在方法执行的时候加了一层拦截,当判断这个方法是被引入的接口提供的方法的时候,那么就执行委托类中的逻辑而不是目标类中的方法

关于通知的总结

通过上文的分析我们可以发现,通知总共可以分为这么几类

  1. 普通的通知(前置,后置,异常等,没有实现MethodInterceptor接口)
  2. 环绕通知(实现了MethodInterceptor接口)
  3. 引入通知(需要提供额外的引入的信息,实现了MethodInterceptor接口)

上面的分类并不标准,只是为了方便大家记忆跟理解,虽然我们普通的通知没有直接实现MethodInterceptor接口,但其实它的底层也是依赖于拦截器来完成的,大家可以看看下面这个类

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

	@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
} // 根据传入的一个前置通知,创建一个对应的拦截器
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
} } public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable { private final MethodBeforeAdvice advice; public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
} // 实际上还是利用拦截器,在方法执行前调用了通知的before方法完成了前置通知
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
} }

Advisor (绑定通知跟切点)

一个Advisor 实际上就是一个绑定在指定切点上的通知。在前面的例子我们可以发现,有两种添加通知的方式

// 一个Advisor代表的是一个已经跟指定切点绑定了的通知
// 在这个例子中意味着环绕通知不会作用到toString方法上
Advisor advisor = new DefaultPointcutAdvisor(new DmzPointcut(), new DmzAroundAdvice()); // 添加一个绑定了指定切点的环绕通知
proxyFactory.addAdvisor(advisor); // 添加一个返回后的通知
proxyFactory.addAdvice(new DmzAfterReturnAdvice());

一种是直接添加了一个Advisor,还有一种是添加一个Advice,后者也会被转换成一个Advisor然后再进行添加,没有指定切点的通知是没有任何意义的

public void addAdvice(Advice advice) throws AopConfigException {
int pos = this.advisors.size();
// 默认添加到集合的最后一个位置
addAdvice(pos, advice);
} // 这个方法添加通知
public void addAdvice(int pos, Advice advice) throws AopConfigException {
Assert.notNull(advice, "Advice must not be null"); // 如果是一个引入通知,那么构建一个DefaultIntroductionAdvisor
// DefaultIntroductionAdvisor会匹配所有类
if (advice instanceof IntroductionInfo) {
addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
}
// 不能直接添加一个不是IntroductionInfo的DynamicIntroductionAdvice(动态引入通知)
else if (advice instanceof DynamicIntroductionAdvice) {
throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");
}
else {
// 如果是普通的通知,那么会创建一个DefaultPointcutAdvisor
// DefaultPointcutAdvisor所定义的切点会匹配所有类以及所有方法
addAdvisor(pos, new DefaultPointcutAdvisor(advice));
}
}

ProxyCreatorSupport

这个类的主要作用是为创建一个AOP代理对象提供一些功能支持,通过它的getAopProxyFactory能获取一个创建代理对象的工厂。

// 这里我只保留了这个类中的关键代码
public class ProxyCreatorSupport extends AdvisedSupport { private AopProxyFactory aopProxyFactory; // 空参构造,默认会创建一个DefaultAopProxyFactory
// 通过这个ProxyFactory可以创建一个cglib代理或者jdk代理
public ProxyCreatorSupport() {
this.aopProxyFactory = new DefaultAopProxyFactory();
} // 通过这个方法可以创建一个具体的代理对象
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
// 实际就是使用DefaultAopProxyFactory来创建一个代理对象
// 可以看到在调用createAopProxy方法时,传入的参数是this
// 这是因为ProxyCreatorSupport本身就保存了创建整个代理对象所需要的配置信息
return getAopProxyFactory().createAopProxy(this);
}
}

另外通过上面的UML类图还能看到,ProxyCreatorSupport继承了AdvisedSupportAdvisedSupport继承了ProxyConfig

ProxyConfig

其中ProxyConfig是所有的AOP代理工厂的父类,它包含了创建一个AOP代理所需要的基础的通用的一些配置信息

// 这里省略了一些getter跟setter方法
public class ProxyConfig implements Serializable { // 是否开启cglib代理,默认不开启使用jdk动态代理
private boolean proxyTargetClass = false; // 是否启用优化,默认为false,按照官网对这个参数的解释
// 这个优化是针对cglib,如果设计为true的话,会做一些侵入性的优化
// 是否开启在jdk代理的情况下没有影响
// 官网中特地说明了,除非对cglib的优化非常了解,否则不要开启这个参数
private boolean optimize = false; // 生成的代理类是否需要实现Advised接口,这个接口可以向外提供操作通知的方法
// 如果为false会实现
// 为true的话,不会实现
boolean opaque = false; // 是否将当前的配置类暴露到一个线程上下文中,如果设置为true的话
// 可以通过AopContext.currentProxy()来获取到当前的代理对象
boolean exposeProxy = false; // 标志着是否冻结整个配置,如果冻结了,那么配置信息将不允许修改
private boolean frozen = false;
}

AdvisedSupport

当我们为某个对象创建代理时,除了需要上面的ProxyConfig提供的一些基础配置外,起码还需要知道

  1. 需要执行的通知是哪些?
  2. 目标对象是谁?
  3. 创建出来的代理需要实现哪些接口?

而这些配置信息是由AdvisedSupport提供的,AdvisedSupport本身实现了Advised接口,Advised接口定义了管理通知的方法。


在了解了上面的API后我们来看看Spring提供了几种创建AOP代理的方式

  1. ProxyFactoryBean
  2. ProxyFactory
  3. Auto-proxy

ProxyFactoryBean的方式创建AOP代理

使用示例

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/> <bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/> <bean id="dmzProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!---->
<property name="proxyInterfaces" value="java.lang.Runnable"/>
<property name="proxyTargetClass" value="true"/>
<property name="target" ref="dmzService"/>
<property name="interceptorNames">
<list>
<value>aroundAdvice</value>
</list>
</property>
</bean> </beans>
// 目标类
public class DmzService {
@Override
public String toString() {
System.out.println("dmzService toString invoke");
return "dmzService";
} public void testAop(){
System.out.println("testAop invoke");
}
} // 通知
public class DmzAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("aroundAdvice invoked");
return invocation.proceed();
}
} public class SourceMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc =
new ClassPathXmlApplicationContext("application-init.xml");
DmzService dmzProxy = ((DmzService) cc.getBean("dmzProxy"));
dmzProxy.testAop();
}
}

ProxyFactoryBean介绍

跟普通的FactoryBean一样,这个类的主要作用就是通过getObject方法能够获取一个Bean,不同的是这个类获取到的是代理后的Bean。

我们查看这个类的继承关系可以发现

这个类除了实现了FactoryBean接口以及一些Aware接口外,额外还继承了ProxyCreatorSupport类。它是一个factoryBean,所以我们重点就关注它的getObject方法即可。

public Object getObject() throws BeansException {
// 初始化通知链
// 这里主要就是将在XML中配置的通知添加到
// AdvisedSupport管理的配置中去
initializeAdvisorChain();
if (isSingleton()) {
// 如果是单例的,那么获取一个单例的代理对象
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
// 如果是原型的,获取一个原型的代理对象
return newPrototypeInstance();
}
}

关于这段代码就不做过多分析了,它其实就两步(不管是哪种方式创建代理,都分为这两步)

  1. 完善创建代理需要的配置信息
  2. 创建代理

其中配置信息分为两部分,其一是AppConfig管理的通用的配置信息,其二是AdvisedSupport管理的通知信息。通用的配置信息我们可以直接在XML中配置,例如在上面的例子中我们就配置了proxyTargetClass属性,而通知信息即使我们在XML中配置了也还需要做一层转换,在前面我们也提到过了,所有的Advice都会被转换成Advisor添加到配置信息中。

ProxyFactory的方式创建AOP代理

使用示例(略,见开头)

ProxyFactory介绍

从上面我们可以看出,ProxyFactory也继承自ProxyCreatorSupport,从之前的例子我们也能感受到,使用它的API来创建一个代理对象也是要先去设置相关的配置信息,最后再调用创建代理的方法

我们之后要分析的自动代理内部就是通过创建了一个ProxyFactory来获取代理对象的。

我们可以对比下ProxyFactoryBeanProxyFactory在创建代理对象时的代码

  • ProxyFactory
public Object getProxy() {
// 调用了ProxyCreatorSupport的createAopProxy()方法创建一个AopProxy对象
// 然后调用AopProxy对象的getProxy方法
return createAopProxy().getProxy();
}
  • ProxyFactoryBean
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = freshTargetSource();
if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
Class<?> targetClass = getTargetClass();
if (targetClass == null) {
throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
}
setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
}
super.setFrozen(this.freezeProxy);
// 重点就看这里
// 这里调用了ProxyCreatorSupport的createAopProxy()方法创建一个AopProxy对象
// 而getProxy方法就是调用创建的AopProxy的getProxy方法
this.singletonInstance = getProxy(createAopProxy());
}
return this.singletonInstance;
} protected Object getProxy(AopProxy aopProxy) {
return aopProxy.getProxy(this.proxyClassLoader);
}

综上,我们可以得出结论,不管是通过哪种方式创建AOP代理,核心代码就一句

createAopProxy().getProxy()

这句代码也是我们接下来源码分析的重点

Auto-proxy(实现自动AOP代理)

自动代理机制的实现其实很简单,就是通过Bean的后置处理器,在创建Bean的最后一步对Bean进行代理,并将代理对象放入到容器中。

实现自动代理的核心类就是AbstractAutoProxyCreator。我们来看看它的继承关系

为了更好的体会自动代理的作用,我们对它的三个具体的实现类来进行分析,分别是

  1. BeanNameAutoProxyCreator
  2. DefaultAdvisorAutoProxyCreator
  3. AnnotationAwareAspectJAutoProxyCreator

BeanNameAutoProxyCreator

使用示例

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/> <bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/> <bean id="beforeAdvice" class="com.dmz.spring.initalize.aop.advice.DmzBeforeAdvice"/> <!--使用很简单,只要配置一个BeanNameAutoProxyCreator即可-->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" name="autoProxyCreator">
<!--使用cglib代理-->
<property name="proxyTargetClass" value="true"/>
<!--对所有以dmz开头的bean进行自动代理-->
<property name="beanNames" value="dmz*"/>
<!--添加两个通知-->
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>aroundAdvice</value>
</list>
</property>
</bean> </beans>
public class SourceMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc =
new ClassPathXmlApplicationContext("application-init.xml");
DmzService dmzProxy = ((DmzService) cc.getBean("dmzService"));
dmzProxy.testAop();
}
}
// 程序打印:
// before invoke method [testAop],aop before logic invoked
// aroundAdvice invoked
// testAop invoke

DefaultAdvisorAutoProxyCreator

使用示例

在上面例子的基础上我们要修改配置文件,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/> <bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/> <bean id="beforeAdvice" class="com.dmz.spring.initalize.aop.advice.DmzBeforeAdvice"/> <bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="dmzBeforeAdvisor">
<property name="advice" ref="beforeAdvice"/>
</bean> <bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="dmzAroundAdvisor">
<property name="advice" ref="aroundAdvice"/>
</bean> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
id="advisorAutoProxyCreator">
<!--这两个参数标明了我们要使用所有以dmz开头的Advisor类型的通知
这里必须配置是Advisor,不能是Advice或者interceptor,
可以看到DefaultAdvisorAutoProxyCreator跟BeanNameAutoProxyCreator的区别在于
BeanNameAutoProxyCreator需要指定要被代理的bean的名称,
而DefaultAdvisorAutoProxyCreator不需要,它会根据我们传入的Advisor
获取到需要被代理的切点
-->
<property name="usePrefix" value="true"/>
<property name="advisorBeanNamePrefix" value="dmz"/> <property name="proxyTargetClass" value="true"/>
</bean>
</beans>

测试代码就不放了,大家可以自行测试,肯定是没问题的

AnnotationAwareAspectJAutoProxyCreator

我们正常在使用AOP的时候都会在配置类上添加一个@EnableAspectJAutoProxy注解,这个注解干了什么事呢?

实际就是向容器中注册了一个AnnotationAwareAspectJAutoProxyCreator

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 这里导入了一个类
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy { boolean proxyTargetClass() default false; boolean exposeProxy() default false; }

通过@EnableAspectJAutoProxy导入了一个AspectJAutoProxyRegistrar,这个类会向容器中注册一个AnnotationAwareAspectJAutoProxyCreator,对应源码如下:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 在这里完成的注册
// 最终会调用到AopUtils的registerAspectJAnnotationAutoProxyCreatorIfNecessary方法
// 完成AnnotationAwareAspectJAutoProxyCreator这个bd的注册
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); // 解析注解的属性
// proxyTargetClass:为true的话开启cglib代理,默认为jdk代理
// exposeProxy:是否将代理对象暴露到线程上下文中
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
} if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
} }

前面已经说过了,自动代理机制实际上就是Spring在内部new了一个ProxyFactory,通过它创建了一个代理对象。对应的代码就在AbstractAutoProxyCreator中的createProxy方法内,源码如下:

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 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);
}
// 通过proxyFactory来创建一个代理对象
return proxyFactory.getProxy(getProxyClassLoader());
}

关于这个类的执行流程在下篇文章中我再详细介绍,接下来我们要分析的就是具体创建AOP代理的源码了。对应的核心源码就是我们之前所提到的

createAopProxy().getProxy();

这行代码分为两步,我们逐步分析

  1. 调用AopProxyFactorycreateAopProxy()方法获取一个AopProxy对象
  2. 调用AopProxy对象的getProxy()方法

核心源码分析

createAopProxy方法分析

AopProxyFactory在Spring中只有一个默认的实现类,就是DefaultAopProxyFactory,它的对应的createAopProxy的是实现代码如下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    // 就是通过AOP相关的配置信息来决定到底是使用cglib代理还是jdk代理
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 如果开启了优化,或者ProxyTargetClass设置为true
// 或者没有提供代理类需要实现的接口,那么使用cglib代理
// 在前面分析参数的时候已经说过了
// 默认情况下Optimize都为false,也不建议设置为true,因为会进行一些侵入性的优化
// 除非你对cglib的优化非常了解,否则不建议开启
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.");
}
// 需要注意的是,如果需要代理的类本身就是一个接口
// 或者需要被代理的类本身就是一个通过jdk动态代理生成的类
// 那么不管如何设置都会使用jdk动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
// 否则都是jdk代理
else {
return new JdkDynamicAopProxy(config);
}
} // 判断是否提供代理类需要实现的接口
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
} }

getProxy方法分析

从对createAopProxy方法的分析可以看到,我们要么执行的是ObjenesisCglibAopProxy中的getProxy方法,要么就是JdkDynamicAopProxygetProxy方法,二者的区别在于一个是通过cglib的方式生成代理对象,而后者则是通过jdk的方式生成动态代理。

这里我只分析一个JdkDynamicAopProxy,首先我们来看看这个类的继承关系

希望你之前已经阅读过

原创 动态代理学习(一)自己动手模拟JDK动态代理

原创 动态代理学习(二)JDK动态代理源码分析

可以看到这个类本身就是一个InvocationHandler,这意味着当调用代理对象中的方法时,最终会调用到JdkDynamicAopProxyinvoke方法。

所以对于这个类我们起码应该关注两个方法

  1. getProxy方法
  2. invoke方法

getProxy方法源码如下:

public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
// 这里获取到代理类需要实现的所有的接口
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
// 需要明确是否在接口定义了hashCode以及equals方法
// 如果接口中没有定义,那么在调用代理对象的equals方法的时候
// 如果两个对象相等,那么意味着它们的目标对象,通知以及实现的接口都相同
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

我们再来看看到底是怎么获取到需要实现的接口的

static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
// 第一步:获取在配置中指定的需要实现的接口
Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces(); // 第二步:如果没有指定需要实现的接口,但是需要代理的目标类本身就是一个接口
// 那么将其添加到代理类需要实现的接口的集合中
// 如果目标类本身不是一个接口,但是是经过jdk代理后的一个类
// 那么获取这个代理后的类所有实现的接口,并添加到需要实现的接口集合中
if (specifiedInterfaces.length == 0) {
Class<?> targetClass = advised.getTargetClass();
if (targetClass != null) {
if (targetClass.isInterface()) {
advised.setInterfaces(targetClass);
}
else if (Proxy.isProxyClass(targetClass)) {
advised.setInterfaces(targetClass.getInterfaces());
}
specifiedInterfaces = advised.getProxiedInterfaces();
}
} // 第三步:为代理类添加三个默认需要实现的接口,分别是
// 1.SpringProxy,一个标记接口,代表这个类是通过Spring的AOP代理生成的
// 2.Advised,提供了管理通知的方法
// 3.DecoratingProxy,用户获取到真实的目标对象
// 这个真实对象指的是在嵌套代理的情况下会获取到最终的目标对象
// 而不是指返回这个ProxyFactory的target
boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);
boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);
boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class));
int nonUserIfcCount = 0;
if (addSpringProxy) {
nonUserIfcCount++;
}
if (addAdvised) {
nonUserIfcCount++;
}
if (addDecoratingProxy) {
nonUserIfcCount++;
}
Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];
System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);
int index = specifiedInterfaces.length;
if (addSpringProxy) {
proxiedInterfaces[index] = SpringProxy.class;
index++;
}
if (addAdvised) {
proxiedInterfaces[index] = Advised.class;
index++;
}
if (addDecoratingProxy) {
proxiedInterfaces[index] = DecoratingProxy.class;
}
return proxiedInterfaces;
}

invoke方法分析

在确认了需要实现的接口后,直接调用了jdk的动态代理方法,这个我们就不做分析了,接下来我们来看看Spring是如何将通知应用到代理对象上的,对应的要分析的代码就是JdkDynamicAopProxyinvoke方法,源码如下:

// 这个方法的代码稍微有点长,代码也比较难,希望大家能耐心看完
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource;
Object target = null; try {
// 首先处理的是hashCode跟equals方法
// 如果接口中没有定义这两个方法,那么会调用本类中定义的equals方法
// 前面我们也说过了,只有当两个类的目标对象,通知以及实现的接口都相等的情况下
// equals才会返回true
// 如果接口中定义了这两个方法,那么最终会调用目标对象中的方法
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
return hashCode();
} // 也就是说我们调用的是DecoratingProxy这个接口中的方法
// 这个接口中只定义了一个getDecoratedClass方法,用于获取到
// 最终的目标对象,在方法实现中会通过一个while循环来不断接近
// 最终的目标对象,直到得到的目标对象不是一个被代理的对象才会返回
else if (method.getDeclaringClass() == DecoratingProxy.class) {
return AopProxyUtils.ultimateTargetClass(this.advised);
} // 说明调用的是Advised接口中的方法,这里只是单纯的进行反射调用
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) { return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
} Object retVal; // 说明需要将代理类暴露到线程上下文中
// 调用AopContext.setCurrentProxy方法将其放入到一个threadLocal中
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
} // 接下来就是真正的执行代理逻辑了
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null); // 先获取整个拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // 如果没有进行拦截,直接反射调用方法
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
} // 否则开始执行整个链条
else {
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
} // 这里是处理一种特殊情况,就是当执行的方法返回值为this的情况
// 这种情况下,需要返回当前的代理对象而不是目标对象
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
}

在上面整个流程中,我们抓住核心的两步

  1. 获取整个拦截器链
  2. 开始在拦截器链上执行方法

我们先看第一步,对应源码如下:

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(method);
List<Object> cached = this.methodCache.get(cacheKey);
if (cached == null) {
// 调用了advisorChainFactory的getInterceptorsAndDynamicInterceptionAdvice方法
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.methodCache.put(cacheKey, cached);
}
return cached;
}
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, @Nullable Class<?> targetClass) { List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length); Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass()); // 是否有引入通知
boolean hasIntroductions = hasMatchingIntroductions(config, actualClass); AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); // 获取到所有的通知
for (Advisor advisor : config.getAdvisors()) {
// 除了引入通知外,可以认为所有的通知都是一个PointcutAdvisor
if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
// config.isPreFiltered:代表的是配置已经过滤好了,是可以直接应用的
// 这句代码的含义就是配置是预过滤的或者在类级别上是匹配的
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
// 接下来要判断在方法级别上是否匹配
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {
// 将通知转换成对应的拦截器
// 有些通知本身就是拦截器,例如环绕通知
// 有些通知需要通过一个AdvisorAdapter来适配成对应的拦截器
// 例如前置通知,后置通知,异常通知等
// 其中MethodBeforeAdvice会被适配成MethodBeforeAdviceInterceptor
// AfterReturningAdvice会被适配成AfterReturningAdviceInterceptor
// ThrowAdvice会被适配成ThrowsAdviceInterceptor
MethodInterceptor[] interceptors = registry.getInterceptors(advisor); // 如果是动态的拦截,会创建一个InterceptorAndDynamicMethodMatcher
// 动态的拦截意味着需要根据具体的参数来决定是否进行拦截
if (mm.isRuntime()) {
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
else if (advisor instanceof IntroductionAdvisor) {
// 说明是引入通知
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
// 前文我们有提到过,引入通知实际就是通过一个拦截器
// 将方法交由引入的类执行而不是目标类
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
else {
// 可能会扩展出一些通知,一般不会
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
} return interceptorList;
}

在构建好拦截器链后,接下来就是真正执行方法了,对应代码就是

// 先创建一个MethodInvocation
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// 开始在拦截器链上执行这个方法
retVal = invocation.proceed();

最后的关键代码就落在了ReflectiveMethodInvocationproceed方法

public Object proceed() throws Throwable {

    // 满足这个条件,说明执行到了最后一个拦截器,那么直接反射调用目标方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
} // 获取到下一个要执行的拦截器
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// 前面构建拦截器链的时候我们可以看到,动态的拦截的话会创建一个InterceptorAndDynamicMethodMatcher
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// 如果匹配失败了,执行拦截器链中的下一个拦截逻辑
return proceed();
}
}
else {
// 调用拦截器中的invoke方法,可以看到这里将this作为参数传入了
// 所以我们在拦截器中调用 MethodInvocation的proceed时又会进行入当前这个方法
// 然后去执行链条中的下一个拦截器
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}

总结

本文主要是为下篇文章做准备,下篇文章将会结束整个IOC流程的分析,IOC的最后一步便是为Bean创建代理。本文已经分析了代理的具体创建逻辑,在下篇文章中我们主要结合Spring的启动流程来看一看Spring是如何将通知添加到创建代理的配置信息中去的。

关于整个IOC跟AOP的模块还会有两篇文章,一篇用于结束整个IOC流程,另外一篇专门探讨Spring中循环依赖的解决。完成这两篇文章中,接下来打算用5到7篇文章对Spring的事务管理进行分析!

如果我的文章能帮到你,记得点个赞哈~!

如果本文对你有帮助的话,记得点个赞吧!也欢迎关注我的公众号,微信搜索:程序员DMZ,或者扫描下方二维码,跟着我一起认认真真学Java,踏踏实实做一个coder。

我叫DMZ,一个在学习路上匍匐前行的小菜鸟!

Spring中AOP相关的API及源码解析的更多相关文章

  1. Java生鲜电商平台-SpringCloud微服务架构中网络请求性能优化与源码解析

    Java生鲜电商平台-SpringCloud微服务架构中网络请求性能优化与源码解析 说明:Java生鲜电商平台中,由于服务进行了拆分,很多的业务服务导致了请求的网络延迟与性能消耗,对应的这些问题,我们 ...

  2. Spring中AOP相关源码解析

    前言 在Spring中AOP是我们使用的非常频繁的一个特性.通过AOP我们可以补足一些面向对象编程中不足或难以实现的部分. AOP 前置理论 首先在学习源码之前我们需要了解关于AOP的相关概念如切点切 ...

  3. 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和 ...

  4. spring默认启动位置以及contextConfigLocation设置源码解析

    这几天在看spring的源码,涉及到spring启动位置的部分,下面就看看spring到底是从哪儿开始加载的.本文使用的是spring3.0M3 首先spring的加载会借助一个监听器ContextL ...

  5. JUC中Lock和ReentrantLock介绍及源码解析

    Lock框架是jdk1.5新增的,作用和synchronized的作用一样,所以学习的时候可以和synchronized做对比.在这里先和synchronized做一下简单对比,然后分析下Lock接口 ...

  6. Soul API 网关源码解析 02

    如何读开源项目:对着文档跑demo,对着demo看代码,懂一点就开始试,有问题了问社区. 今日目标: 1.运行examples下面的 http服务 2.学习文档,结合divde插件,发起http请求s ...

  7. 【spring源码学习】spring的事件发布监听机制源码解析

    [一]相关源代码类 (1)spring的事件发布监听机制的核心管理类:org.springframework.context.event.SimpleApplicationEventMulticast ...

  8. Soul API 网关源码解析 03

    目标 使用 soul 代理 dubbo 服务 dubbo 服务如何注册到网关的? dubbo 插件是如何工作的? 理清 http --> 网关--> dubbo provider 整条链路 ...

  9. Django中CBV的执行顺序之源码解析

    浅析Django中的CBV的执行顺序 下图为CBV方式的执行顺序,大概执行流程如下: 其中浅蓝色为在假设自己写的类,即Test类中没有dispatch方法的情况下的执行顺序,当自己的类中有dispat ...

随机推荐

  1. Java实现蓝桥杯打印图形

    标题:打印图形 如下的程序会在控制台绘制分形图(就是整体与局部自相似的图形). 当n=1,2,3的时候,输出如下: 请仔细分析程序,并填写划线部分缺少的代码. n=1时: o ooo o n=2时: ...

  2. Java实现 LeetCode 133 克隆图

    133. 克隆图 给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆). 图中的每个节点都包含它的值 val(int) 和其邻居的列表(list[Node]). class Node { ...

  3. Java实现 LeetCode 127 单词接龙

    127. 单词接龙 给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度.转换需遵循如下规则: 每次转换只能改变一个字 ...

  4. java实现基因牛的繁殖

    基因牛的繁殖 基因牛 张教授采用基因干预技术成功培养出一头母牛,三年后,这头母牛每年会生出1头母牛, 生出来的母牛三年后,又可以每年生出一头母牛.如此循环下去,请问张教授n年后有多少头母牛? 以下程序 ...

  5. ElasticSearch系列之(一):介绍、安装(Docker、Windows、Linux)

    1.介绍 Elasticsearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口.Elasticsearch是用Java语言开发的,并 ...

  6. Hadoop之hadoop fs和hdfs dfs、hdfs fs三者区别

      适用范围 案例 备注 小记 hadoop fs 使用范围最广,对象:可任何对象       hadoop dfs 只HDFS文件系统相关       hdfs fs 只HDFS文件系统相关(包括与 ...

  7. DOM 元素的循环遍历

    ​博客地址:https://ainyi.com/89​ 获取 DOM 元素的几种方式 get 方式: getElementById getElementsByTagName getElementsBy ...

  8. Linux: ssh命令 远程登录

    1.查看SSH客户端版本 使用ssh -V命令可以得到版本号.需要注意的是,Linux一般自带的是OpenSSH; $ ssh -V ssh: SSH Secure Shell 3.2.9.1 (no ...

  9. Idea创建Scala的Maven项目

    Idea版本(2018.1.5) Scala版本(2.11.0) Java版本(1.8.0_151) 创建Scala的Maven项目 Idea新建项目如图,输入GroupId和ArtifactId之后 ...

  10. tp6 路由匹配参数获取问题

    tp6是一个封装度很高的框架,在大部分场景下都能做到开箱即用 本次遇到情况为,当请求消息体为索引数组时,路由参数无法正常获取 首先看正常路由匹配 路由定义 Route::post('test/:a/: ...