[关闭]
@Otokaze 2018-12-11T10:02:55.000000Z 字数 84147 阅读 465

Spring 笔记

Java

Spring 简介

Spring 框架的七大模块:

Spring Web 包是用来与其它 Web 框架集成用的,如 Struts2;而 Spring Web MVC 包是 Spring 提供的一个 MVC 框架,别搞混了,使用 Spring MVC 只需导入 spring-webmvc。

Spring 最核心的两个概念就是 IoC 和 AOP,IoC 是控制反转的英文缩写,AOP 是面向切面编程的英文缩写。

最基本的 spring 应用程序需要导入的包为:spring-corespring-context。建议使用 maven 等自动构建工具来管理你的项目,因为这些自动构建工具通常都自带完善的 java 依赖包解决方案,本文使用 maven。

更新:对于基本的 spring 应用程序(IoC 容器),只需引入 spring-context 依赖就行,因为 spring-context 依赖于 spring-core,所以不需要显式引入 spring-core。

Spring Maven 依赖表

Hello World

编辑 pom.xml,引入 spring-core 和 spring-context 依赖:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project>
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.zfl9</groupId>
  5. <artifactId>JavaSE_HelloWorld</artifactId>
  6. <version>1.0-SNAPSHOT</version>
  7. <properties>
  8. <maven.test.skip>true</maven.test.skip>
  9. <maven.compiler.source>1.8</maven.compiler.source>
  10. <maven.compiler.target>1.8</maven.compiler.target>
  11. <spring.version>4.3.20.RELEASE</spring.version>
  12. </properties>
  13. <dependencies>
  14. <dependency>
  15. <groupId>org.springframework</groupId>
  16. <artifactId>spring-core</artifactId>
  17. <version>${spring.version}</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.springframework</groupId>
  21. <artifactId>spring-context</artifactId>
  22. <version>${spring.version}</version>
  23. </dependency>
  24. </dependencies>
  25. </project>

创建 HelloWorld.java 类(Bean):

  1. package com.zfl9;
  2. public class HelloWorld {
  3. private String message;
  4. public HelloWorld() {
  5. }
  6. public String getMessage() {
  7. return message;
  8. }
  9. public void setMessage(String message) {
  10. this.message = message;
  11. }
  12. @Override
  13. public String toString() {
  14. return String.format("message: %s", this.message);
  15. }
  16. }

HelloWorld 类是一个标准的 Java Bean(或者说 POJO),即无参构造函数、私有属性的 getter 与 setter 方法。但因为 HelloWorld 类并没有实现序列化接口,所以我更喜欢称它为 POJO(即:普通 Java 类)。

创建 MainApp.java 类(运行的主类):

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. HelloWorld helloworld = (HelloWorld) context.getBean("helloworld");
  8. System.out.println(helloworld);
  9. }
  10. }

MainApp 类是一个 Main 可运行类,ApplicationContext 是一个 bean 容器(也就是 IoC 容器,其实你完全可以将 IoC 容器理解为一个 Map 对象,key 就是 bean 对象的 ID,value 就是 bean 对象的引用)。ApplicationContext 是一个借口,用 Spring 里面的术语叫做 Bean 容器/工厂,而 ClassPathXmlApplicationContext 是 ApplicationContext 接口的一个实现类,构造参数 beans.xml 表示 IoC 容器的配置文件名(很容易知道,beans.xml 应该能够在应用程序的 CLASSPATH 路径中找到)。

当我们调用 context 的 getBean 方法时,需要提供 bean 对象对应的 ID(见 beans.xml 文件内容),调用后,context 内部会返回对应的 bean 对象的引用给我们(返回的类型为 Object),需进行强制类型转换。

beans.xml Spring 配置文件内容:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="helloworld" class="com.zfl9.HelloWorld">
  6. <property name="message" value="Hello, Spring!"/>
  7. </bean>
  8. </beans>

根元素为 beans,主要元素为 bean,bean 元素有两个基本属性:idclass,id 为 bean 对象的所属 ID,class 为 bean 对象的所属类名(全限定类名),当 Spring 实例化 bean 对象时,会执行类似 Object obj = new com.zfl9.HelloWorld() 的语句,即调用对应类的无参构造函数,这也是为什么我们的 bean 类需要一个无参构造函数(好吧,其实也不是必须,但是通常是这样的,毕竟 spring 允许我们传递构造参数给 bean 类)。而 bean 元素中的 property 元素则对应 setter 方法,比如上面的配置,就是调用对应的 setMessage() 方法,将 value 值传递给 bean 实例。

运行我们的 hello world 程序,将得到如下输出:

  1. message: Hello, Spring!

IoC 容器

Spring 容器是 Spring Framework 的核心。容器将创建对象,将它们连接在一起,配置它们,并管理从创建到销毁的整个生命周期。Spring 容器使用 DI 来管理组成应用程序的组件。这些对象称为 Spring Beans。容器通过读取提供的配置元数据获取有关要实例化,配置和组装的对象的指令。配置元数据可以由 XML,Java 注释或 Java 代码表示。

Spring 提供两种常用的 Bean 容器:

我们上面的 HelloWorld 程序用的就是第二种,通常我们也是使用第二种容器,因为他提供许多有用的功能,但是也并不是说第一种 BeanFactory 就一无是处了,在资源有限的条件下,BeanFactory 的速度更快。注意,ApplicationContext 属于 spring-context 包,而 BeanFactory 属于 spring-core 包,ApplicationContext 是 BeanFactory 的子接口。

除非有充分的理由(比如资源有限,如手机、applet),你才应该考虑使用 BeanFactory。一般情况下,建议使用 ApplicationContext,因为后者提供更多功能,ApplicationContext 是 BeanFactory 的子接口。

最常用的 ApplicationContext 实现类:

Bean 定义

构成应用程序主干并由 Spring IoC 容器管理的对象称为 bean。bean 是一个由 Spring IoC 容器实例化,组装和管理的对象。这些 bean 是使用您提供给容器的配置元数据创建的。例如,以前面章节中已经看到的 XML <bean/> 定义的形式。

Bean 定义包含称为 配置元数据 的信息,容器需要知道以下内容

这些配置元数据通常都可以使用以下属性表示:

配置 bean 元数据的方法

Spring 3 开始,支持 JavaConfig 形式的配置,可以没有 xml 文件,不过就目前来说,spring.xml 形式仍然是主流形式,不过我们最好还是来熟悉一下 JavaConfig 的配置形式,免得以后看到这种配置而一脸懵逼。

XML 配置形式:
spring.xml

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://www.springframework.org/schema/beans
  4. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  5. <bean id="helloBean" class="com.mkyong.hello.impl.HelloWorldImpl">
  6. </beans>

JavaConfig 配置形式

  1. package com.mkyong.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import com.mkyong.hello.HelloWorld;
  5. import com.mkyong.hello.impl.HelloWorldImpl;
  6. @Configuration
  7. public class AppConfig {
  8. @Bean(name="helloBean")
  9. public HelloWorld helloWorld() {
  10. return new HelloWorldImpl();
  11. }
  12. }

XML 形式的容器初始化:

  1. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

JavaConfig 形式的容器初始化:

  1. ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

注意,JavaConfig 形式又称为 Java-Based 形式,都是一个意思,另外,Spring MVC 上如果要使用 Java-Based 配置形式,需要弄什么 WebApplicationInitializer,总之我感觉不如直接配置 web.xml 和 mvc.xml 来的直接。

最常用的是 XML 配置文件与 Java 注解形式,目前先介绍 XML 文件形式。注解形式后面会详细介绍,但在这之前,我们还是需要先了解基本配置方法以及其他一些重要的基础知识。XML 的常用配置(结合上面的信息):

  1. <?xml version = "1.0" encoding = "UTF-8"?>
  2. <beans xmlns = "http://www.springframework.org/schema/beans"
  3. xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation = "http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <!-- A simple bean definition -->
  7. <bean id = "..." class = "...">
  8. <!-- collaborators and configuration for this bean go here -->
  9. </bean>
  10. <!-- A bean definition with lazy init set on -->
  11. <bean id = "..." class = "..." lazy-init = "true">
  12. <!-- collaborators and configuration for this bean go here -->
  13. </bean>
  14. <!-- A bean definition with initialization method -->
  15. <bean id = "..." class = "..." init-method = "...">
  16. <!-- collaborators and configuration for this bean go here -->
  17. </bean>
  18. <!-- A bean definition with destruction method -->
  19. <bean id = "..." class = "..." destroy-method = "...">
  20. <!-- collaborators and configuration for this bean go here -->
  21. </bean>
  22. </beans>

Bean 作用域

作用域就是前面提到的 scope 属性,所谓作用域其实就是 bean 对象的生命周期,是的,叫做生命周期通常更好理解。默认支持以下五种生命周期,其中前两种是所有 spring 容器都可用,后三种只在支持 web 的 application context 中可用(比如 spring mvc 中可以使用后三种作用域,前两种是通用的):

通常 global-session 不会被用到,所以我们可以说 spring 里面的 bean 作用域有四个,分别是 单例模式原型模式请求范围会话范围。后两个用于 web,比较好理解,单例模式是指同一时间,同一个 IoC 容器中只存在同一个 bean 类的实例,这是默认作用范围,而原型则是每次 getBean() 返回的对象都不相同。

Bean 回调

在某些时候,我们可能需要在构造 bean 时执行一些初始化操作,然后在销毁 bean 时执行一些资源释放操作,spring 允许我们这么做,我们只需使用 init-methoddestroy-method 属性告诉 spring,对应的 init 方法名和 destroy 方法名(init、destroy 方法签名不应该接收任何参数,返回任何值)。

需要注意的是,如果在非 web 环境中使用 spring 的 IoC 容器,且注册了 destroy-method 回调,请记得对 IoC 容器对象调用 context.registerShutdownHook() 方法,来向 JVM 注册 shutdown hook 钩子,不然 destroy hook 是不会被执行的。之所以 web 环境中不需要这么做是因为 web 容器会确保 Spring 的 IoC 容器正常 shutdown。

HelloWorld 类:

  1. package com.zfl9;
  2. public class HelloWorld {
  3. private String message;
  4. public HelloWorld() {
  5. }
  6. public String getMessage() {
  7. return message;
  8. }
  9. public void setMessage(String message) {
  10. this.message = message;
  11. }
  12. @Override
  13. public String toString() {
  14. return String.format("message: %s", this.message);
  15. }
  16. public void init() {
  17. System.out.println("init bean");
  18. }
  19. public void destroy() {
  20. System.out.println("destroy bean");
  21. }
  22. }

MainApp 类:

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.AbstractApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. public class MainApp {
  6. public static void main(String[] args) {
  7. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  8. ((AbstractApplicationContext) context).registerShutdownHook();
  9. HelloWorld helloworld = (HelloWorld) context.getBean("helloworld");
  10. System.out.println(helloworld);
  11. }
  12. }

beans.xml 配置:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="helloworld" class="com.zfl9.HelloWorld" init-method="init" destroy-method="destroy">
  6. <property name="message" value="Hello, Spring!"/>
  7. </bean>
  8. </beans>

运行结果:

  1. init bean
  2. message: Hello, Spring!
  3. destroy bean

如果你有太多的 bean 具有相同名称的初始化或销毁方法,则不需要在每个单独的 bean 上声明 init-method 和 destroy-method。相反,框架提供了使用 <beans> 元素上的 default-init-methoddefault-destroy-method 属性配置此类情况的灵活性,如下所示 :

  1. <beans xmlns = "http://www.springframework.org/schema/beans"
  2. xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation = "http://www.springframework.org/schema/beans
  4. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
  5. default-init-method = "init"
  6. default-destroy-method = "destroy">
  7. <bean id = "..." class = "...">
  8. <!-- collaborators and configuration for this bean go here -->
  9. </bean>
  10. </beans>

Bean 处理器

BeanPostProcessor 是用来扩展默认的 Spring IoC 容器的初始化逻辑,即在 spring 创建 bean 对象前调用某些方法,在 spring 创建 bean 对象之后调用某些方法,我们来看一下这个简单的例子。

HelloWorld 类不变,我们添加一个新的类,为 HelloWorld bean 添加 post 处理器,InitHelloWorld

  1. package com.zfl9;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.beans.factory.config.BeanPostProcessor;
  4. public class InitHelloWorld implements BeanPostProcessor {
  5. @Override
  6. public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
  7. System.out.println("<pre-init> beanName: " + name);
  8. return bean;
  9. }
  10. @Override
  11. public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
  12. System.out.println("<post-init> beanName: " + name);
  13. return bean;
  14. }
  15. }

然后修改 beans.xml 文件,注册一个新的 bean,class 为 InitHelloWorld,可以有多个 post 处理器:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="helloworld" class="com.zfl9.HelloWorld" init-method="init" destroy-method="destroy">
  6. <property name="message" value="Hello, Spring!"/>
  7. </bean>
  8. <bean class="com.zfl9.InitHelloWorld"/>
  9. </beans>

运行结果如下:

  1. <pre-init> beanName: helloworld
  2. init bean
  3. <post-init> beanName: helloworld
  4. message: Hello, Spring!
  5. destroy bean

注意,这个 BeanPostProcessor 是针对所有 bean 都有效的。

Bean 继承

子 bean 将从父 bean 中继承配置数据,当然子 bean 中可以覆盖父 bean 中定义的值也可以添加新的值,这与 Java 的继承概念很相似,虽然 Bean 的继承与 Java 的继承没有什么关系,但是它们的概念以及作用都是相同的,提取公共的部分。在 XML 文件中,可以在子 bean 中使用 parent 属性指定当前 Bean 的父 Bean(注意 parent 的值时其父 bean 的 ID,不是全限定类名)。

还有一点需要说明的是,父 bean 与子 bean 的 class 之间是没有什么关系的,不存在继承关系,你应该将 bean 之间的继承理解为 bean 的 property 数据的继承,请看下面的例子,HelloWorld 和 HelloChina。

HelloWorld 类:

  1. package com.zfl9;
  2. public class HelloWorld {
  3. private String message1;
  4. private String message2;
  5. public HelloWorld() {
  6. }
  7. public String getMessage1() {
  8. return message1;
  9. }
  10. public void setMessage1(String message1) {
  11. this.message1 = message1;
  12. }
  13. public String getMessage2() {
  14. return message2;
  15. }
  16. public void setMessage2(String message2) {
  17. this.message2 = message2;
  18. }
  19. @Override
  20. public String toString() {
  21. return String.format("HelloWorld { message1: '%s', message2: '%s' }", this.message1, this.message2);
  22. }
  23. }

HelloChina 类:

  1. package com.zfl9;
  2. public class HelloChina {
  3. private String message1;
  4. private String message2;
  5. private String message3;
  6. public HelloChina() {
  7. }
  8. public String getMessage1() {
  9. return message1;
  10. }
  11. public void setMessage1(String message1) {
  12. this.message1 = message1;
  13. }
  14. public String getMessage2() {
  15. return message2;
  16. }
  17. public void setMessage2(String message2) {
  18. this.message2 = message2;
  19. }
  20. public String getMessage3() {
  21. return message3;
  22. }
  23. public void setMessage3(String message3) {
  24. this.message3 = message3;
  25. }
  26. @Override
  27. public String toString() {
  28. return String.format("HelloChina { message1: '%s', message2: '%s', message3: '%s' }", this.message1, this.message2, this.message3);
  29. }
  30. }

IocMain 类:

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. HelloWorld helloWorld = (HelloWorld) context.getBean("helloWorld");
  8. System.out.println(helloWorld);
  9. HelloChina helloChina = (HelloChina) context.getBean("helloChina");
  10. System.out.println(helloChina);
  11. }
  12. }

beans.xml 文件:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="helloWorld" class="com.zfl9.HelloWorld">
  6. <property name="message1" value="Hello"/>
  7. <property name="message2" value="World"/>
  8. </bean>
  9. <bean id="helloChina" class="com.zfl9.HelloChina" parent="helloWorld">
  10. <property name="message2" value="江西"/>
  11. <property name="message3" value="赣州"/>
  12. </bean>
  13. </beans>

运行结果如下:

  1. HelloWorld { message1: 'Hello', message2: 'World' }
  2. HelloChina { message1: 'Hello', message2: '江西', message3: '赣州' }

bean 模板
bean 模板与 bean 继承很相似,只是父 bean 没有所谓 class 属性,它只是一个单纯的数据载体(模板):

  1. <?xml version = "1.0" encoding = "UTF-8"?>
  2. <beans xmlns = "http://www.springframework.org/schema/beans"
  3. xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation = "http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id = "beanTeamplate" abstract = "true">
  7. <property name = "message1" value = "Hello World!"/>
  8. <property name = "message2" value = "Hello Second World!"/>
  9. <property name = "message3" value = "Namaste India!"/>
  10. </bean>
  11. <bean id = "helloIndia" class = "com.tutorialspoint.HelloIndia" parent = "beanTeamplate">
  12. <property name = "message1" value = "Hello India!"/>
  13. <property name = "message3" value = "Namaste India!"/>
  14. </bean>
  15. </beans>

注意 beanTemplate 的 abstract 属性,true 表示这是模板,没有对应的 class,IoC 容器不会实例化它。

依赖注入

所谓依赖注入就是一个对象获取它所依赖对象的方式是被动的,而不是主动的,因为这个控制权反转了,所以也被称为控制反转。所谓主动获取依赖就是自己 new 出依赖对象,而被动获取则是通过 构造函数setter 方法 来将依赖对象传递给自己,这个传递依赖对象的过程有个专业名词 - 依赖注入

您可以混合使用基于 构造函数 和基于 Setter 方法 的依赖注入,但是一个好的经验法则是:对于 必选依赖项,建议使用 构造函数 来注入,对于 可选依赖项,建议使用 setter 方法 来注入。

基于构造函数参数的依赖注入
TextEditor

  1. package com.zfl9;
  2. public class TextEditor {
  3. private SpellChecker spellChecker;
  4. public TextEditor(SpellChecker spellChecker) {
  5. System.out.println("Inside TextEditor constructor.");
  6. this.spellChecker = spellChecker;
  7. }
  8. public void checkSpelling() {
  9. this.spellChecker.checkSpelling();
  10. }
  11. }

SpellChecker

  1. package com.zfl9;
  2. public class SpellChecker {
  3. public SpellChecker() {
  4. System.out.println("Inside SpellChecker Constructor.");
  5. }
  6. public void checkSpelling() {
  7. System.out.println("Inside SpellChecker checkSpelling.");
  8. }
  9. }

IocMain

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. TextEditor textEditor = (TextEditor) context.getBean("textEditor");
  8. textEditor.checkSpelling();
  9. }
  10. }

beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="spellChecker" class="com.zfl9.SpellChecker"/>
  6. <bean id="textEditor" class="com.zfl9.TextEditor">
  7. <constructor-arg ref="spellChecker"/>
  8. </bean>
  9. </beans>

运行结果:

  1. Inside SpellChecker Constructor.
  2. Inside TextEditor constructor.
  3. Inside SpellChecker checkSpelling.

我们知道 Java 中有两大数据类型:基本类型引用类型。基本类型共有八种,分别为 shortintlongfloatdoublebooleanbytechar,这八种基本类型都可以使用字面值表示,所以在 beans.xml 文件中,使用 value 属性来存储它们的值(前面已经演示很多次了),而对于引用类型,比如上面的 SpellChecker 参数,就是一个引用类型,我们不能通过 value 字面值来表示它,所以我们需要先使用 bean 元素在 IoC 容器中注册 spellChecker 对象,然后在 textEditor 对象的构造函数参数中使用 ref 来引用对应的参数的 bean ID。当然,String 对象是可以使用 value 字面量表示的。

问题来了,如果构造器有多个参数需要注入,那么该怎么做呢?有三种方式:

方法一,按顺序

  1. package x.y;
  2. public class Foo {
  3. public Foo(Bar bar, Baz baz) {
  4. // ...
  5. }
  6. }
  1. <beans>
  2. <bean id = "foo" class = "x.y.Foo">
  3. <constructor-arg ref = "bar"/>
  4. <constructor-arg ref = "baz"/>
  5. </bean>
  6. <bean id = "bar" class = "x.y.Bar"/>
  7. <bean id = "baz" class = "x.y.Baz"/>
  8. </beans>

方法二,按类型

  1. package x.y;
  2. public class Foo {
  3. public Foo(int year, String name) {
  4. // ...
  5. }
  6. }
  1. <beans>
  2. <bean id = "exampleBean" class = "examples.ExampleBean">
  3. <constructor-arg type = "int" value = "2001"/>
  4. <constructor-arg type = "java.lang.String" value = "Zara"/>
  5. </bean>
  6. </beans>

方法三,按索引(推荐)
注意索引值是从 0 开始的,大家应该很熟悉吧。

  1. <beans>
  2. <bean id = "exampleBean" class = "examples.ExampleBean">
  3. <constructor-arg index = "0" value = "2001"/>
  4. <constructor-arg index = "1" value = "Zara"/>
  5. </bean>
  6. </beans>

第二种方法不是很好,如果参数类型都相同就很尴尬了,第一种可读性稍微差点,所以第三种方式最好。例子:

Container.java

  1. package com.zfl9;
  2. import java.io.IOException;
  3. import java.sql.SQLException;
  4. public class Container {
  5. private IOException ioException;
  6. private SQLException sqlException;
  7. private RuntimeException runtimeException;
  8. public Container(IOException ioException, SQLException sqlException, RuntimeException runtimeException) {
  9. this.ioException = ioException;
  10. this.sqlException = sqlException;
  11. this.runtimeException = runtimeException;
  12. }
  13. public IOException getIoException() {
  14. return ioException;
  15. }
  16. public void setIoException(IOException ioException) {
  17. this.ioException = ioException;
  18. }
  19. public SQLException getSqlException() {
  20. return sqlException;
  21. }
  22. public void setSqlException(SQLException sqlException) {
  23. this.sqlException = sqlException;
  24. }
  25. public RuntimeException getRuntimeException() {
  26. return runtimeException;
  27. }
  28. public void setRuntimeException(RuntimeException runtimeException) {
  29. this.runtimeException = runtimeException;
  30. }
  31. @Override
  32. public String toString() {
  33. return String.format("Container {\n\tIOException: %s\n\tSQLException: %s\n\tRuntimeException: %s\n}", this.ioException, this.sqlException, this.runtimeException);
  34. }
  35. }

IocMain.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. TextEditor textEditor = (TextEditor) context.getBean("textEditor");
  8. textEditor.checkSpelling();
  9. Container container = (Container) context.getBean("container");
  10. System.out.println(container);
  11. }
  12. }

beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="spellChecker" class="com.zfl9.SpellChecker"/>
  6. <bean id="textEditor" class="com.zfl9.TextEditor">
  7. <constructor-arg ref="spellChecker"/>
  8. </bean>
  9. <bean id="ioException" class="java.io.IOException"/>
  10. <bean id="sqlException" class="java.sql.SQLException"/>
  11. <bean id="runtimeException" class="java.lang.RuntimeException"/>
  12. <bean id="container" class="com.zfl9.Container">
  13. <constructor-arg index="0" ref="ioException"/>
  14. <constructor-arg index="1" ref="sqlException"/>
  15. <constructor-arg index="2" ref="runtimeException"/>
  16. </bean>
  17. </beans>

运行结果:

  1. Inside SpellChecker Constructor.
  2. Inside TextEditor constructor.
  3. Inside SpellChecker checkSpelling.
  4. Container {
  5. IOException: java.io.IOException
  6. SQLException: java.sql.SQLException
  7. RuntimeException: java.lang.RuntimeException
  8. }

基于 Setter 方法的依赖注入
基于 setter 的方式其实我们前面已经用了很多次了,就是 property 元素的运用而已,再次强调一点,constructor-arg 是用来指定构造器参数的(构造函数 DI),而 property 是用来指定对象属性的(setter 方法 DI),我们继续以上面的 TextEditor 为例,演示如何使用 settter 方式进行 DI。

TextEditor.java

  1. package com.zfl9;
  2. public class TextEditor {
  3. private SpellChecker spellChecker;
  4. public TextEditor() {
  5. }
  6. public SpellChecker getSpellChecker() {
  7. return spellChecker;
  8. }
  9. public void setSpellChecker(SpellChecker spellChecker) {
  10. this.spellChecker = spellChecker;
  11. }
  12. public void checkSpelling() {
  13. this.spellChecker.checkSpelling();
  14. }
  15. }

SpellChecker.java

  1. package com.zfl9;
  2. public class SpellChecker {
  3. public SpellChecker() {
  4. System.out.println("Inside SpellChecker Constructor.");
  5. }
  6. public void checkSpelling() {
  7. System.out.println("Inside SpellChecker checkSpelling.");
  8. }
  9. }

IocMain.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. TextEditor textEditor = (TextEditor) context.getBean("textEditor");
  8. textEditor.checkSpelling();
  9. }
  10. }

beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="spellChecker" class="com.zfl9.SpellChecker"/>
  6. <bean id="textEditor" class="com.zfl9.TextEditor">
  7. <property name="spellChecker" ref="spellChecker"/>
  8. </bean>
  9. </beans>

运行结果:

  1. Inside SpellChecker Constructor.
  2. Inside SpellChecker checkSpelling.

使用 p-namespace 设置 property 属性
传统方式:

  1. <?xml version = "1.0" encoding = "UTF-8"?>
  2. <beans xmlns = "http://www.springframework.org/schema/beans"
  3. xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation = "http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id = "john-classic" class = "com.example.Person">
  7. <property name = "name" value = "John Doe"/>
  8. <property name = "spouse" ref = "jane"/>
  9. </bean>
  10. <bean id = "jane" class = "com.example.Person">
  11. <property name = "name" value = "John Doe"/>
  12. </bean>
  13. </beans>

改进方式:

  1. <?xml version = "1.0" encoding = "UTF-8"?>
  2. <beans xmlns = "http://www.springframework.org/schema/beans"
  3. xmlns:p = "http://www.springframework.org/schema/p"
  4. xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
  5. xsi:schemaLocation = "http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  7. <bean id = "john-classic"
  8. class = "com.example.Person"
  9. p:name = "John Doe"
  10. p:spouse-ref = "jane"/>
  11. <bean id =" jane"
  12. class = "com.example.Person"
  13. p:name = "John Doe"/>
  14. </beans>

在这里,您应该注意使用 p-namespace 指定原始值和对象引用的区别。该 -ref 部分表示这是另一个 bean 的引用,而非 property 名称,因为 java 标识符规范规定 - 不是一个有效字符,所以可以这么做。

内部 Bean

内部 Bean 和 Java 内部类很相似,我们之所以要使用 Java 内部类,是为了体现外部类和内部类的一个层级关系,比如 MapMap.Entry,它们是一个归属关系,这也是为什么使用内部类比使用外部类更有说明力的理由。在 spring ioc 容器中,我们也可以定义内部 bean,比如之前的构造函数注入、setter 方法注入中,我们会使用 ref 来引用 ioc 容器中的其它 bean 实例,但这些 bean 的作用仅仅是作为参数、属性值,所以我们可以将它们定义在需要它们的 bean 的内部,这样更加整洁,也能避免对全局命名空间进行污染。

还是以前面的 setter 方法注入为例子,我们可以这样写 beans.xml:

  1. <?xml version = "1.0" encoding = "UTF-8"?>
  2. <beans xmlns = "http://www.springframework.org/schema/beans"
  3. xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation = "http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <!-- Definition for textEditor bean using inner bean -->
  7. <bean id = "textEditor" class = "com.tutorialspoint.TextEditor">
  8. <property name = "spellChecker">
  9. <bean class = "com.tutorialspoint.SpellChecker"/>
  10. </property>
  11. </bean>
  12. </beans>

注入集合

前面我们都是注入的标量值,这一节我们来看看如何注入集合类型的值。spring ioc 容器支持以下 4 种集合类型的注入:

例子,JavaCollection.java:

  1. package com.zfl9;
  2. import java.util.List;
  3. import java.util.Map;
  4. import java.util.Properties;
  5. import java.util.Set;
  6. public class JavaCollection {
  7. private List list;
  8. private Set set;
  9. private Map map;
  10. private Properties props;
  11. public JavaCollection() {
  12. }
  13. public List getList() {
  14. System.out.println(list);
  15. return list;
  16. }
  17. public void setList(List list) {
  18. this.list = list;
  19. }
  20. public Set getSet() {
  21. System.out.println(set);
  22. return set;
  23. }
  24. public void setSet(Set set) {
  25. this.set = set;
  26. }
  27. public Map getMap() {
  28. System.out.println(map);
  29. return map;
  30. }
  31. public void setMap(Map map) {
  32. this.map = map;
  33. }
  34. public Properties getProps() {
  35. System.out.println(props);
  36. return props;
  37. }
  38. public void setProps(Properties props) {
  39. this.props = props;
  40. }
  41. }

IocMain.java:

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. JavaCollection javaCollection = (JavaCollection) context.getBean("javaCollection");
  8. javaCollection.getList();
  9. javaCollection.getSet();
  10. javaCollection.getMap();
  11. javaCollection.getProps();
  12. }
  13. }

beans.xml:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="javaCollection" class="com.zfl9.JavaCollection">
  6. <property name="list">
  7. <list>
  8. <value>www.zfl9.com</value>
  9. <value>www.zfl9.com</value>
  10. <value>www.baidu.com</value>
  11. <value>www.google.com</value>
  12. </list>
  13. </property>
  14. <property name="set">
  15. <set>
  16. <value>www.zfl9.com</value>
  17. <value>www.zfl9.com</value>
  18. <value>www.baidu.com</value>
  19. <value>www.google.com</value>
  20. </set>
  21. </property>
  22. <property name="map">
  23. <map>
  24. <entry key="1" value="zfl9"/>
  25. <entry key="2" value="baidu"/>
  26. <entry key="3" value="google"/>
  27. </map>
  28. </property>
  29. <property name="props">
  30. <props>
  31. <prop key="1">zfl9</prop>
  32. <prop key="2">baidu</prop>
  33. <prop key="3">google</prop>
  34. </props>
  35. </property>
  36. </bean>
  37. </beans>

运行结果:

  1. [www.zfl9.com, www.zfl9.com, www.baidu.com, www.google.com]
  2. [www.zfl9.com, www.baidu.com, www.google.com]
  3. {1=zfl9, 2=baidu, 3=google}
  4. {3=google, 2=baidu, 1=zfl9}

引用其它 bean
除了上面这种字面量提供的方式的外,我们也可以引用其他 bean 来提供数据,如下:

  1. <?xml version = "1.0" encoding = "UTF-8"?>
  2. <beans xmlns = "http://www.springframework.org/schema/beans"
  3. xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation = "http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <!-- Bean Definition to handle references and values -->
  7. <bean id = "..." class = "...">
  8. <!-- Passing bean reference for java.util.List -->
  9. <property name = "addressList">
  10. <list>
  11. <ref bean = "address1"/>
  12. <ref bean = "address2"/>
  13. <value>Pakistan</value>
  14. </list>
  15. </property>
  16. <!-- Passing bean reference for java.util.Set -->
  17. <property name = "addressSet">
  18. <set>
  19. <ref bean = "address1"/>
  20. <ref bean = "address2"/>
  21. <value>Pakistan</value>
  22. </set>
  23. </property>
  24. <!-- Passing bean reference for java.util.Map -->
  25. <property name = "addressMap">
  26. <map>
  27. <entry key = "one" value = "INDIA"/>
  28. <entry key = "two" value-ref = "address1"/>
  29. <entry key = "three" value-ref = "address2"/>
  30. </map>
  31. </property>
  32. </bean>
  33. </beans>

注入空字符串

  1. <bean id = "..." class = "exampleBean">
  2. <property name="email" value=""/>
  3. </bean>

注入 null 值

  1. <bean id = "..." class = "exampleBean">
  2. <property name="email"><null/></property>
  3. </bean>

自动装配

所谓自动装配就是 Spring 自动查找 property 或 constructor-arg 的 reference,我们之前的所有配置都是手动装配的,因为所有的 property 或 constructor-arg 都是手动配置的,现在来学习“自动装配”。

在 IoC 容器中查找“符合条件”的 bean,然后装配到 bean 上的行为就叫做“自动装配”。

no
这是默认值,表示不是自动装配,也就是说这是“手动装配”。

byName
所谓 byName 自动装配模式,就是 Spring 自动在 IoC 容器中查找与 property name 相同的 bean,然后装配到当前 bean,一句话就是:查找与属性名称相同的 bean,然后进行装配

例子:
TextEditor.java

  1. package com.zfl9;
  2. public class TextEditor {
  3. private SpellChecker spellChecker;
  4. public TextEditor() {
  5. }
  6. public SpellChecker getSpellChecker() {
  7. return spellChecker;
  8. }
  9. public void setSpellChecker(SpellChecker spellChecker) {
  10. this.spellChecker = spellChecker;
  11. }
  12. public void checkSpelling() {
  13. spellChecker.checkSpelling();
  14. }
  15. }

SpellChecker.java

  1. package com.zfl9;
  2. public class SpellChecker {
  3. public SpellChecker() {
  4. System.out.println("SpellChecker.constructor()");
  5. }
  6. public void checkSpelling() {
  7. System.out.println("SpellChecker.checkSpelling()");
  8. }
  9. }

IocMain.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. TextEditor textEditor = (TextEditor) context.getBean("textEditor");
  8. textEditor.checkSpelling();
  9. }
  10. }

beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="spellChecker" class="com.zfl9.SpellChecker"/>
  6. <bean id="textEditor" class="com.zfl9.TextEditor" autowire="byName"/>
  7. </beans>

注意原先我们是怎么写的:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="spellChecker" class="com.zfl9.SpellChecker"/>
  6. <bean id="textEditor" class="com.zfl9.TextEditor">
  7. <property name="spellChecker" ref="spellChecker"/>
  8. </bean>
  9. </beans>

注意,使用自动装配时我们仍然可以手动指定一些属性,这没有任何影响,比如:

全部都手动装配:

  1. <beans>
  2. <bean id = "textEditor" class = "com.tutorialspoint.TextEditor">
  3. <property name = "spellChecker" ref = "spellChecker" />
  4. <property name = "name" value = "Generic Text Editor" />
  5. </bean>
  6. <bean id = "spellChecker" class = "com.tutorialspoint.SpellChecker"></bean>
  7. </beans>

自动装配与手动装配混合:

  1. <beans>
  2. <bean id = "textEditor" class = "com.tutorialspoint.TextEditor" autowire = "byName">
  3. <property name = "name" value = "Generic Text Editor" />
  4. </bean>
  5. <bean id = "spellChecker" class = "com.tutorialspoint.SpellChecker"></bean>
  6. </beans>

byType
和 byName 差不多,byType 是查找与属性类型相匹配的 bean,然后自动装配上去。

TextEditor.java

  1. package com.zfl9;
  2. public class TextEditor {
  3. private String name;
  4. private SpellChecker spellChecker;
  5. public TextEditor() {
  6. }
  7. public String getName() {
  8. return name;
  9. }
  10. public void setName(String name) {
  11. this.name = name;
  12. }
  13. public SpellChecker getSpellChecker() {
  14. return spellChecker;
  15. }
  16. public void setSpellChecker(SpellChecker spellChecker) {
  17. this.spellChecker = spellChecker;
  18. }
  19. public void checkSpelling() {
  20. spellChecker.checkSpelling();
  21. }
  22. }

SpellChecker.java

  1. package com.zfl9;
  2. public class SpellChecker {
  3. public SpellChecker() {
  4. System.out.println("SpellChecker.constructor()");
  5. }
  6. public void checkSpelling() {
  7. System.out.println("SpellChecker.checkSpelling()");
  8. }
  9. }

IocMain.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. TextEditor textEditor = (TextEditor) context.getBean("textEditor");
  8. textEditor.checkSpelling();
  9. SpellChecker spellChecker = (SpellChecker) context.getBean("com.zfl9.SpellChecker");
  10. spellChecker.checkSpelling();
  11. }
  12. }

beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean class="com.zfl9.SpellChecker"/>
  6. <bean id="textEditor" class="com.zfl9.TextEditor" autowire="byType">
  7. <property name="name" value="Otokaze"/>
  8. </bean>
  9. </beans>

运行结果:

  1. SpellChecker.constructor()
  2. SpellChecker.checkSpelling()
  3. SpellChecker.checkSpelling()

注意,因为 bean 定义中,只有 class 属性时必选的,其他属性都是可以省略的,上面我们省略了 com.zfl9.SpellChecker 类的 bean ID,而默认的 ID 就是 class 的全限定类名,所以我们可以在 IocMain.java 中使用这个 ID 找到这个 Bean。其他的什么与 byName 一样,不再解释。

更新:如果 bean 定义没有提供 id 或 name 属性,那么我们仍然可以通过 bean 的全限定类名来访问它(但不要认为这个 bean 的标识符就是它的全限定类名。)

constructor
这种类型的自动装配其实与 byType 非常相似,都是按照类型进行匹配,但是 constructor 装配模式适用于构造器参数的自动装配,而 byType 则适用于对象属性的自动装配(setter 方法),我们来看例子:

TextEditor.java

  1. package com.zfl9;
  2. public class TextEditor {
  3. private String name;
  4. private SpellChecker spellChecker;
  5. public TextEditor(String name, SpellChecker spellChecker) {
  6. this.name = name;
  7. this.spellChecker = spellChecker;
  8. }
  9. public String getName() {
  10. return name;
  11. }
  12. public void setName(String name) {
  13. this.name = name;
  14. }
  15. public SpellChecker getSpellChecker() {
  16. return spellChecker;
  17. }
  18. public void setSpellChecker(SpellChecker spellChecker) {
  19. this.spellChecker = spellChecker;
  20. }
  21. public void checkSpelling() {
  22. spellChecker.checkSpelling();
  23. }
  24. }

IocMain.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. TextEditor textEditor = (TextEditor) context.getBean("textEditor");
  8. textEditor.checkSpelling();
  9. }
  10. }

beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean class="com.zfl9.SpellChecker"/>
  6. <bean id="textEditor" class="com.zfl9.TextEditor" autowire="constructor">
  7. <constructor-arg name="name" value="Otokaze"/>
  8. </bean>
  9. </beans>

运行结果:

  1. SpellChecker.constructor()
  2. SpellChecker.checkSpelling()

autodetect
自动选择模式,即首先尝试使用 constructor 模式,如果不行,就使用 byType 模式。

自动装配的缺点
虽然使用自动装配可以减少 XML 文件的配置,但是除非大家都使用自动装配,否则这种只写一部分 property、constructor-arg 的方式可能会让人感到疑惑,导致 XML 文件的可读性降低,甚至带来混淆。

  1. 自动装配的实例属性/构造器参数仍然可以通过 <property><constructor-arg> 元素覆盖。
  2. 对于某些特殊数据类型,无法使用自动装配,比如基本数据类型、String 字符串、Class 类。
  3. 令人感到疑惑,可读性不好,所以除非有充分的理由,否则不是很建议使用自动装配。

注解装配

从 Spring 2.5 版本开始,支持使用 Java 注解对依赖注入进行配置(之前是全部通过 XML 文件进行配置,现在我们可以使用 Java 注解类进行辅助配置,甚至完全使用 Java 注解进行 DI 配置也是可行的)。Spring 默认没有开启注解装配的支持,所以我们需要在 beans.xml 文件中加入 <context:annotation-config/>,具体的:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  6. <context:annotation-config/>
  7. </beans>

稍微解释一下 context:annotation-config 的作用,配置该元素后,Spring 会自动扫描 application context 中的 bean 上面配置的 Java 注解,并且激活它们。也就是说,添加这行后,Spring 会自动应用 IoC 容器中的 bean 的对应类上面的 Java 注解,这下应该理解了吧。

也就是说,只有位于 application context 容器中的 bean 上面的注解才会被 Spring 扫描并激活。

那么 context:annotaion-config 设置后,Spring 会扫描 bean 上面的哪些注解呢:

@Required
该 @Required 注解适用于 bean 属性的 setter 方法,并表示受影响的 bean 属性必须在 XML 配置文件在配置时进行填充。否则,容器抛出 BeanInitializationException异常。以下示例显示@Required注释的使用。

Student.java
我们给 name 和 age 属性对应的 setter 方法标注了 @Required,表示他们是必选属性。

  1. package com.zfl9;
  2. import org.springframework.beans.factory.annotation.Required;
  3. public class Student {
  4. private String name;
  5. private Integer age;
  6. public Student() {
  7. }
  8. public String getName() {
  9. return name;
  10. }
  11. @Required
  12. public void setName(String name) {
  13. this.name = name;
  14. }
  15. public Integer getAge() {
  16. return age;
  17. }
  18. @Required
  19. public void setAge(Integer age) {
  20. this.age = age;
  21. }
  22. @Override
  23. public String toString() {
  24. return String.format("Student: [ name = '%s', age = %d ]", name, age);
  25. }
  26. }

IocMain.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. Student student = (Student) context.getBean("student");
  8. System.out.println(student);
  9. }
  10. }

beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  6. <context:annotation-config/>
  7. <bean id="student" class="com.zfl9.Student">
  8. <property name="name" value="Otokaze"/>
  9. <property name="age" value="120"/>
  10. </bean>
  11. </beans>

运行结果:

  1. Student: [ name = 'Otokaze', age = 120 ]

如果我们将某个或者两个 property 都注释掉,那么就会报错,提示 name/age 参数是必选的。

@Autowired
自动装配的注解,默认是先按照 byType 模式在 IoC 容器中查找匹配的 bean,然后装配上去,当然也可以与 @Qualifier 注解搭配使用,来指明使用哪个 ID 的 bean,此时可以理解为变成了 byName 的装配模式。

@Autowired 注解可以用在任何成员方法上面(构造方法、普通方法、setter 方法),也可以用在对应的成员属性上面(private 访问性也行,Spring 会使用反射 API 自动写入依赖对象的引用值,不要感到奇怪,这是反射 API 应该做的,我记得学习 Java 反射的时候就演示过这个功能,修改 String 对象的私有字符数组)。注意,@Autowird 还能够用在方法参数上,并且可以和 @Qualifier 注解一起使用,变为 byName 形式。

使用 @Autowired 允许我们更自由的实现依赖注入,因为普通的 XML 方式只能对 setter 方法和构造函数参数进行依赖注入,而 @Autowired 注解允许使用在任何方法上,Spring 会自动在 IoC 容器中查找符合条件(默认 byType,使用 @Qualifier 变成 byName 模式)的 bean,然后注入到合适的位置。

基于 setter 方法的自动装配
TextEditor.java

  1. package com.zfl9;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. public class TextEditor {
  4. private SpellChecker spellChecker;
  5. public TextEditor() {
  6. }
  7. public SpellChecker getSpellChecker() {
  8. return spellChecker;
  9. }
  10. @Autowired
  11. public void setSpellChecker(SpellChecker spellChecker) {
  12. this.spellChecker = spellChecker;
  13. }
  14. public void checkSpelling() {
  15. spellChecker.checkSpelling();
  16. }
  17. }

SpellChecker.java

  1. package com.zfl9;
  2. public class SpellChecker {
  3. public SpellChecker() {
  4. System.out.println("SpellChecker.constructor()");
  5. }
  6. public void checkSpelling() {
  7. System.out.println("SpellChecker.checkSpelling()");
  8. }
  9. }

IocMain.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. TextEditor textEditor = (TextEditor) context.getBean("textEditor");
  8. textEditor.checkSpelling();
  9. }
  10. }

beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  6. <context:annotation-config/>
  7. <bean id="textEditor" class="com.zfl9.TextEditor"/>
  8. <bean id="spellChecker" class="com.zfl9.SpellChecker"/>
  9. </beans>

运行结果:

  1. SpellChecker.constructor()
  2. SpellChecker.checkSpelling()

基于成员属性的自动装配
TextEditor.java

  1. package com.zfl9;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. public class TextEditor {
  4. @Autowired
  5. private SpellChecker spellChecker;
  6. public TextEditor() {
  7. }
  8. public void checkSpelling() {
  9. spellChecker.checkSpelling();
  10. }
  11. }

运行结果:

  1. SpellChecker.constructor()
  2. SpellChecker.checkSpelling()

基于构造函数的自动装配
TextEditor.java

  1. package com.zfl9;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. public class TextEditor {
  4. private SpellChecker spellChecker;
  5. @Autowired
  6. public TextEditor(SpellChecker spellChecker) {
  7. this.spellChecker = spellChecker;
  8. }
  9. public void checkSpelling() {
  10. spellChecker.checkSpelling();
  11. }
  12. }

运行结果:

  1. SpellChecker.constructor()
  2. SpellChecker.checkSpelling()

基于普通方法的自动装配
TextEditor.java

  1. package com.zfl9;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. public class TextEditor {
  4. @Autowired
  5. private SpellChecker spellChecker;
  6. public TextEditor() {
  7. }
  8. public void checkSpelling() {
  9. spellChecker.checkSpelling();
  10. }
  11. @Autowired
  12. public void showStatus(TextEditor textEditor, SpellChecker spellChecker) {
  13. System.out.println(textEditor);
  14. System.out.println(spellChecker);
  15. System.out.println(this.spellChecker);
  16. }
  17. }

注意,这里我们同时使用了成员变量的自动装配和普通方法的自动装配,都是没有问题的,其实你只要将 application context 看作是一个资源池就行,装配的对象都是从这个资源池中挑选的,这是执行结果:

  1. SpellChecker.constructor()
  2. com.zfl9.TextEditor@167fdd33
  3. com.zfl9.SpellChecker@1e965684
  4. com.zfl9.SpellChecker@1e965684
  5. SpellChecker.checkSpelling()

@Autowired 的 required 选项
默认情况下,如果标注了 @Autowired 的变量没有找到合适的注入对象,那么 Spring 将会抛出异常,但是我们可以通过 @Autowired 注解的 required 布尔属性来改变这一行为,默认值为 true,表示对应的依赖时必须的,我们将它设为 false 就表示这个依赖对象是可选的。

  1. package com.tutorialspoint;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. public class Student {
  4. private Integer age;
  5. private String name;
  6. @Autowired(required=false)
  7. public void setAge(Integer age) {
  8. this.age = age;
  9. }
  10. public Integer getAge() {
  11. return age;
  12. }
  13. @Autowired
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. public String getName() {
  18. return name;
  19. }
  20. }

@Qualifier
当您创建多个相同类型的 bean 并且只想使用属性关联其中一个 bean 时,可能会出现这种情况。在这种情况下,您可以使用 @Qualifier 注解和 @Autowired 通过指定要关联的确切 bean 来消除混淆(歧义)。

Student.java

  1. package com.zfl9;
  2. public class Student {
  3. private String name;
  4. private int age;
  5. public Student() {
  6. }
  7. public String getName() {
  8. return name;
  9. }
  10. public void setName(String name) {
  11. this.name = name;
  12. }
  13. public int getAge() {
  14. return age;
  15. }
  16. public void setAge(int age) {
  17. this.age = age;
  18. }
  19. @Override
  20. public String toString() {
  21. return String.format("Student { name = %s, age = %d }", name, age);
  22. }
  23. }

Profile.java

  1. package com.zfl9;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.beans.factory.annotation.Qualifier;
  4. public class Profile {
  5. @Autowired
  6. @Qualifier("student1")
  7. private Student student;
  8. public Profile() {
  9. }
  10. @Override
  11. public String toString() {
  12. return String.format("Profile { student: %s }", student);
  13. }
  14. }

IocMain.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. Profile profile = (Profile) context.getBean("profile");
  8. System.out.println(profile);
  9. }
  10. }

beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  6. <context:annotation-config/>
  7. <bean id="profile" class="com.zfl9.Profile"/>
  8. <bean id="student1" class="com.zfl9.Student">
  9. <property name="name" value="Otokaze"/>
  10. <property name="age" value="110"/>
  11. </bean>
  12. <bean id="student2" class="com.zfl9.Student">
  13. <property name="name" value="Google"/>
  14. <property name="age" value="120"/>
  15. </bean>
  16. </beans>

注意,因为 @Autowired 是按照 Type 查找对应的 bean 依赖对象的,所以如果 context 中有多个类型相同的 bean,就会产生歧义,Spring 不知道该使用哪个,所以运行时会抛出异常,我们需要使用 @Qualifier("beanName") 来告诉 Autowired 究竟使用哪个 bean 对象,运行结果如下:

  1. Profile { student: Student { name = Otokaze, age = 110 } }

@PostConstructor 和 @PreDestroy 注解
在之前,我们如果需要定义 init 和 destroy 方法,需要在 bean 元素中添加属性 init-method 和 destroy-method,里面指明对应的 public void METHOD() {} 签名的方法,不过,有了注解之后,我们可以直接在对应的 init 和 destroy 方法上标注 @PostConstructor@PreDestroy 注解即可,例子:

HelloWorld.java

  1. package com.zfl9;
  2. import javax.annotation.PostConstruct;
  3. import javax.annotation.PreDestroy;
  4. public class HelloWorld {
  5. @PostConstruct
  6. public void init() {
  7. System.out.println("Hello World!");
  8. }
  9. @PreDestroy
  10. public void destroy() {
  11. System.out.println("Goodbye World!");
  12. }
  13. }

IocMain.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. ((ClassPathXmlApplicationContext) context).registerShutdownHook();
  8. }
  9. }

beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  6. <context:annotation-config/>
  7. <bean id="helloworld" class="com.zfl9.HelloWorld"/>
  8. </beans>

运行结果:

  1. Hello World!
  2. Goodbye World!

@Resource 注解
@Resource 注解与 @Autowired 注解的作用很相似,只不过,@Resource 是 byName 形式的自动装配,而 @Autowired 是 byType 形式的自动装配(配合 @Qualifier 注解当然也是可以将其转换为 byName 的)。

@Resource 的常用参数是 name,用来指明要匹配的 bean 名称,如果省略,则默认以变量名称来查找对应的 bean,如果按照名称找不到对应的 bean,则 @Resource 会尝试通过按照 Type 来查找对应的 bean,如果还招不到就只有抛出异常了。注意,如果显示指定了 name 参数,那么就只会按照 bean ID 进行查找,找不到就抛出异常,所以一般不需要也不建议指定 name 参数,省略这个参数有更多的回退空间。

@Autowired 和 @Resource 注解的用法和可标注的位置是相同的,不同的是 Autowired 是由 Spring 提供的,而 Resource 注解是由 Java 提供的(JSR),虽然许多人建议使用 @Resource,但其实没有什么区别。

更正:@Resource 不能用于构造函数。@Resource 只能用在成员变量、成员变量对应的 setter 方法上!!!

成员变量:

  1. package com.zfl9;
  2. import java.sql.SQLException;
  3. import javax.annotation.Resource;
  4. import org.xml.sax.SAXException;
  5. public class HelloWorld {
  6. @Resource
  7. private SAXException saxException;
  8. @Resource
  9. private SQLException sqlException;
  10. @Resource
  11. private RuntimeException sslException;
  12. @Override
  13. public String toString() {
  14. return saxException + "\n" + sqlException + "\n" + sslException;
  15. }
  16. }

Setter 方法:

  1. package com.zfl9;
  2. import java.sql.SQLException;
  3. import javax.annotation.Resource;
  4. import org.xml.sax.SAXException;
  5. public class HelloWorld {
  6. private SAXException saxException;
  7. private SQLException sqlException;
  8. private RuntimeException sslException;
  9. @Resource
  10. public void setSaxException(SAXException saxException) {
  11. this.saxException = saxException;
  12. }
  13. @Resource
  14. public void setSqlException(SQLException sqlException) {
  15. this.sqlException = sqlException;
  16. }
  17. @Resource
  18. public void setSslException(RuntimeException sslException) {
  19. this.sslException = sslException;
  20. }
  21. @Override
  22. public String toString() {
  23. return saxException + "\n" + sqlException + "\n" + sslException;
  24. }
  25. }

所以,实际上,还是 @Autowired 用的多一点,反正我们都是使用 Spring 的框架,还怕什么依赖呀。

事件回调

您已经在所有章节中看到 Spring 的核心是 ApplicationContext,它管理 bean 的完整生命周期。

ApplicationContext 在加载 bean 时会发布某些类型的事件。例如,bean 容器启动时会发布 ContextStartedEvent 事件,当 bean 容器停止时会发布 ContextStoppedEvent 事件。

ApplicationContext 的事件处理是通过 ApplicationEvent 事件类和 ApplicationListener 监听器接口实现的。因此,如果 bean 实现了 ApplicationListener 接口,那么 ApplicationContext 在发生相关 ApplicationEvent 时,都会通知这个 bean。

Spring ApplicationContext 提供以下标准事件

Spring 的事件回调机制是单线程的,所以不要在 bean 监听器的回调方法上执行阻塞操作。

创建 bean 事件监听器的步骤
要监听上下文事件,bean 应该实现 ApplicationListener 接口,该接口只有一个 onApplicationEvent() 方法。因此,让我们编写一个示例来查看事件如何传播以及如何使代码根据特定事件执行所需任务。

HelloWorld.java

  1. package com.zfl9;
  2. public class HelloWorld {
  3. private String message;
  4. public String getMessage() {
  5. return message;
  6. }
  7. public void setMessage(String message) {
  8. this.message = message;
  9. }
  10. @Override
  11. public String toString() {
  12. return message;
  13. }
  14. }

ContextStartListener.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationListener;
  3. import org.springframework.context.event.ContextStartedEvent;
  4. public class ContextStartListener implements ApplicationListener<ContextStartedEvent> {
  5. @Override
  6. public void onApplicationEvent(ContextStartedEvent contextStartedEvent) {
  7. System.out.println("context started.");
  8. }
  9. }

ContextStopListener.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationListener;
  3. import org.springframework.context.event.ContextStoppedEvent;
  4. public class ContextStopListener implements ApplicationListener<ContextStoppedEvent> {
  5. @Override
  6. public void onApplicationEvent(ContextStoppedEvent contextStoppedEvent) {
  7. System.out.println("context stopped.");
  8. }
  9. }

IocMain.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class IocMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  7. ((ClassPathXmlApplicationContext) context).start();
  8. HelloWorld helloWorld = (HelloWorld) context.getBean("helloworld");
  9. System.out.println(helloWorld);
  10. ((ClassPathXmlApplicationContext) context).stop();
  11. }
  12. }

beans.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  6. <context:annotation-config/>
  7. <bean id="helloworld" class="com.zfl9.HelloWorld">
  8. <property name="message" value="Hello, World"/>
  9. </bean>
  10. <bean id="contextStartListener" class="com.zfl9.ContextStartListener"/>
  11. <bean id="contextStopListener" class="com.zfl9.ContextStopListener"/>
  12. </beans>

运行结果:

  1. context started.
  2. Hello, World
  3. context stopped.

AOP FAQ 1

AOP 是 Spring 独有的概念吗?
不是,除了 Spring AOP 外,常见的 AOP 实现还有:

AOP Alliance 是什么, 为什么 Spring AOP 需要 aopalliance.jar?
AOP Alliance 是 AOP 的接口标准,定义了 AOP 中的基础概念(Advice、CutPoint、Advisor 等),目的是为各种 AOP 实现提供统一的接口,本身并不是一种 AOP 的实现(如同 JDBC API 和 JDBC 驱动的关系)。Spring AOP、Guice AOP 等都采用了 AOP Alliance 中定义的接口,因而这些 lib 都需要依赖 aopalliance.jar。注:Spring 4.3 后内置了 AOP Alliance 接口,不再需要单独的 aopalliance.jar。

Spring AOP 和 AspectJ 的区别?
Spring AOP 采用 动态代理 的方式(首选 JDK 动态代理,备选 CGLIB 动态代理),在运行期间动态生成代理类来实现 AOP,不修改原类的实现;AspectJ 使用编译期 字节码织入(weave)的方式,在编译的时候,直接修改类的字节码,把所定义的切面代码逻辑插入到目标类中。注: AspectJ 除了编译期静态织入的方式之外,也支持加载时动态织入修改类的字节码。

Spring AOP 如何生成代理类?
Spring AOP 使用 JDK 动态代理机制或者 CGLIB 动态代理机制生成。对于实现了接口的类使用 JDK Proxy,对于没有实现接口的类使用 CGLIB 实现。指定 proxy-target-class 为 true 可强制使用 CGLIB:

  1. <aop:aspectj-autoproxy proxy-target-class="true"/>

JDK Proxy 和 CGLIB 代理有什么区别?
JDK Proxy 只适用于类实现了接口的情况,cglib 则是生成原来的子类,对于没有实现接口的情况也适用。cglib 采用字节码生成的方式来在代理类中调用原类方法, JDK Proxy 则是使用反射调用,由于反射存在额外 security check 的开销,而 jvm jit 对反射的内联支持不够好,所以 JDK Proxy 在性能上弱于 cglib。

spring-aspects 又是什么鬼
因为 Spring AOP XML 配置文件定义的方式太繁琐遭到吐槽,所以 spring 从 AspectJ 中吸收了其定义 AOP 的方式,包括 AspectJ Annotation 和 AspectJ-XML 配置。然而其实现依然是动态代理的方式,与 aspectj 字节码织入的方式不同。

为什么 spring-aspects 还需要 aspectjweaver.jar 才能工作
spring-aspects 实现 XML 配置解析和 Aspectj 注解方式的时候,借用了 aspectjweaver.jar 中定义的 annotation 和 class,所以需要依赖 aspectj-weaver.jar 包,但是这仅仅是 API 层面的借鉴,在实现 AOP 的原理上,Spring AOP 依然是通过动态代理机制实现的。Spring 3.2 之前,spring-aspects 对 aspectjweaver 的依赖还是 optional 的,需要自己去添加依赖;Sprint 3.2 之后,spring-aspects 已经包含了 aspectjweaver 依赖,所以不再需要手动配置这个依赖项。

Spring AOP 与 AspectJ 的区别和联系
AOP 是 Spring 框架的重要组成部分。目前我所接触的 AOP 实现框架有 Spring AOP 还有就是 AspectJ (还有另外几种我没有接触过)。我们先来说说他们的区别:

AspectJ 是一个比较牛逼的 AOP 框架,他可以对类的成员变量,方法进行拦截。由于 AspectJ 是 Java 语言语法和语义的扩展,所以它提供了自己的一套处理方面的关键字(AspectJ 有自己的语法,为了处理这些语法,AspectJ 还有专门的编译器)。除了包含字段和方法之外,AspectJ 的方面声明还包含切入点和通知成员。所以 AspectJ 是一个完整的 AOP 生态环境,并且很强大,有多种实现 AOP 的方式,一种是编译期间织入,一种是编译后织入,一种是运行期间织入。

Spring AOP 依赖的是 Spring 框架方便的、最小化的运行时配置,所以不需要独立的启动器。但是,使用这个技术,只能通知从 Spring 框架检索出的对象(即 Spring AOP 只能用来处理 IoC 容器中的对象)。Spring 的 AOP 技术只能是对方法进行拦截

在 Spring AOP 中我们同样也可以使用类似 AspectJ 的注解来实现 AOP 功能,但是这里要注意一下,使 AspectJ 的注解时,AOP 的实现方式还是 Spring AOP。Spring 缺省使用 JDK 动态代理来作为 AOP 的实现,这样任何接口都可以被代理,Spring 也可以使用 CGLIB 代理,对于需要代理类而不是代理接口的时候 CGLIB 是很有必要的。如果一个业务对象没有实现接口,默认就会使用 CGLIB 代理。

Spring AOP 和 AscpectJ 之间的关系:Spring 使用了和 AspectJ 一样的注解,并使用 AspectJ 来做切入点解析和匹配(AspectJ 5 让第三方使用 AspectJ 的切入点解析和匹配引擎的工具 API)。但是 Spring AOP 运行时仍旧是纯的 Spring AOP,并不依赖于 AspectJ 的编译器或者织入器。

Spring AOP 的运行没有依托 AspectJ 的运行,但是在概念和表达式语法层面使用了 AspectJ 的风格。AspectJ 是一套独立的 AOP 面向切面编程的解决方案,和 Spring AOP 没什么关系。AspectJ 是由 Eclipse 基金会开发的。

Spring AOP 与 AspectJ 的区别 2
AOP(Aspect Orient Programming),作为面向对象编程的一种补充,广泛应用于处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等。AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为 编译时增强(典型代表:AspectJ);而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为 运行时增强(典型代表 Spring AOP)。

先说说 AspectJ
之前,我还以为 AspectJ 是 Spring 的一部分,因为我们谈到 Spring AOP 一般都会提到 AspectJ。但实际上,AspectJ 是一套独立的面向切面编程的解决方案,和 Spring AOP 没任何关系,硬要说有什么关系,只能说 Spring AOP 借鉴了 AspectJ 的语法和注解和配置(但运行时,Spring AOP 与 AspectJ 没任何关系)。下面我们抛开 Spring,单纯的看看 AspectJ。

AspectJ 安装
AspectJ 下载地址:http://www.eclipse.org/aspectj/downloads.php。下载 AspectJ jar 包,然后双击安装。安装好的目录结构为:

AspectJ HelloWorld 实现
服务类,方法很简单,就是打印 Hello, AspectJ 到 STDOU。

  1. package com.ywsc.fenfenzhong.aspectj.learn;
  2. public class SayHelloService {
  3. public void say() {
  4. System.out.println("Hello, AspectJ");
  5. }
  6. }

调用 say() 方法之后,需要记录日志。通过 AspectJ 的后置增强来实现。

  1. package com.ywsc.fenfenzhong.aspectj.learn;
  2. public aspect LogAspect {
  3. pointcut logPointcut():execution(void SayHelloService.say());
  4. after():logPointcut() {
  5. System.out.println("记录日志 ...");
  6. }
  7. }

编译、运行:

  1. # 编译SayHelloService
  2. # 生成 SayHelloService.class
  3. ajc SayHelloService.java LogAspect.java
  4. # 运行 SayHelloService
  5. # 输出 Hello, AspectJ、记录日志
  6. java SayHelloService

ajc.exe 可以理解为 javac.exe 命令,都用于编译 Java 程序,区别是 ajc.exe 命令可识别 AspectJ 的语法;我们可以将 ajc.exe 看为一个增强版的 javac.exe 命令(普通 javac + 支持 AspectJ 语法的编译器)。执行 ajc 命令后生成的 SayHelloService.class 文件是由 SayHelloService.java 和 LogAspect.java 共同生成的,这个 class 文件是一个正常的普通的 class 文件,所以无需其他额外的处理,可以直接运行。这表明 AspectJ 在编译时“自动”编译得到了一个新类,这个新类增强了原有的 SayHelloService.java 类的功能,因此 AspectJ 通常被称为 编译时增强的 AOP 框架

与 AspectJ 相对的还有另外一种 AOP 框架(Spring AOP),它不需要在编译时对目标类进行增强,而是运行时生成目标类的代理类,该代理类要么与目标类实现相同的接口,要么是目标类的子类。总之,代理类的实例可作为目标类的实例来使用。一般来说,编译时增强的 AOP 框架在性能上更有优势,因为运行时动态增强的 AOP 框架需要每次运行时都进行动态增强(不过也只是第一次调用时有点开销,在这之后一般都会被缓存起来,所以性能都差不多,不必在意)。

再谈 Spring AOP
Spring AOP 也是对目标类增强,生成代理类。但是与 AspectJ 的最大区别在于:Spring AOP 的运行时增强,而 AspectJ 是编译时增强。另一个区别是,AspectJ 的 AOP 显然比 Spring AOP 更强大,因为 Spring AOP 只支持方法级别的增强,而 AspectJ 支持方法、字段等级别的增强。不过其实一般情况下,方法级别的增强就够了,毕竟 Spring AOP 的初衷就不是提供像 AspectJ 那样完善、强大、复杂的 AOP 支持,而是为了解决企业开发中常见的日志记录、事务管理、权限控制等代码带来的问题。

曾经以为 AspectJ 是 Spring AOP 一部分,因为 Spring AOP 使用了 AspectJ 的 Annotation,使用了 AspectJ 注解来定义切面,使用 Pointcut 来定义切入点,使用 Advice 来定义增强处理。虽然使用了 Aspect 的 Annotation,但是并没有使用它的编译器和织入器。其实现原理是 JDK 动态代理,在运行时生成代理类(为了处理没有实现任何借口的类的 AOP 支持,Spring AOP 会对它们使用 CGLIB 动态代理)。

为了启用 Spring AOP 对 AspectJ 注解方面的配置支持,并保证 Spring IoC 容器中的目标 Bean 被一个或多个切面自动增强,必须在 Spring XML 配置文件中添加如下配置(加入后,需要依赖 aspectj 的注解包):

  1. <aop:aspectj-autoproxy/>

当启用 AspectJ 支持后,Spring 会自动识别出 IoC 容器中被 @Aspect 标注的 Bean,并将该 Bean 作为切面 Bean 来处理。切面 Bean 与普通 Bean 没有任何区别,一样使用 <bean.../> 元素进行配置,一样支持使用依赖注入来配置属性值。

使用 Spring AOP 改写 Hello World 的例子

  1. package com.ywsc.fenfenzhong.aspectj.learn;
  2. import org.springframework.stereotype.Component;
  3. @Component
  4. public class SayHelloService {
  5. public void say() {
  6. System.out.print("Hello, AspectJ");
  7. }
  8. }
  1. package com.ywsc.fenfenzhong.aspectj.learn;
  2. import org.aspectj.lang.annotation.After;
  3. import org.aspectj.lang.annotation.Aspect;
  4. import org.springframework.stereotype.Component;
  5. @Aspect
  6. @Component
  7. public class LogAspect {
  8. @After("execution(* com.ywsc.fenfenzhong.aspectj.learn.SayHelloService.*(..))")
  9. public void log() {
  10. System.out.println("记录日志 ...");
  11. }
  12. }
  1. package com.ywsc.fenfenzhong.mongodb;
  2. import com.ywsc.fenfenzhong.aspectj.learn.SayHelloService;
  3. public class TestCase {
  4. public static void main(String[] args) {
  5. SayHelloService sayHelloService = ApplicationUtil.getContext().getBean(SayHelloService.class);
  6. sayHelloService.say();
  7. }
  8. }

输出结果:

  1. Hello AspectJ
  2. 记录日志...

AOP 的总结

AOP 代理 = 原有业务类 + 增强处理类。

最后说说 CGLIB
CGLIB(Code Generation Library)是一个代码生成类库。可以在运行时动态的生成某个类的子类。CGLIB 动态代理弥补了 JDK 动态代理的一个不足之处,JDK 动态代理只能针对接口进行动态代理,如果委托类没有实现任何接口,那么 JDK 动态代理将无能为力,而 CGLIB 动态代理则是通过生成委托类的子类来实现动态代理的,所以只要委托类不是 final 类,或者委托类的方法不是 final 方法,就都能够被动态代理。

要想 Spring AOP 强制使用 CGLIB 生成代理类,只需要在 Spring 的配置文件引入:

  1. <aop:aspectj-autoproxy proxy-target-class="true"/>

proxy-target-class 属性的默认值为 false,表示优先使用 JDK 动态代理,只有当无法使用 JDK 动态代理时,Spring AOP 才会考虑使用 CGLIB 动态代理,一般我们不需要修改这个属性,让 Spring 自己决定最好。

AOP FAQ 2

Spring AOP 与 AspectJ
前两天看了一些关于 Spring AOP 和 AspectJ 的文章,但是总是感觉非常的乱,有的说 Spring AOP 跟 AspectJ 相互独立,有的说 Spring AOP 依赖于 AspectJ,有的甚至直接把两者混为一谈。现在我告诉大家:Spring AOP 和 AspectJ 之间没有任何关系,它们是不同公司的不同 AOP 产品,AspectJ 提供一个完整的 AOP 实现,当然 Spring AOP 也提供一个完整的 AOP 实现。但是 AspectJ 提供的 AOP 支持更完整且更强大,而 Spring AOP 提供的 AOP 支持不如 AspectJ 这么强大,应该说“轻量”,但对我们来说足够用了。

那为什么我们使用 Spring AOP 的时候会用到 AspectJ 的注解呢?其实 Spring AOP 以前只支持 XML 文件的 AOP 配置,但是由于 XML 文件的配置方式太繁琐,所以 Spring AOP 机智的引进了 AspectJ 的注解配置方式,并且为了减少开发人员的学习成本,直接将 AspectJ 的注解全部照搬了过来,因为是“照搬”的,所以使用当你在 Spring AOP 中使用 AspectJ 注解配置语法时,通常还需要导入 AspectJ 的某些依赖包。

有必要强调一点,Spring AOP 引入 AspectJ 注解的目的是为了取代 XML 这种繁琐的 AOP 配置,Spring AOP 的底层实现是没有任何改变的,依旧是通过动态代理机制(首选 JDK 动态代理,备选 CGLIB 动态代理),这一点不要搞混了。而 AspectJ 的实现方式与动态代理没有任何关系,AspectJ 有两种实现方式:一种是编译期间织入,这种方式是开销最小的,编译出来的就是普通的 class 文件,所以 jvm 能直接运行,不需要做任何特殊操作。另一种是通过 java-agent 代理,在类加载期间操作 class 文件,织入对应的切面。

AspectJ 的切面描述方法
AspectJ 提供了两套对切面的描述方法,一种就是我们常见的 基于 Java 注解 切面描述的方法,这种方法兼容 java 语法,写起来十分方便,不需要 IDE 的额外语法检测支持;另外一种是 基于 aspect 文件 的切面描述方法,这种语法本身并不是 java 语法,因此写的时候需要 IDE 的插件支持才能进行语法检查。所以,实际使用中,用的最多的还是基于 Java 注解的方式来描述切面,因为不需要额外的语法插件支持,学习成本也低。

AspectJ 相关 jar 包
AspectJ 是 Eclipse 基金会的一个项目,官网就在 Eclipse 官网里。官网里提供了一个 aspectJ.jar 的下载链接,但其实这个链接只是一个安装包,把安装包里的东西解压后就是一个 文档 + 脚本 + jar包 的程序包,其中比较重要的是如下部分:

  1. myths@pc:~/aspectj1.8$ tree bin/ lib/
  2. bin/
  3. ├── aj
  4. ├── aj5
  5. ├── ajbrowser
  6. ├── ajc
  7. └── ajdoc
  8. lib/
  9. ├── aspectjrt.jar
  10. ├── aspectjtools.jar
  11. ├── aspectjweaver.jar
  12. └── org.aspectj.matcher.jar

这些 jar 包并不总是需要从官网下载,很多情况下在 maven 等中心库中直接找会更方便。
其中重要的文件是前三个 jar 包,bin 文件夹中的脚本其实都是调用这些 jar 包的命令。

AspectJ 的几种使用方法

其实说到底就是两种织入方式:编译时织入(ajc 编译器)、运行时织入(java agent 代理)。

说到这里,我又想说说 AOP 的三个织入时机了:

  1. 编译期间织入:AspectJ 实现方式。需要特殊的 Java 编译器(如 ajc 编译器)。
  2. 装载期间织入:AspectJ 实现方式。需要特殊的类加载器或通过 java-agent 代理。
  3. 运行期间织入:Spring AOP 实现方式。实现原理是动态代理(JDK/CGLIB 动态代理)。

所以从性能上讲,AspectJ 可能比 Spring AOP 好,特别是编译期间织入,这可能是性能最好的一种,因为没有任何运行时开销。不过 Spring AOP 的性能也不能说低,在实际运用中,不用太过担心 AOP 带来的性能开销,如果真的有性能问题,你最应该检查的是你的代码,而不是怪 AOP。

为什么能推断出 Spring AOP 依旧使用动态代理来实现 AOP?
根据 AspectJ 的使用方式,我们知道,如果要向代码织入切面,要么使用 ajc 编译要么使用 aspectjweaver 的 agent 代理。但是 Spring AOP 既没有依赖任何 aspectjtools 的相关 jar 包(ajc 编译器相关),虽然依赖了 aspectjweaver 这个包(agent 代理相关),但是并没有使用 agent 代理(只是复用了里面的一些类,避免重复造轮子而已)。所以,Spring AOP 依旧使用动态代理实现 AOP,而不是使用 AspectJ 的编译时织入或者加载时织入。

AspectJ 提供两种切面描述方式java注解描述方式aspect文件描述方式;AspectJ 提供两种切面织入方式ajc编译器,编译期间织入agent代理,加载期间织入,请不要搞混了这四个东西,它们可以任意搭配。

基于 aspect 源文件的描述方式
业务类:

  1. public class App {
  2. public void say() {
  3. System.out.println("App say");
  4. }
  5. public static void main(String[] args) {
  6. App app = new App();
  7. app.say();
  8. }
  9. }

切面类:

  1. public aspect AjAspect {
  2. pointcut say(): execution(* App.say(..));
  3. before(): say() {
  4. System.out.println("AjAspect before say");
  5. }
  6. after(): say() {
  7. System.out.println("AjAspect after say");
  8. }
  9. }

基于 annotation 注解的描述方式
业务类:

  1. package com.mythsman.test;
  2. public class App {
  3. public void say() {
  4. System.out.println("App say");
  5. }
  6. public static void main(String[] args) {
  7. App app = new App();
  8. app.say();
  9. }
  10. }

切面类:

  1. package com.mythsman.test;
  2. import org.aspectj.lang.annotation.After;
  3. import org.aspectj.lang.annotation.Aspect;
  4. import org.aspectj.lang.annotation.Before;
  5. import org.aspectj.lang.annotation.Pointcut;
  6. @Aspect
  7. public class AnnoAspect {
  8. @Pointcut("execution(* com.mythsman.test.App.say(..))")
  9. public void jointPoint() {
  10. }
  11. @Before("jointPoint()")
  12. public void before() {
  13. System.out.println("AnnoAspect before say");
  14. }
  15. @After("jointPoint()")
  16. public void after() {
  17. System.out.println("AnnoAspect after say");
  18. }
  19. }

运行结果:

  1. AnnoAspect before say
  2. App say
  3. AnnoAspect after say

Spring AOP 和 aspectjweaver.jar 关系
Spring 2.0 以后,引入了 @AspectJ 和 Schema-based 的两种配置方式(Java注解、XML配置)。注意,@AspectJ 和 AspectJ 没多大关系,并不是说基于 AspectJ 实现的,而仅仅是使用了 AspectJ 中的概念,包括使用的注解也是直接来自于 AspectJ 的包。所以我们在使用 Spring AOP 时,需要引入 aspectjweaver.jar 这个依赖包,这个包来自于 AspectJ,使用 maven 引入这个依赖:

  1. <dependency>
  2. <groupId>org.aspectj</groupId>
  3. <artifactId>aspectjweaver</artifactId>
  4. <version>1.8.11</version>
  5. </dependency>

如果是使用 Spring Boot 的话,添加以下依赖即可:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-aop</artifactId>
  4. </dependency>

在 @AspectJ 的配置方式中,之所以要引入 aspectjweaver.jar 并不是因为我们需要使用 AspectJ 的处理功能,而是因为 Spring 使用了 AspectJ 提供的一些注解,实际上还是纯的 Spring AOP 代码。说了这么多,明确一点,@AspectJ 采用注解的方式来配置使用 Spring AOP。

首先,我们需要开启 @AspectJ 的注解配置方式,在 Spring 配置文件中加入:

  1. <aop:aspectj-autoproxy/>

一旦开启了上面的配置,那么所有使用 @Aspect 注解的 bean 都会被 Spring 当做用来实现 AOP 的配置类,我们称之为一个 Aspect。注意了,@Aspect 注解要作用在 bean 上面,不管是使用 @Component 等注解方式,还是在 xml 中配置 bean,首先它需要是一个 bean。比如下面这个 bean,它的类名上使用了 @Aspect,它就会被当做 Spring AOP 的配置。

  1. <bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
  2. <!-- configure properties of aspect here as normal -->
  3. </bean>
  1. package org.xyz;
  2. import org.aspectj.lang.annotation.Aspect;
  3. @Aspect
  4. public class NotVeryUsefulAspect {
  5. // TODO
  6. }

AOP 核心概念

Aspect 切面:切面由 pointcutadvice 组成,pointcut 和 advice 是相辅相成的,advice 为增强代码(方法),pointcut 为切入位置(表达式)。可以简单地认为,被 @Aspect 标注的 Class 就是切面。

Advice 增强:所谓增强就是一段代码(在 Java 中就是一个方法),有人喜欢将 Advice 直译为“通知”,这种翻译其实一点都不好,我觉得翻译为“增强”更好一点,比如日志记录就是一个增强,权限检查也是一个增强。

PointCut 切点:切点是一个表达式,用来告诉 advice 应该给哪些 join point 增强,join point 即连接点,所谓连接点就是可以被增强的实体,在 AspectJ 中,join point 可以是方法、字段等,而在 Spring AOP 中,join point 只能是方法(因为动态代理是基于方法的,所以存在这个限制)。

JoinPoint 连接点:连接点是可以被 Advice 增强的实体,在 AspectJ 中,join point 可以是方法也可以是字段;在 Spring AOP 中,join point 始终是方法。注意:join point 是实体,pointcut 是表达式。

Weaving 织入:所谓织入就是将切面类和业务类合并在一起的动作,Spring AOP 使用动态代理进行切面织入。

关于 AOP 的四个核心概念,上面的解释已经很清楚了,如果你还不懂,再来看一下这个通俗易懂的例子:

Java注解方式、XML配置方式
Spring AOP 支持两种 AOP 配置方法:一种是基于 Java 注解(常用),一种是基于 XML 文件(较繁琐)。因为基于 XML 文件的配置方式很麻烦,所以大部分人都是使用注解方式来进行 AOP 配置的,因为 Spring AOP 的注解配置方式完全是从 AspectJ 那里照搬过来的,所以需要在 maven 中引入 aspectjweaver.jar 依赖,然后修改 Spring 配置文件,添加下面这行配置,来启用 @AspectJ 注解风格的配置方式:

  1. <!-- 优先考虑 JDK 动态代理,不行在考虑 CGLIB 动态代理 -->
  2. <aop:aspectj-autoproxy/>
  3. <!-- 告诉 Spring AOP,不使用 JDK 动态代理,只用 CGLIB -->
  4. <aop:aspectj-autoproxy proxy-target-class="true"/>

声明 Aspect 切面
声明一个 Aspect 切面很简单,只需给 bean 类加上 @Aspect 注解就行:

  1. package org.xyz;
  2. import org.aspectj.lang.annotation.Aspect;
  3. @Aspect
  4. public class AspectModule {
  5. }

前面说了,Spring AOP 只作用于 IoC 容器中的 bean,所以需要注册为 bean:

  1. <bean id = "myAspect" class = "org.xyz.AspectModule">
  2. <!-- configure properties of aspect here as normal -->
  3. </bean>

声明 PointCut 切点
声明一个 PointCut 切点:我们知道 Aspect 切面由两个部分组成,一个是 Advice 增强,一个是 PointCut 切点,因为是使用注解来声明切点,所以我们需要在一个空方法上面标注 @PointCut 注解,被 @PointCut 注解标注的方法应该是空的,因为它没有实际的作用,它的作用仅仅是承载 @PointCut 注解而已,并且便于 Advice 引用它。承载切点的方法签名一般为 private void MethodName() {}(方法体为空),例子:

  1. import org.aspectj.lang.annotation.PointCut;
  2. @PointCut("execution(* com.xyz.myapp.service.*.*(..))")
  3. private void businessService() {}

我们实际关心的是 @PointCut 里面的 value 元素值,这是一个 String 类型的表达式,用来匹配要被增强的方法,比如上面这个例子,将匹配 com.xyz.myapp.service 包下所有类中的所有方法。被 @PointCut 注解的方法的名称就是连接点的名称,我们可以在 Advice 增强注解上面引用它。

再来看一个例子,它将匹配 com.tutorialspoint 包下的 Student 类的所有 getName() 方法:

  1. @PointCut("execution(* com.tutorialspoint.Student.getName(..))")
  2. private void getName() {}

声明 Advice 增强

  1. @Before("businessService()")
  2. public void doBeforeTask(){
  3. // TODO
  4. }
  5. @After("businessService()")
  6. public void doAfterTask(){
  7. // TODO
  8. }
  9. @AfterReturning(PointCut = "businessService()", returning = "retVal")
  10. public void doAfterReturnningTask(Object retVal){
  11. // TODO
  12. }
  13. @AfterThrowing(PointCut = "businessService()", throwing = "ex")
  14. public void doAfterThrowingTask(Exception ex){
  15. // TODO
  16. }
  17. @Around("businessService()")
  18. public void doAroundTask(){
  19. // TODO
  20. }

当然,Aspect 切面中可以没有 PointCut,因为我们可以在 Advice 上面内联对应的 PointCut 表达式:

  1. @Before("execution(* com.xyz.myapp.service.*.*(..))")
  2. public doBeforeTask(){
  3. // TODO
  4. }

注意到没?我们可以直接在 Advice 中内联 PointCut 表达式,写法与在 @PointCut 上面的表达式一样,一个最佳实践是,如果一个 PointCut 表达式在多处地方都被引用了,那么就不建议使用内联方式了,因为这会导致重复代码,如果后期要修改表达式的内容,就意味着要同时修改多个地方的表达式,不利于维护。这个时候应该使用原始方法来定义,即在一个空方法上定义 PointCut 表达式,然后在 Advice 上使用 MethodName() 来引用对应的 PointCut 表达式内容,如上所示。有必要说明一点,Spring AOP 只能增强 public 方法。

关于 PointCut 表达式的语法,这里简单的说一下,最常见的就是下面这种了:

  1. @PointCut("execution(* com.tutorialspoint.Student.getName(..))")

最常见的描述符就是 execution,其具体的语法可以用下面这个表达式来概括:

  1. execution([modifiers-pattern] returnType-pattern name-pattern(param-pattern) [throws-pattern])

其实就是一个普通的 Java 方法定义而已,只不过可以使用某些特殊的通配符来进行匹配,就这么简单。其中用方括号标识的部分是可以省略的,还有一个需要注意的地方就是这个 name-pattern 是全限定方法名,所谓全限定方法名就是包含其对应的全限定类名的方法名,也比较好理解。有这么一些特殊的关键字:

上面的描述有些不太准确,我们来看另一个文档的描述,关于 pattern 中的通配符:

5 种 Advice 类型

AOP HelloWorld

配置 pom.xml,引入 spring-context、spring-aop、aspectjweaver 依赖:

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-context</artifactId>
  4. <version>${spring.version}</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework</groupId>
  8. <artifactId>spring-aop</artifactId>
  9. <version>${spring.version}</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.aspectj</groupId>
  13. <artifactId>aspectjweaver</artifactId>
  14. <version>1.9.2</version>
  15. </dependency>

编写业务类,HelloWorld.java,包含一个简单的 hello() 方法,打印一行字符串:

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class HelloWorld {
  5. public void hello() {
  6. System.out.println("Hello, World!");
  7. }
  8. public static void main(String[] args) {
  9. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  10. HelloWorld helloWorld = (HelloWorld) context.getBean("helloWorld");
  11. helloWorld.hello();
  12. }
  13. }

编写切面类,HelloAspect.java,定义了一个 hello() 切点,以及 Before 增强和 After 增强:

  1. package com.zfl9;
  2. import org.aspectj.lang.annotation.After;
  3. import org.aspectj.lang.annotation.Aspect;
  4. import org.aspectj.lang.annotation.Before;
  5. import org.aspectj.lang.annotation.Pointcut;
  6. @Aspect
  7. public class HelloAspect {
  8. @Pointcut("execution(void com.zfl9.HelloWorld.hello())")
  9. private void hello() {}
  10. @Before("hello()")
  11. public void beforeHello() {
  12. System.out.println("before hello()");
  13. }
  14. @After("hello()")
  15. public void afterHello() {
  16. System.out.println("after hello()");
  17. }
  18. }

编写 beans.xml(Spring 配置文件,因为需要从 ClassPath 中获取,所以放到 resources 目录):

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
  5. <aop:aspectj-autoproxy/>
  6. <bean id="helloWorld" class="com.zfl9.HelloWorld"/>
  7. <bean id="helloAspect" class="com.zfl9.HelloAspect"/>
  8. </beans>

然后运行,输出结果如下,可以发现 AOP 织入正常,没有问题:

  1. before hello()
  2. Hello, World!
  3. after hello()

Before 增强

所谓 Before 增强就是在执行目标方法之前,先执行我们的增强代码,再执行目标方法。

  1. @PointCut("execution(* com.tutorialspoint.Student.getName(..))")
  2. private void selectGetName(){}
  3. @Before("selectGetName()")
  4. public void beforeAdvice(){
  5. System.out.println("Going to setup student profile.");
  6. }

After 增强

所谓 After 增强就是在执行目标方法之后(无论执行成功还是失败),执行我们的增强代码。

  1. @PointCut("execution(* com.tutorialspoint.Student.getAge(..))")
  2. private void selectGetName(){}
  3. @After("selectGetAge()")
  4. public void afterAdvice(){
  5. System.out.println("Student profile setup completed.");
  6. }

AfterReturning 增强

AfterReturning 增强属于 After 细分的一种,即只有方法执行成功后才会执行此增强,例子:

业务类:

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class HelloWorld {
  5. public String hello() {
  6. System.out.println("hello, world!");
  7. return "return: hello, world";
  8. }
  9. public static void main(String[] args) {
  10. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  11. HelloWorld helloWorld = (HelloWorld) context.getBean("helloWorld");
  12. helloWorld.hello();
  13. }
  14. }

切面类:

  1. package com.zfl9;
  2. import org.aspectj.lang.JoinPoint;
  3. import org.aspectj.lang.annotation.AfterReturning;
  4. import org.aspectj.lang.annotation.Aspect;
  5. import org.aspectj.lang.annotation.Pointcut;
  6. @Aspect
  7. public class HelloAspect {
  8. @Pointcut("execution(* com.zfl9.HelloWorld.hello())")
  9. private void hello() {}
  10. @AfterReturning(pointcut = "hello()", returning = "returnValue")
  11. public void afterReturning(JoinPoint joinPoint, Object returnValue) {
  12. System.out.println(joinPoint.getSignature()); // method sign
  13. System.out.println(returnValue.toString()); // return value
  14. }
  15. }

运行结果:

  1. hello, world!
  2. String com.zfl9.HelloWorld.hello()
  3. return: hello, world

AfterThrowing 增强

AfterThrowing 增强也是 After 细分的一种,当目标方法在执行过程中抛出异常后,才会执行此增强,例子:

业务类:

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class HelloWorld {
  5. public void hello() {
  6. System.out.println("hello, world!");
  7. throw new RuntimeException("runtime exception");
  8. }
  9. public static void main(String[] args) {
  10. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  11. HelloWorld helloWorld = (HelloWorld) context.getBean("helloWorld");
  12. helloWorld.hello();
  13. }
  14. }

切面类:

  1. package com.zfl9;
  2. import org.aspectj.lang.JoinPoint;
  3. import org.aspectj.lang.annotation.AfterThrowing;
  4. import org.aspectj.lang.annotation.Aspect;
  5. import org.aspectj.lang.annotation.Pointcut;
  6. @Aspect
  7. public class HelloAspect {
  8. @Pointcut("execution(void com.zfl9.HelloWorld.hello())")
  9. private void hello() {}
  10. @AfterThrowing(pointcut = "hello()", throwing = "exception")
  11. public void afterReturning(JoinPoint joinPoint, Exception exception) {
  12. System.out.println(joinPoint.getSignature()); // method sign
  13. System.out.println(exception.getMessage()); // exception obj
  14. }
  15. }

运行结果:

  1. hello, world!
  2. Exception in thread "main" java.lang.RuntimeException: runtime exception
  3. void com.zfl9.HelloWorld.hello()
  4. runtime exception

Around 增强

Around 增强其实就是 Before 增强和 After 增强的结合版,我们来看下面这个简单的例子:

业务类:

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class HelloWorld {
  5. public int add(int a, int b) {
  6. return a + b;
  7. }
  8. public static void main(String[] args) {
  9. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  10. HelloWorld helloWorld = (HelloWorld) context.getBean("helloWorld");
  11. System.out.println("10 + 20 = " + helloWorld.add(10, 20));
  12. }
  13. }

切面类:注意,Around 增强需要返回目标方法的执行结果,执行结果的类型使用 Object 类就行了。

  1. package com.zfl9;
  2. import java.util.Arrays;
  3. import org.aspectj.lang.ProceedingJoinPoint;
  4. import org.aspectj.lang.annotation.Around;
  5. import org.aspectj.lang.annotation.Aspect;
  6. import org.aspectj.lang.annotation.Pointcut;
  7. @Aspect
  8. public class HelloAspect {
  9. @Pointcut("execution(int com.zfl9.HelloWorld.add(int, int))")
  10. private void add() {}
  11. @Around("add()")
  12. public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
  13. System.out.println("method: " + joinPoint.getSignature().getName());
  14. System.out.println("argument: " + Arrays.toString(joinPoint.getArgs()));
  15. System.out.println("before add()");
  16. Object result = joinPoint.proceed();
  17. System.out.println("return value: " + result);
  18. System.out.println("after add()");
  19. return result;
  20. }
  21. }

运行结果:

  1. method: add
  2. argument: [10, 20]
  3. before add()
  4. return value: 30
  5. after add()
  6. 10 + 20 = 30

注意,JoinPoint 和 ProceedingJoinPoint 都是 Interface,后者是前者的子接口,后者增加了一个 proceed() 方法,这个方法的作用就是用来执行目标方法的,它有两个重载版本,一个是不带参数的(如上所示),这种方法调用不会改变目标方法的参数,即使用原有参数;另一个是带有 Object[] args 参数的,这个 args 数组就是表示目标方法的参数(不包括 this 指针)。定义如下(JoinPoint 是目标方法的抽象):

JoinPoint.java

  1. public interface JoinPoint {
  2. String getKind(); // 返回连接点的类型
  3. StaticPart getStaticPart(); // 返回连接点的静态部分
  4. String toString(); // 连接点所在位置的相关信息
  5. String toShortString(); // 连接点所在位置的简短信息
  6. String toLongString(); // 连接点所在位置的详细信息
  7. Object getThis(); // AOP 代理对象,代理类对象
  8. Object getTarget(); // 返回目标对象,委托类对象
  9. Object[] getArgs(); // 返回被增强方法的参数列表
  10. Signature getSignature(); // 返回当前连接点的方法签名
  11. SourceLocation getSourceLocation(); // 返回连接点的源文件的位置
  12. }

ProceedingJoinPoint.java

  1. public interface ProceedingJoinPoint extends JoinPoint {
  2. public Object proceed() throws Throwable;
  3. public Object proceed(Object[] args) throws Throwable;
  4. }

在 Around 增强中,第一个参数为 ProceedingJoinPoint,因为 Around 增强要返回目标方法的执行结果。
除了 Around 增强外,其他四个增强方法的参数都可以为空,当然第一个参数也可以都为 JoinPoint。例子:

参数都为空
业务类:

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class HelloWorld {
  5. public void testForBeforeAdvice() {
  6. System.out.println("testForBeforeAdvice()");
  7. }
  8. public void testForAfterAdvice() {
  9. System.out.println("testForAfterAdvice()");
  10. }
  11. public String testForAfterReturningAdvice() {
  12. System.out.println("testForAfterReturningAdvice()");
  13. return "testForAfterReturningAdvice-result";
  14. }
  15. public void testForAfterThrowingAdvice() {
  16. System.out.println("testForAfterThrowingAdvice()");
  17. throw new RuntimeException("test-runtime-exception");
  18. }
  19. public String testForAroundAdvice() {
  20. System.out.println("testForAroundAdvice()");
  21. return "testForAroundAdvice-result";
  22. }
  23. public static void main(String[] args) {
  24. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  25. HelloWorld helloWorld = (HelloWorld) context.getBean("helloWorld");
  26. helloWorld.testForBeforeAdvice();
  27. helloWorld.testForAfterAdvice();
  28. helloWorld.testForAfterReturningAdvice();
  29. try {
  30. helloWorld.testForAfterThrowingAdvice();
  31. } catch (RuntimeException e) {
  32. System.out.println(e.getMessage());
  33. }
  34. helloWorld.testForAroundAdvice();
  35. }
  36. }

切面类:

  1. package com.zfl9;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.After;
  4. import org.aspectj.lang.annotation.AfterReturning;
  5. import org.aspectj.lang.annotation.AfterThrowing;
  6. import org.aspectj.lang.annotation.Around;
  7. import org.aspectj.lang.annotation.Aspect;
  8. import org.aspectj.lang.annotation.Before;
  9. @Aspect
  10. public class HelloAspect {
  11. @Before("execution(* com.zfl9.HelloWorld.testForBeforeAdvice(..))")
  12. public void beforeAdvice() {
  13. System.out.println("inside before advice");
  14. }
  15. @After("execution(* com.zfl9.HelloWorld.testForAfterAdvice(..))")
  16. public void afterAdvice() {
  17. System.out.println("inside after advice");
  18. }
  19. @AfterReturning(pointcut = "execution(* com.zfl9.HelloWorld.testForAfterReturningAdvice(..))")
  20. public void afterReturningAdvice() {
  21. System.out.println("inside afterReturning advice");
  22. }
  23. @AfterThrowing(pointcut = "execution(* com.zfl9.HelloWorld.testForAfterThrowingAdvice(..))")
  24. public void afterThrowingAdvice() {
  25. System.out.println("inside afterThrowing advice");
  26. }
  27. @Around(value = "execution(* com.zfl9.HelloWorld.testForAroundAdvice(..))")
  28. public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
  29. System.out.println("inside around advice");
  30. return joinPoint.proceed();
  31. }
  32. }

执行结果:

  1. inside before advice
  2. testForBeforeAdvice()
  3. testForAfterAdvice()
  4. inside after advice
  5. testForAfterReturningAdvice()
  6. inside afterReturning advice
  7. testForAfterThrowingAdvice()
  8. inside afterThrowing advice
  9. test-runtime-exception
  10. inside around advice
  11. testForAroundAdvice()

参数都不为空
切面类:

  1. package com.zfl9;
  2. import org.aspectj.lang.JoinPoint;
  3. import org.aspectj.lang.ProceedingJoinPoint;
  4. import org.aspectj.lang.annotation.After;
  5. import org.aspectj.lang.annotation.AfterReturning;
  6. import org.aspectj.lang.annotation.AfterThrowing;
  7. import org.aspectj.lang.annotation.Around;
  8. import org.aspectj.lang.annotation.Aspect;
  9. import org.aspectj.lang.annotation.Before;
  10. @Aspect
  11. public class HelloAspect {
  12. @Before("execution(* com.zfl9.HelloWorld.testForBeforeAdvice(..))")
  13. public void beforeAdvice(JoinPoint joinPoint) {
  14. System.out.println("inside before advice");
  15. System.out.println("joinPoint: " + joinPoint);
  16. }
  17. @After("execution(* com.zfl9.HelloWorld.testForAfterAdvice(..))")
  18. public void afterAdvice(JoinPoint joinPoint) {
  19. System.out.println("inside after advice");
  20. System.out.println("joinPoint: " + joinPoint);
  21. }
  22. @AfterReturning(pointcut = "execution(* com.zfl9.HelloWorld.testForAfterReturningAdvice(..))")
  23. public void afterReturningAdvice(JoinPoint joinPoint) {
  24. System.out.println("inside afterReturning advice");
  25. System.out.println("joinPoint: " + joinPoint);
  26. }
  27. @AfterThrowing(pointcut = "execution(* com.zfl9.HelloWorld.testForAfterThrowingAdvice(..))")
  28. public void afterThrowingAdvice(JoinPoint joinPoint) {
  29. System.out.println("inside afterThrowing advice");
  30. System.out.println("joinPoint: " + joinPoint);
  31. }
  32. @Around(value = "execution(* com.zfl9.HelloWorld.testForAroundAdvice(..))")
  33. public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
  34. System.out.println("inside around advice");
  35. System.out.println("joinPoint: " + joinPoint);
  36. return joinPoint.proceed();
  37. }
  38. }

执行结果:

  1. inside before advice
  2. joinPoint: execution(void com.zfl9.HelloWorld.testForBeforeAdvice())
  3. testForBeforeAdvice()
  4. testForAfterAdvice()
  5. inside after advice
  6. joinPoint: execution(void com.zfl9.HelloWorld.testForAfterAdvice())
  7. testForAfterReturningAdvice()
  8. inside afterReturning advice
  9. joinPoint: execution(String com.zfl9.HelloWorld.testForAfterReturningAdvice())
  10. testForAfterThrowingAdvice()
  11. inside afterThrowing advice
  12. joinPoint: execution(void com.zfl9.HelloWorld.testForAfterThrowingAdvice())
  13. test-runtime-exception
  14. inside around advice
  15. joinPoint: execution(String com.zfl9.HelloWorld.testForAroundAdvice())
  16. testForAroundAdvice()

AfterReturning 和 AfterThrowing 的第二个参数

  1. package com.zfl9;
  2. import org.aspectj.lang.JoinPoint;
  3. import org.aspectj.lang.ProceedingJoinPoint;
  4. import org.aspectj.lang.annotation.After;
  5. import org.aspectj.lang.annotation.AfterReturning;
  6. import org.aspectj.lang.annotation.AfterThrowing;
  7. import org.aspectj.lang.annotation.Around;
  8. import org.aspectj.lang.annotation.Aspect;
  9. import org.aspectj.lang.annotation.Before;
  10. @Aspect
  11. public class HelloAspect {
  12. @Before("execution(* com.zfl9.HelloWorld.testForBeforeAdvice(..))")
  13. public void beforeAdvice(JoinPoint joinPoint) {
  14. System.out.println("inside before advice");
  15. System.out.println("joinPoint: " + joinPoint);
  16. }
  17. @After("execution(* com.zfl9.HelloWorld.testForAfterAdvice(..))")
  18. public void afterAdvice(JoinPoint joinPoint) {
  19. System.out.println("inside after advice");
  20. System.out.println("joinPoint: " + joinPoint);
  21. }
  22. @AfterReturning(pointcut = "execution(* com.zfl9.HelloWorld.testForAfterReturningAdvice(..))", returning = "result")
  23. public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
  24. System.out.println("inside afterReturning advice");
  25. System.out.println("joinPoint: " + joinPoint);
  26. System.out.println("result: " + result);
  27. }
  28. @AfterThrowing(pointcut = "execution(* com.zfl9.HelloWorld.testForAfterThrowingAdvice(..))", throwing = "throwable")
  29. public void afterThrowingAdvice(JoinPoint joinPoint, Throwable throwable) {
  30. System.out.println("inside afterThrowing advice");
  31. System.out.println("joinPoint: " + joinPoint);
  32. System.out.println("throwable: " + throwable);
  33. }
  34. @Around(value = "execution(* com.zfl9.HelloWorld.testForAroundAdvice(..))")
  35. public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
  36. System.out.println("inside around advice");
  37. System.out.println("joinPoint: " + joinPoint);
  38. return joinPoint.proceed();
  39. }
  40. }
  1. inside before advice
  2. joinPoint: execution(void com.zfl9.HelloWorld.testForBeforeAdvice())
  3. testForBeforeAdvice()
  4. testForAfterAdvice()
  5. inside after advice
  6. joinPoint: execution(void com.zfl9.HelloWorld.testForAfterAdvice())
  7. testForAfterReturningAdvice()
  8. inside afterReturning advice
  9. joinPoint: execution(String com.zfl9.HelloWorld.testForAfterReturningAdvice())
  10. result: testForAfterReturningAdvice-result
  11. testForAfterThrowingAdvice()
  12. inside afterThrowing advice
  13. joinPoint: execution(void com.zfl9.HelloWorld.testForAfterThrowingAdvice())
  14. throwable: java.lang.RuntimeException: test-runtime-exception
  15. test-runtime-exception
  16. inside around advice
  17. joinPoint: execution(String com.zfl9.HelloWorld.testForAroundAdvice())
  18. testForAroundAdvice()

几个特殊的配置

<context:annotation-config/>
激活 context 中所有 bean 上面的注解(如 @Autowired 注解)。

<context:component-scan base-package="com.zfl9"/>
扫描指定包(含所有子包,递归扫描)下的所有 bean 定义注解,并注册到 context 中,并激活 context 中所有 bean 上面的注解,所以如果定义了此元素,就不再需要定义 <context:annotation-config/> 元素。

<mvc:annotation-driven/>
用在 Spring MVC 中,虽然不定义这个元素 Web 程序一般也能运行,但最好还是加上该元素。此元素将注册将 @Controller 所需的 HandlerMappingHandlerAdapter bean。此外,该元素还会设置一些默认值。

mvc:annotation-driven 的作用已经详细介绍了,你只要记住,使用 Spring MVC 时,在 Spring 配置文件上加上这行准没错。而 context:annotation-config 其实在最开头的 IoC 容器中就学习了,它的作用是用来激活已经在 bean 容器中的 bean 上面的注解(@Autowired、@Resource、@Require 等),这里有一个关键点,那就是在 IoC 容器中的 bean,所以我们还是需要先在 beans.xml 中注册对应的 bean,Spring 才会扫描 bean 上面的注解。而 context:component-scan 的作用是递归扫描指定 package 下面的 Class 文件,如果发现被 @Component 标注的类(包括 @Component 的子注解),那么 Spring 就会把他们当作一个 bean,然后注册到 bean 容器中(实例化它们的实例),并且还会激活 IoC 容器中的所有 bean 上面的注解(@Autowired、@Resource、@Require 等),所以如果配置了 component-scan 元素,就不需要配置 annotation-config 元素了,因为没有这个必要了。一般为了方便,我们都会在 beans.xml 中加上 context:component-scan 配置,这样就不需要在 beans.xml 中定义 <bean> 元素来手动注册 bean 了。

总结:

@Component、@Controller、@Service、@Repository 注解

当然你完全可以全部组件都使用 @Component 注解,但是为了符合语义,非常不建议这么做,而且 Spring 分出三个子注解肯定是有原因的,而且可能对不同的子注解还有不同的处理,最好按照约定做事,不要自找麻烦。

这四个注解都有一个 value 属性,表示 bean 的名称,默认情况下,bean 名称为对应的类名(首字母小写,注意不是全限定类名,比如 com.zfl9.service.MyService 对应的 bean 名称就是 myService)。不过我觉得如果想要使用 bean 名称来引用对应的 bean 的话,最好还是给这些注解设置 value 属性,免得发生歧义。

注意,我们使用 context.getBean() 来获取 Bean 对象时,除了可以使用 bean 名称外,还可以使用 bean 对应的 java.lang.Class 对象实例,比如 context.getBean(com.zfl9.service.MyService.class)

context:component-scan 扫描多个包

  1. <context:component-scan base-package="x.y.service, x.y.controller, x.y.dao"/>

JDBC 模板

配置 pom.xml,引入 spring-jdbc、mysql-jdbc-driver 依赖:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project>
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.zfl9</groupId>
  5. <artifactId>JavaSE_HelloWorld</artifactId>
  6. <version>1.0-SNAPSHOT</version>
  7. <properties>
  8. <maven.test.skip>true</maven.test.skip>
  9. <maven.compiler.source>1.8</maven.compiler.source>
  10. <maven.compiler.target>1.8</maven.compiler.target>
  11. <spring.version>4.3.20.RELEASE</spring.version>
  12. <mysqlDriver.version>8.0.13</mysqlDriver.version>
  13. </properties>
  14. <dependencies>
  15. <dependency>
  16. <groupId>org.springframework</groupId>
  17. <artifactId>spring-context</artifactId>
  18. <version>${spring.version}</version>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.springframework</groupId>
  22. <artifactId>spring-jdbc</artifactId>
  23. <version>${spring.version}</version>
  24. </dependency>
  25. <dependency>
  26. <groupId>mysql</groupId>
  27. <artifactId>mysql-connector-java</artifactId>
  28. <version>${mysqlDriver.version}</version>
  29. </dependency>
  30. </dependencies>
  31. </project>

JdbcTemplate 和 DbUtils 都是轻量级的 JDBC 帮助库,之所以被称为“轻量级 Helper”,是因为他们仅仅是 JDBC API 的简单封装,目的是为了简化 JDBC API 繁琐的操作,让我们专心编写 SQL 语句。这两个类库都非常优秀,关于 DbUtils,在之前的 jdbc 学习中我已经详细介绍了,现在我们来学习一下 JdbcTemplate。

和 DbUtils 一样,JdbcTemplate 的基本操作也是 query()、update()、batchUpdate()、execute()。如果想要了解 JdbcTemplate 的全部 API,请移步 JavaDoc,这里我们列举一些常用的 JdbcTemplate 方法:

  1. /* queryForObject() 单行单列 */
  2. <T> T queryForObject(String sql, Class<T> requiredType)
  3. <T> T queryForObject(String sql, Class<T> requiredType, Object... args)
  4. /* queryForObject() 单行多列 */
  5. <T> T queryForObject(String sql, RowMapper<T> rowMapper)
  6. <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
  7. /* queryForMap() 单行查询 */
  8. Map<String, Object> queryForMap(String sql)
  9. Map<String, Object> queryForMap(String sql, Object... args)
  10. /* queryForList() 多行查询 */
  11. List<Map<String, Object>> queryForList(String sql)
  12. List<Map<String, Object>> queryForList(String sql, Object... args)
  13. <T> List<T> queryForList(String sql, Class<T> elementType)
  14. <T> List<T> queryForList(String sql, Class<T> elementType, Object... args)
  15. /* query() 多行查询 */
  16. <T> List<T> query(String sql, RowMapper<T> rowMapper)
  17. <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args)
  18. /* update() */
  19. int update(String sql)
  20. int update(String sql, Object... args)
  21. /* batchUpdate() */
  22. int[] batchUpdate(String... sql)
  23. int[] batchUpdate(String sql, List<Object[]> args)
  24. /* execute() */
  25. void execute(String sql)

创建 Student 表:

  1. CREATE TABLE `student` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT,
  3. `name` varchar(20) NOT NULL,
  4. `age` int(11) NOT NULL,
  5. PRIMARY KEY (`id`)
  6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

创建 Student.java:

  1. package com.zfl9;
  2. public class Student {
  3. private int id;
  4. private String name;
  5. private int age;
  6. public int getId() {
  7. return id;
  8. }
  9. public void setId(int id) {
  10. this.id = id;
  11. }
  12. public String getName() {
  13. return name;
  14. }
  15. public void setName(String name) {
  16. this.name = name;
  17. }
  18. public int getAge() {
  19. return age;
  20. }
  21. public void setAge(int age) {
  22. this.age = age;
  23. }
  24. @Override
  25. public String toString() {
  26. return String.format("[id = %d, name = %s, age = %d]", id, name, age);
  27. }
  28. }

创建 StudentMapper.java

  1. package com.zfl9;
  2. import java.sql.ResultSet;
  3. import java.sql.SQLException;
  4. import org.springframework.jdbc.core.RowMapper;
  5. public class StudentMapper implements RowMapper<Student> {
  6. @Override
  7. public Student mapRow(ResultSet resultSet, int i) throws SQLException {
  8. Student student = new Student();
  9. student.setId(resultSet.getInt("id"));
  10. student.setName(resultSet.getString("name"));
  11. student.setAge(resultSet.getInt("age"));
  12. return student;
  13. }
  14. }

创建 StudentDao.java

  1. package com.zfl9;
  2. import java.util.List;
  3. import javax.sql.DataSource;
  4. import org.springframework.jdbc.core.JdbcTemplate;
  5. public class StudentDao {
  6. private JdbcTemplate jdbcTemplate;
  7. public void setDataSource(DataSource dataSource) {
  8. jdbcTemplate = new JdbcTemplate(dataSource);
  9. }
  10. public Student select(int id) {
  11. String sql = "select * from Student where id = ?";
  12. return jdbcTemplate.queryForObject(sql, new StudentMapper(), id);
  13. }
  14. public List<Student> selectAll() {
  15. String sql = "select * from Student";
  16. return jdbcTemplate.query(sql, new StudentMapper());
  17. }
  18. public void create(String name, int age) {
  19. String sql = "insert into Student (name, age) values (?, ?)";
  20. jdbcTemplate.update(sql, name, age);
  21. }
  22. public void update(int id, int age) {
  23. String sql = "update Student set age = ? where id = ?";
  24. jdbcTemplate.update(sql, age, id);
  25. }
  26. public void delete(int id) {
  27. String sql = "delete from Student where id = ?";
  28. jdbcTemplate.update(sql, id);
  29. }
  30. public void deleteAll() {
  31. String sql = "truncate table Student";
  32. jdbcTemplate.update(sql);
  33. }
  34. }

创建 StudentMain.java

  1. package com.zfl9;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class StudentMain {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
  7. StudentDao studentDao = context.getBean("studentDao", StudentDao.class);
  8. /* create student */
  9. studentDao.create("小明", 15);
  10. studentDao.create("小红", 14);
  11. studentDao.create("小刚", 15);
  12. /* select student */
  13. for (Student student : studentDao.selectAll()) {
  14. System.out.println(student);
  15. }
  16. /* update student */
  17. studentDao.update(1, 13);
  18. /* select student */
  19. for (Student student : studentDao.selectAll()) {
  20. System.out.println(student);
  21. }
  22. /* delete student */
  23. studentDao.deleteAll();
  24. /* select student */
  25. for (Student student : studentDao.selectAll()) {
  26. System.out.println(student);
  27. }
  28. }
  29. }

配置 spring.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  6. <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
  7. <property name="url" value="jdbc:mysql://localhost/test?serverTimezone=UTC"/>
  8. <property name="username" value="root"/>
  9. <property name="password" value="123456"/>
  10. </bean>
  11. <bean id="studentDao" class="com.zfl9.StudentDao">
  12. <property name="dataSource" ref="dataSource"/>
  13. </bean>
  14. </beans>

运行结果:

  1. [id = 1, name = 小明, age = 15]
  2. [id = 2, name = 小红, age = 14]
  3. [id = 3, name = 小刚, age = 15]
  4. [id = 1, name = 小明, age = 13]
  5. [id = 2, name = 小红, age = 14]
  6. [id = 3, name = 小刚, age = 15]
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注