代码
@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方法。接着会得到空指针异常:
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类型来决定调用的是哪个方法。
相关推荐
总结,Spring 4.0与Maven的结合为我们提供了强大的企业级应用开发能力。通过深入理解并实践这个Demo,开发者可以更好地掌握Spring 4.0的核心特性和Maven的构建流程,为实际项目开发打下坚实基础。
Spring 是一个广泛使用的Java应用程序框架,它以控制反转(Inversion of Control, IoC)和面向切面编程(Aspect Oriented Programming, AOP)为核心,旨在简化企业级应用的开发。Spring 的轻量级特性使其在不增加...
这段代码会创建一个表单,其中的`modelAttribute`指定了与之关联的模型对象(User),`path`属性对应模型对象的属性名。 **6. 表单数据的处理** 当用户提交表单时,Spring MVC会自动将表单数据绑定到指定的模型对象...
7. **Spring Cloud OpenFeign**:Feign 是一个声明式的 Web 服务客户端,使得编写 HTTP 客户端变得更简单,它将服务接口和调用逻辑绑定在一起,提供了更简洁的编码方式。 8. **Spring Cloud Bus**:Spring Cloud ...
Spring MVC是Spring框架的一个模块,专门用于处理Web应用的请求-响应模型。它遵循Model-View-Controller(MVC)设计模式,将业务逻辑、数据和用户界面分离开来,使得代码更易于管理和测试。在Spring MVC中,控制器...
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. ...
3. **Spring Remoting**: 这是Spring框架的一部分,它允许Flex客户端与Spring应用服务器端的服务进行远程调用。常用的方式有HTTPInvoker和HTTPService。 **三、开发流程** 1. **设置项目结构**: 创建Spring MVC...
Spring框架是Java开发中的核心组件,它为应用程序提供了一个全面的基础设施,包括依赖注入(DI)、面向切面编程(AOP)以及各种企业级服务。本教程专为初学者设计,旨在帮助“菜鸟”快速掌握Spring的基本概念和实战...
Ibatis的核心理念是SQL映射,通过XML或注解方式将Java对象与数据库表字段进行绑定,实现了数据访问层的解耦。在本案例中,你将看到如何创建Mapper接口,编写XML配置文件,以及如何在Service层中调用这些Mapper方法...
- **属性绑定**:Spring Boot支持将配置文件中的配置项自动绑定到对应的JavaBean中,大大简化了配置的读取和使用。 2. **Spring Boot Starter**: - **依赖管理**:Spring Boot提供了一组便捷的starter依赖,帮助...
这段代码会在JavaScript中显示错误信息,同时将其存储在一个隐藏的输入字段中,以便后续处理。 总之,`spring:bind` 和 `BindStatus` 是Spring MVC中用于数据绑定和验证的强大工具,它们使得开发者能够方便地处理...
在IT行业中,Spring框架是Java应用开发中的一个关键组件,它提供了一整套服务管理和配置方式,极大地简化了企业级应用的开发。而CXF是一个开源的服务栈,用于构建和开发Web服务。当我们需要在Spring环境中发布Web...
这段代码描述的是如何创建一个需要通过URL参数访问的接口,用于模拟获取商品列表。下面将详细解释这个过程。 首先,Spring Boot提供了`@RestController`注解,用于标记一个类作为RESTful控制器。在这个控制器类中,...
Spring MVC、MyBatis 实现的员工管理系统是一个典型的Java Web应用程序,它利用了Spring框架的MVC模式和MyBatis作为持久层框架。这个系统旨在帮助管理员工信息,包括增删改查等基本操作,同时也提供了对数据的高效...
XFire的工作流程包括一系列处理阶段,如传输、预转发、转发、策略实施、用户信息处理、预调用和服务调用等,每个阶段都可以插入自定义Handler来处理和转换消息。 集成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 Web Flow是Spring Framework的一个扩展模块,专门设计用于处理复杂的、多步骤的web应用程序流程。版本2.30是其一个重要的迭代,包含了多项更新与优化,为开发者提供了更加强大且灵活的工作流管理能力。本文将...
- 基于Vue.js:Sword v3.5.0采用Vue.js作为主要的前端框架,Vue.js是一个轻量级、高性能的前端MVVM库,提供了数据绑定和组件化功能,使得代码组织更加清晰,维护更加容易。 - UI组件库:Sword可能使用了Element UI...
这段代码告诉Spring使用AspectJ的注解驱动的AOP支持。 接下来,我们需要定义切面。切面可以通过`<aop:aspect>`标签创建,如下所示: ```xml ... ``` 这里,`id`是切面的唯一标识,`ref`引用的是包含通知逻辑的...