代码

Java代码  

  1. @Component
  2. public class B {
  3. void test() {
  4. System.out.println("hello");
  5. }
  6. }

Java代码  

  1. @Component
  2. public class A {
  3. @Autowired
  4. private B b;
  5. public final void test() {
  6. b.test();
  7. }
  8. }

Java代码  

  1. @Component
  2. @Aspect
  3. public class MyAspect {
  4. @Before("execution(* *(..))")
  5. public void before() {
  6. }
  7. }

Java代码  

  1. @Configuration
  2. @ComponentScan
  3. @EnableAspectJAutoProxy(proxyTargetClass = true)
  4. public class Test {
  5. public static void main(String[] args) {
  6. AnnotationConfigApplicationContext ctx =
  7. new AnnotationConfigApplicationContext(Test.class);
  8. A a = ctx.getBean(A.class);
  9. a.test();
  10. }
  11. }

问题

1、A通过字段注入方式注入B ;

2、A的test方法是final的,因此该方法不能被代理;

3、被代理的对象的调用顺序:

Proxy.test()

--->Aspect Before/Around Advice

---->Target.test()

---->Aspect After/Around Advice

即当某个目标对象被代理后,我们首先调用代理对象的方法,其首先调用切面的前置增强/环绕增强,然后调用目标对象的方法,最后调用后置/环绕增强完成整个调用流程。

但是我们知道如果是基于CGLIB的代理:

final的类不能生成代理对象;因为final的类不能生成代理对象;

final的方法不能被代理;但是还是能生成代理对象的;

在我们的示例里,A的test方法是无法被代理的,但是A还是会生成一个代理对象(因为我们的切入点是execution(* *(..)),还是可以对如toString()之类的方法代理的):

即如果调用a.toString()相当于:

proxy.toString() [com.github.zhangkaitao.A$$EnhancerByCGLIB$$73d79efe]

---->MyAspect.before()

----->target.toString() [com.github.zhangkaitao.A]

但是如果调用a.test()相当于:

proxy.test() [com.github.zhangkaitao.A$$EnhancerByCGLIB$$73d79efe]

当我们直接调用生成的代理对象的test方法。接着会得到空指针异常:

写道

Exception in thread "main" java.lang.NullPointerException
at com.github.zhangkaitao.A.test(A.java:16)
at com.github.zhangkaitao.Test.main(Test.java:21)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

从异常可以看出是A.test()方法中的b对象是空;

但是我们发现b对象是注入了,但是注入给的是目标对象,而代理对象是没有注入的,请看debug信息:

从上图可以看出,目标对象的b注入了;而生成的代理对象的b是没有值的;又因为我们调用“代理对象.final方法()”是属于编译期绑定,所以会抛出如上的空指针异常。也就是此问题还是因为对象与方法的绑定问题造成的。

调用绑定

所谓调用绑定,即当我们使用“对象.字段”/“对象.方法()”调用时,对象与字段/方法之间是如何绑定的;此处有两种绑定:编译期绑定与运行期绑定。

编译期绑定:对象与字段/方法之间的绑定关系发生在写代码期间(即编译期间),即它们的关系在编译期间(写完代码)就确定了,如:

Java代码  

  1. public class StaticBindTest {
  2. static class A {
  3. public int i = 1;
  4. public static void hello() {
  5. System.out.println("1");
  6. }
  7. }
  8. static class B extends A {
  9. public int i = 2;
  10. public static void hello() {
  11. System.out.println("2");
  12. }
  13. }
  14. public static void main(String[] args) {
  15. A a = new B();
  16. System.out.println(a.i);
  17. a.hello();
  18. }
  19. }

如上代码将输出1,即A的i值,而不是B的i值;这就是所谓的编译期绑定,即访问的字段/方法绑定到声明类型上,而不是运行时的那个对象的类型上。

还有如:

Java代码  

  1. public class StaticBindTest2 {
  2. static class A {
  3. public void hello(Number i) {
  4. System.out.println("Number");
  5. }
  6. public void hello(Integer i) {
  7. System.out.println("Integer");
  8. }
  9. public void hello(Long i) {
  10. System.out.println("Long");
  11. }
  12. }
  13. public static void main(String[] args) {
  14. A a = new A();
  15. Number i = Integer.valueOf(1);
  16. Number l = Long.valueOf(1L);
  17. a.hello(i);
  18. a.hello(l);
  19. }
  20. }

都讲输出Number,而不是Integer和Long;这也是编译期绑定;即方法参数绑定时根据声明时的类型进行绑定也叫做静态绑定/早绑定。

如果我们使用“a.hello(null);”调用会发生什么情况呢?此时就会发生二义性,即绑定到Integer/Long参数上都可以的,所以我们应该使用“a.hello((Integer)null);”来强制调用。还有在绑定时都是先子类型(Integer/Long)到父类型(Number)进行绑定。

编译期绑定:调用的都是声明的类型的字段/方法或者根据参数声明时类型调用重载方法;静态字段/方法、private/final方法、实例对象的字段/重载方法都是编译期绑定,即除了方法覆盖都是编译期绑定;也可以说成除了运行期绑定之外的绑定都是编译期绑定。为什么这么说呢?接着往下看。

运行期绑定“对象.方法()”是根据程序运行期间对象的实际类型来绑定方法的,如:

Java代码  

  1. public class DynamicBindTest {
  2. static class A {
  3. public void hello() {
  4. System.out.println("a");
  5. }
  6. }
  7. static class B extends A {
  8. public void hello() {
  9. System.out.println("b");
  10. }
  11. }
  12. public static void main(String[] args) {
  13. A a = new B();
  14. a.hello();
  15. }
  16. }

如上代码将输出b,即说明了hello()方法调用不是根据声明时类型决定,而是根据运行期间的那个对象类型决定的;也叫做动态绑定/迟绑定。

运行期绑定:“对象.方法()”是根据运行期对象的实际类型决定的;即new的哪个对象就绑定该方法到那个对象类型上;只有方法覆盖是运行期绑定;其他都是编译期绑定;该机制用于实现多态。

在Java中,除了方法覆盖是运行期绑定,其他都是静态绑定就好理解了。

单分派与双分派

单分派:调用对象的方法是由对象的类型决定的;

多分派:调用对象的方法是由对象的类型决定的和其他因素(如方法参数类型)决定的;双分派是多分派的特例。

Java是一种单分派语言,可以通过如访问者设计模式来模拟多分派。

比如之前的重载的编译期绑定,和覆盖的运行期绑定,都是根据对象类型(不管是声明时类型/运行时类型)决定调用的哪个方法;跟方法参数实际运行时类型无关(而与声明时类型有关)。

接下来看一个双分派的例子:

Java代码  

  1. public class DoubleDispatchTest {
  2. static interface Element {
  3. public void accept(Visitor v);
  4. }
  5. static class AElement implements Element {
  6. public void accept(Visitor v) {
  7. v.visit(this);
  8. }
  9. }
  10. static class BElement implements Element {
  11. public void accept(Visitor v) {
  12. v.visit(this);
  13. }
  14. }
  15. static interface Visitor {
  16. public void visit(AElement aElement);
  17. public void visit(BElement bElement);
  18. }
  19. static class Visitor1 implements Visitor {
  20. public void visit(AElement aElement) {
  21. System.out.println("1A");
  22. }
  23. public void visit(BElement bElement) {
  24. System.out.println("1B");
  25. }
  26. }
  27. static class Visitor2 implements Visitor {
  28. public void visit(AElement aElement) {
  29. System.out.println("2A");
  30. }
  31. public void visit(BElement bElement) {
  32. System.out.println("2B");
  33. }
  34. }
  35. public static void main(String[] args) {
  36. Element a = new AElement();
  37. Element b = new BElement();
  38. Visitor v1 = new Visitor1();
  39. Visitor v2 = new Visitor2();
  40. a.accept(v1);
  41. a.accept(v2);
  42. b.accept(v1);
  43. b.accept(v2);
  44. }
  45. }

此处可以看出如"a.accept(v)",根据Element类型和Visitor类型来决定调用的是哪个方法。

一段Spring代码引起的调用绑定总结相关推荐

  1. js调用android代码怎么写,Android端使用WebView注入一段js代码实现js调用android

    需求:为网页上个链接增加点击事件,但是这个链接无法增加js代码 url:http://public.rongcloud.cn/view/D4F444BE2D94D760329F3CF38B4AE35C ...

  2. Spring常见问题解决 - AOP调用被拦截类的属性报NPE

    Spring常见问题解决 - AOP调用被拦截类的属性报NPE 一. 案例复现 二. 被拦截类的属性为何是null? 2.1 原理分析 2.2 解决 2.2.1 为何加一个 get 方法就可以避免NP ...

  3. 一小段jQuery代码的分析与优化

    今天刚回家,QQ群里就看到有人求助优化一段jQuery代码,简单看了一下,发现如果对jQuery这东西只停留在用的层面,而不知其具体实现的话,真的很容易用出问题来.这也是为什么近期我一直不怎么推崇用j ...

  4. BSS段,数据段,代码段,堆内存和栈

    BSS段,数据段,代码段,堆内存和栈 作者:delnabla 日期:2010-10-26 字体大小: 小 中 大 BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的 ...

  5. C语言中内存分布及程序运行中(BSS段、数据段、代码段、堆栈)

    BSS段:(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域.BSS是英文Block Started by Symbol的简称.BSS段属于静态内存分配. 数据段 :数据 ...

  6. Spring的refresh()方法调用过程

    Spring的refresh()方法调用过程 refresh()是Spring中比较核心的方法,Spring所有的初始化都在这个方法中完成 具体代码如下 public void refresh() t ...

  7. BSS段、数据段、代码段、堆与栈

    BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域.BSS是英文Block Started by Symbol的简称.BSS段属于静态内存分配. 数据段 ...

  8. Linux中的内存段(BSS、数据段、代码段、堆、栈)

    在Linux 系统中,在运行一个程序时,程序中未初始化的全局变量会被加载到以下哪个内存段中? BSS(Block Started by Symbol)通常是指用来存放程序中未初始化的全局变量和静态变量 ...

  9. 请求网页时,怎么给我返回了一段 JavaScript 代码

    今天给大家带来的是一个论坛网站,牛仔俱乐部-努比亚社区, 网址为:https://bbs.nubia.cn/ 如果你想要获取这个网站的源代码的话,必须要先获取一个 cookie,其字段名为:acw_s ...

最新文章

  1. 操作系统经典书籍--现代操作系统
  2. java工程师之旅-一个月工作心得
  3. 微软遭遇滑铁卢,chrome成为最受欢迎浏览器
  4. java打印等边三角市,java 打印菱形和等边三角形
  5. hibernate版本_Spring Boot入门(最新基于SpringBoot2.2.2版本系列教程)
  6. 生成缩略图代码(转帖)
  7. Access操作的注意事项
  8. 分享一个好用的网页pdf打印插件
  9. 倒立摆小车matlab仿真,倒立摆系统的建模及Matlab仿真分析
  10. 搜索引擎 —海量数据搜索
  11. 连接共享打印机0x0000011b win10
  12. 什么杀毒软件最好|什么杀毒软件好用
  13. 使用超链接实现企业QQ在线客服
  14. mppdbLibra
  15. 基于STM32的ESP8266获取心知天气数据
  16. 为什么都是技术合伙人被踢出局?
  17. airplus.exe
  18. 计算机端口怎么配置波特率,PLC波特率设置
  19. 剑指 Offer 05. 替换空格无标题(正则表达式)
  20. 奥塔在线:架构实践基础之一图胜千言

热门文章

  1. 为安装有系统及应用的服务器更换硬盘方法一例
  2. 扩展筛选LightOj 1054 Efficient Pseudo Code
  3. 《MongoDB权威指南》读书笔记 第一章 简介
  4. 防止程序启动两次的方法CreateMutex()
  5. 【TensorFlow】ValueError: Shape must be rank 1 but is rank 0 for ' ’ with input shapes: [].问题
  6. FSM(状态机)、HFSM(分层状态机)、BT(行为树)的区别
  7. TCP之深入浅出send和recv
  8. 吴恩达深度学习课程deeplearning.ai课程作业:Class 1 Week 4 assignment4_2
  9. pygame做的著名游戏_pygame教程(十):汉诺塔游戏
  10. wofstream写中文失败,导致stream对象badbit