`
jinnianshilongnian
  • 浏览: 21456698 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2409463
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:3001129
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5634231
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:258509
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1594522
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:249423
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5850937
Group-logo
跟我学Nginx+Lua开...
浏览量:699492
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:781962
社区版块
存档分类
最新评论

一段Spring代码引起的调用绑定总结

阅读更多

代码

@Component
public class B {
    void test() {
        System.out.println("hello");
    }
}
@Component
public class A {
    @Autowired
    private B b;
    public final void test() {
        b.test();
    }
}

 

@Component
@Aspect
public class MyAspect {
    @Before("execution(* *(..))")
    public void before() {

    }
}

 

@Configuration
@ComponentScan
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx =
                new AnnotationConfigApplicationContext(Test.class);
        A a = ctx.getBean(A.class);
        a.test();
    }
}

 

问题 

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方法()”是属于编译期绑定,所以会抛出如上的空指针异常。也就是此问题还是因为对象与方法的绑定问题造成的。

 

调用绑定

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

 

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

public class StaticBindTest {
    static class A {
        public int i = 1;
        public static void hello() {
            System.out.println("1");
        }
    }
    static class B extends A {
        public int i = 2;
        public static void hello() {
            System.out.println("2");
        }
    }

    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.i);
        a.hello();
    }
}

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

 

还有如:

public class StaticBindTest2 {
    static class A {
        public void hello(Number i) {
            System.out.println("Number");
        }
        public void hello(Integer i) {
            System.out.println("Integer");
        }
        public void hello(Long i) {
            System.out.println("Long");
        }
    }
    public static void main(String[] args) {
        A a = new A();
        Number i = Integer.valueOf(1);
        Number l = Long.valueOf(1L);
        a.hello(i);
        a.hello(l);
    }
}

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

 

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

 

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

 

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

public class DynamicBindTest {
    static class A {
        public void hello() {
            System.out.println("a");
        }
    }
    static class B extends A {
        public void hello() {
            System.out.println("b");
        }
    }

    public static void main(String[] args) {
        A a = new B();
        a.hello();
    }
}

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

 

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

 

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

 

单分派与双分派

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

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

 

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

 

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

 

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

public class DoubleDispatchTest {

    static interface Element {
        public void accept(Visitor v);
    }
    static class AElement implements Element {
        public void accept(Visitor v) {
            v.visit(this);
        }
    }
    static class BElement implements Element {
        public void accept(Visitor v) {
            v.visit(this);
        }
    }

    static interface Visitor {
        public void visit(AElement aElement);
        public void visit(BElement bElement);
    }

    static class Visitor1 implements Visitor {
        public void visit(AElement aElement) {
            System.out.println("1A");
        }
        public void visit(BElement bElement) {
            System.out.println("1B");
        }
    }

    static class Visitor2 implements Visitor {
        public void visit(AElement aElement) {
            System.out.println("2A");
        }
        public void visit(BElement bElement) {
            System.out.println("2B");
        }
    }


    public static void main(String[] args) {
        Element a = new AElement();
        Element b = new BElement();
        Visitor v1 = new Visitor1();
        Visitor v2 = new Visitor2();
        a.accept(v1);
        a.accept(v2);
        b.accept(v1);
        b.accept(v2);
    }
}

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

 

 

11
2
分享到:
评论
2 楼 fair_jm 2014-11-06  
好文 最近刚遇到这个问题 final方法中有autowired的属性 导致调用时空指针
1 楼 sgq0085 2014-03-04  

相关推荐

    spring、spring-mvc学习总结-.pdf

    Spring 是一个广泛使用的Java应用程序框架,它以控制反转(Inversion of Control, IoC)和面向切面编程(Aspect Oriented Programming, AOP)为核心,旨在简化企业级应用的开发。Spring 的轻量级特性使其在不增加...

    Spring web MVC和spring 2.0 form tag解说

    这段代码会创建一个表单,其中的`modelAttribute`指定了与之关联的模型对象(User),`path`属性对应模型对象的属性名。 **6. 表单数据的处理** 当用户提交表单时,Spring MVC会自动将表单数据绑定到指定的模型对象...

    spring菜鸟入门经典实例

    Spring框架是Java开发中的核心组件,它为应用程序提供了一个全面的基础设施,包括依赖注入(DI)、面向切面编程(AOP)以及各种企业级服务。本教程专为初学者设计,旨在帮助“菜鸟”快速掌握Spring的基本概念和实战...

    Spring.net框架

    本部分代码仅仅提供一种功能演示,如果实际应用仍需进一步完善(建议使用一些成型的Ioc框架,例如Spring.net或Castle等)。经过改造后 的系统,组件间依赖关系如下图: 可以看出这次实现了真正的“针对接口编程”...

    Ibatis+Spring+struts完整代码案例

    Ibatis的核心理念是SQL映射,通过XML或注解方式将Java对象与数据库表字段进行绑定,实现了数据访问层的解耦。在本案例中,你将看到如何创建Mapper接口,编写XML配置文件,以及如何在Service层中调用这些Mapper方法...

    springbind BindStatus

    这段代码会在JavaScript中显示错误信息,同时将其存储在一个隐藏的输入字段中,以便后续处理。 总之,`spring:bind` 和 `BindStatus` 是Spring MVC中用于数据绑定和验证的强大工具,它们使得开发者能够方便地处理...

    Spring-Reference_zh_CN(Spring中文参考手册)

    14.5.1. 写在段首 14.5.1.1. Bean 定义 14.5.1.2. 标准MVC控制器代码 14.5.1.3. 把模型数据转化为XML 14.5.1.4. 定义视图属性 14.5.1.5. 文档转换 14.5.2. 小结 14.6. 文档视图(PDF/Excel) 14.6.1. 简介 14.6.2. ...

    spring xfire

    XFire的工作流程包括一系列处理阶段,如传输、预转发、转发、策略实施、用户信息处理、预调用和服务调用等,每个阶段都可以插入自定义Handler来处理和转换消息。 集成Spring的优点在于能够实现服务的无缝集成和管理...

    Spring中文帮助文档

    14.5.1. 写在段首 14.5.2. 小结 14.6. 文档视图(PDF/Excel) 14.6.1. 简介 14.6.2. 配置和安装 14.7. JasperReports 14.7.1. 依赖的资源 14.7.2. 配置 14.7.3. 构造ModelAndView 14.7.4. 使用子报表 14.7...

    Spring API

    14.5.1. 写在段首 14.5.2. 小结 14.6. 文档视图(PDF/Excel) 14.6.1. 简介 14.6.2. 配置和安装 14.7. JasperReports 14.7.1. 依赖的资源 14.7.2. 配置 14.7.3. 构造ModelAndView 14.7.4. 使用子报表 14.7...

    jsf2+spring sample

    3. **MVC架构**:Spring MVC是Spring框架的一部分,提供了一种清晰的模型-视图-控制器架构,便于分离关注点。 4. **数据访问抽象**:Spring支持多种数据源的访问,包括JDBC、Hibernate、MyBatis等,简化了数据操作...

    Spring 2.0 开发参考手册

    14.5.1. 写在段首 14.5.2. 小结 14.6. 文档视图(PDF/Excel) 14.6.1. 简介 14.6.2. 配置和安装 14.7. JasperReports 14.7.1. 依赖的资源 14.7.2. 配置 14.7.3. 构造ModelAndView 14.7.4. 使用子报表 14.7...

    spring chm文档

    Spring Framework 开发参考手册 Rod Johnson Juergen Hoeller Alef Arendsen Colin Sampaleanu Rob Harrop Thomas Risberg Darren Davison Dmitriy Kopylenko Mark Pollack Thierry Templier Erwin ...

    Struts2+Hibernate+Spring基于单表的增删改查code

    学习这个项目,你可以掌握SSH框架的集成方法,了解如何通过Struts2接收用户请求并调用业务逻辑,由Hibernate处理数据库操作,最后Spring管理整个流程。同时,这也是一个很好的实践,帮助初学者理解Web开发中模型、...

    java类代码和xml配置文件.zip

    在Java类代码方面,通常我们会定义一个实现了特定接口的类,这个接口是服务的契约,定义了客户端可以调用的方法。例如,我们可以定义一个名为`HelloWorldService`的接口,其中包含一个`sayHello()`方法,然后创建一...

    Spring+Struts2+ibatis+Extjs 整合 图书管理系统实例

    在本项目中,我们探讨的是一个基于Java技术栈的图书管理系统实现,具体采用了Spring、Struts2、iBatis和ExtJS这四个核心技术。这个系统旨在提供全面的图书管理功能,包括图书的增删改查、用户管理以及可能的借阅、...

    Spring+xFire+wss4j配置Helloworld完整Demo.rar

    而在客户端,可能包含了一段代码,展示了如何配置Spring与xFire集成,调用这个安全的Web服务,并处理相关的安全认证。 为了完成这个示例,开发者需要了解以下步骤: 1. 配置Spring:设置Spring的IoC容器,定义服务...

    Spring面试题

    在对由三部分组成的 Spring 系列 的第 1 部分进行总结时,我使用了一个示例,演示了如何通过 Spring IOC 容器注入应用程序的依赖关系(而不是将它们构建进来)。 我用开启在线信用帐户的用例作为起点。对于该实现,...

    ExtJS+Spring+Hibernate

    总结来说,这个项目展示了如何利用现代Web开发技术栈,即ExtJS作为前端界面、Spring作为后端控制器以及Hibernate作为数据持久化层,构建一个完整的Web应用。这种架构方式可以实现高效的开发流程,同时确保系统的可...

Global site tag (gtag.js) - Google Analytics