Spring(Java框架)

概述

是什么

Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IOC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多 著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。

优点

  1. 面向接口编程,而不是针对类编程。Spring框架将使用接口的复杂度降低到零。
  2. Spring是一个开源免费的框架(容器)
  3. Spring是一个轻量级的、非入侵式的框架。
  4. 控制反转(IOC),面向切面编程(AOP)
  5. 支持事务的处理,对框架整合的支持

Spring的作用

  1. 方便解耦、简化开发 通过 Spring提供的 IoC容器,可以将对象间的依赖关系全部交给Spring容器进行管理,避免硬编码所造成的过度程序混乱和耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可 以更专注于上层的应用。
  2. 对AOP编程的支持 通过 Spring的 AOP 功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过 AOP 轻松应付。
  3. 对声明式事务的支持 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理, 提高开发效率和质量。
  4. 方便程序的测试 ,可以用非容器依赖的编程方式进行测试工作。
  5. 降低对各类框架集成的难度,Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持。
  6. 降低 JavaEE API的使用难度 Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了封装,使这些 API 的使用难度降低。

Spring体系结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i0sQzKeR-1627552286710)(C:\Users\Bear\Desktop\公众号素材图片\spring.png)]

上图是Spring框架的体系结构,这些组件被分为多个模块,分别是Core Container(核心容器),AOP(Aspect Oriented Programming (面向切面编程))、数据层访问集成(Data Access/Integratioin),web层整合及Test单元测试组件。

  1. Core模块是Spring的核心容器,它实现了IOC容器模式,提供了Spring框架的基础功能。此模块中包含的BeanFactory类是Spring的核心类,负责JavaBean的配置与管理。它采用Factory工厂模式实现了IOC即依赖注入。
  2. Context模块继承BeanFactory(或者说Spring核心)类,并且添加了事件处理、国际化、资源装载、透明装载、以及数据校验等功能。它还提供了框架式的Bean的访问方式和很多企业级的功能,如JNDI访问、支持EJB、远程调用、集成模板框架、Email和定时任务调度等。
  3. Spring的AOP模块可以通过事务管理可以使任意Spring管理的对象AOP化。Spring提供了用标准Java语言编写的AOP框架,它的大部分内容都是基于AOP联盟的API开发的。它使应用程序抛开EJB的复杂性,但拥有传统EJB的关键功能。
  4. DAO是DataAccessObject的缩写,DAO模式思想是将业务逻辑代码与数据库交互代码分离,降低两者耦合。通过DAO模式可以使结构变得更为清晰,代码更为简洁。DAO模块提供了JDBC的抽象层,简化了数据库厂商的异常错误(不再从SQLException继承大批代码),大幅度减少代码的编写,并且提供了对声明式事务和编程式事务的支持。
  5. Web模块建立在Context基础之上,它提供了Servlet监听器的Context和Web应用的上下文。对现有的Web框架,如JSF、Tapestry、Structs等,提供了集成。Structs是建立在MVC这种公认的好的模式上的,Struts在M、V和C上都有涉及,但它主要是提供一个好的控制器和一套定制的标签库上,也就是说它的着力点在C和V上,因此,它天生就有MVC所带来的一系列优点,如:结构层次分明,高可重用性,增加了程序的健壮性和可伸缩性,便于开发与设计分工,提供集中统一的权限控制、校验、国际化、日志等等。
  6. Test模块提供对使用JUnit和TestNG来测试Spring组件的支持,它提供一致的ApplicationContext并缓存这些上下文,它还能提供一些mock对象,可以独立地测试代码。

什么是程序的耦合

耦合性(Coupling),也叫耦合度,是模块之间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。

各个模块之间的联系越多,其耦合性就越强,同时表明其独立性越差( 降低耦合性,可以提高其独立扩展性)。在软件工程中,耦合指的就是对象之间的依赖性。

对象之间的耦合越高,维护成本越高。因此对象的设计应该使类和模块之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个 准则就是高内聚低耦合。

它有如下分类:

(1) 内容耦合:当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用。

(2) 公共耦合:两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。

(3) 外部耦合 :一组模块都访问同一全局变量而不是同一全局数据结构,而且不是通过参数表传 递该全局变量的信息,则称之为外部耦合。

(4) 控制耦合:一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进 行适当的动作,这种耦合被称为控制耦合。

(5) 标记耦合:若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间 存在一个标记耦合。

(6) 数据耦合:模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形 式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。

(7) 非直接耦合:两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。

总结

耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上应该采用以下原则:

内聚与耦合

如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。 内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。

耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。 内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。所以在进行软件设计时,应力争做到高内聚,低耦合。

耦合的通俗解释:

指程序之间的依赖关系,包括:类之间的依赖关系、方法间的依赖关系

解耦

解耦就是降低程序之间的依赖关系,应该做到编译期不依赖,运行时才依赖。

解耦的思路:使用反射来创建对象,而避免使用new关键字,或通过读取配置文件来获取要创建的对象全限定类名

Spring快速入门

新建maven工程

新建一个名为spring的maven工程并导入以下依赖

  1. <dependencies>
  2. <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
  3. <dependency>
  4. <groupId>org.springframework</groupId>
  5. <artifactId>spring-context</artifactId>
  6. <version>5.2.9.RELEASE</version>
  7. </dependency>
  8. <!-- 提供get&set 和构造方法的依赖 -->
  9. <dependency>
  10. <groupId>org.projectlombok</groupId>
  11. <artifactId>lombok</artifactId>
  12. <version>1.18.10</version>
  13. </dependency>
  14. <!-- 单元测试依赖 -->
  15. <dependency>
  16. <groupId>junit</groupId>
  17. <artifactId>junit</artifactId>
  18. <version>4.12</version>
  19. <scope>test</scope>
  20. </dependency>
  21. </dependencies>

创建实体对象

新建一个User对象并提供成员变量和get&set方法

  1. @Data //提供get&set方法的注解
  2. @ToString //提供toString方法的注解
  3. public class User {
  4. private String username;
  5. private Integer age;
  6. private String sex;
  7. private String height;
  8. }

编写配置类

在xml配置文件里把User对象交给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
  5. https://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <!-- 以上是spring头文件,必须导入 -->
  7. <!-- 在配置文件里给User对象赋值,交给spring容器去托管对象 -->
  8. <bean id="user" class="com.spring.practice.User">
  9. <!-- 以下使用set方法注入属性: name为成员变量名称,value为属性值-->
  10. <property name="username" value="Trump"/>
  11. <property name="age" value="3"/>
  12. <property name="sex" value="不男不女"/>
  13. <property name="height" value="150cm"/>
  14. </bean>
  15. </beans>

测试

使用ClassPathXmlApplicationContext读取配置类数据,并调用工厂方法获取User对象里的属性

  1. public class SpringTest {
  2. @Test
  3. public void testUser() {
  4. //创建一个Spring容器对象
  5. ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
  6. //获取bean配置的id值User字节码对象
  7. User user = context.getBean("user",User.class);
  8. System.out.println(user);
  9. }
  10. }

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EoLOakps-1627552286739)(C:\Users\Bear\Desktop\导图\spring.png)]

Sping常用API

ApplicationContext接口

org.springframework.context.ApplicationContext接口表示Spring的IOC容器,负责实例化、配置和组装bean。容器通过读取配置文件中配置的元数据来获取关于对象的实例化、配置和组装哪些对象的说明。配置元数据以XML形式、Java注释或Java代码表示。它允许您表示组成应用程序的对象以及这些对象之间的丰富相互依赖关系。

ApplicationContext接口的几个实现类由Spring提供,常见的做法是创建ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext实现类。虽然XML一直是定义配置元数据的传统格式,但是可以指定容器使用Java注释或代码替代XML形式作为元数据的配置,用法是提供少量XML配置以声明性地启用对这些附加元数据格式的支持。

下图显示了SpringIOC容器的流程。应用程序类与配置元数据组合,以便在ApplicationContext创建并初始化后将有一个完全配置和可执行的系统或应用程序。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hIu8lBG3-1627552286740)(C:\Users\Bear\Desktop\导图\spring-ioc.png)]

ClassPathXmlApplicationContext

ClassPathXmlApplicationContext是ApplicationContext的子类,它可以加在类路径下的bean实例配置文件,用于根据配置文件获取对象实例,要求配置文件必须在类路径下,也只能读取在类路径下的配置文件。(比较常用的方式)

  1. //创建容器获取对象的配置实例
  2. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  3. //也可以采用父类new子类的方式。
  4. ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

FileSystemXmlApplicationContext

FileSystemXmlApplicationContext类也继承了ApplicationContext接口,它可以加载磁盘任意路径下的配置文件(必须有访问限权),但必须以绝对路径的方式。

例如:

  1. ApplicationContext context = //指定系统绝对路径声明配置文件
  2. new FileSystemXmlApplicationContext("A:\\markdown-node\\spring\\conf\\bean.xml");

AnnotationConfigApplicationContext

AnnotationConfigApplicationContext类是用于读取注解进行容器元数据的配置,使用此类可以实现基于Java的配置类加载Spring的应用上下文。避免使用繁琐的配置文件进行配置。相比XML配置更加方便快捷。

  1. ApplicationContext context = new //采用读取java配置类来初始化对象实例,替代了XML文件。
  2. AnnotationConfigApplicationContext(AnnotationConfiguration.class);

基于xml的Spring配置

bean标签

从上面入门案例可以看出,bean标签用于管理对象,并创建对象实例设置对象的属性值,该标签的作用就是把对象放到容器中管理。

  1. <!-- 格式↓↓↓ -->
  2. <bean id="对象id" class="全限定类名" >
  3. <property name="成员变量" value="属性值"/>
  4. ...
  5. </bean>

在 spring 配置文件中使用 bean 标签,标签里面添加对应属性就可以实现对象创建,在 bean 标签有很多属性,下面是常用的bean属性。

id属性

bean标签中的id属性为对象的唯一标识,用于在创建容器对象的时候指定此id。

例如:

  1. //创建容器对象,指定配置文件名称
  2. ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
  3. //指定配置文件中的id唯一标识,并获取User类的字节码对象
  4. User user = context.getBean("user",User.class);

name属性

name属性和id属性类似,都是指定对象的唯一标识作为标记,如果两个都指定了唯一标识则都能生效,不同的标识id都可作为对象的实例化标志,例如

  1. <!-- id和name属性相同的标识都可识别,如果不同则可以分别取值使用 -->
  2. <bean name="user" id="user" class="com.spring.practice.User" ></bean>

class属性

提供被管理对象的全限定类名,供配置文件读取。

  1. <!-- 在com.spring.practice包下的User类 -->
  2. <bean id="user" class="com.spring.practice.User" ></bean>

scope属性

scope属性表示对象在spring容器(IOC容器)中的生命周期,也可以理解为对象在spring容器中的创建方式。

scope属性一共默认有以下两种取值。

属性 作用
singleton 单例模式:容器共享一个对象实例
prototype 多例模式:每次获取都是新的实例

容器默认的模式就是singleton单例模式,下面调用两个对象实例做比较。

  1. ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
  2. User user1 = context.getBean(User.class);
  3. User user2 = context.getBean(User.class);
  4. //因为spring容器默认是singleton模式,所以共享一个对象实例此时会打印为true
  5. System.out.println(user1 == user2);

修改XML配置文件为prototype属性,再创建两个对象实例做==比较。

  1. <bean id="user" class="com.spring.practice.User" scope="prototype"></bean>

如下图所示,创建了两个对象实例做比较显示为false,说明prototype属性起了作用,spring容器共享了多个实例。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N2sKG8ir-1627552286743)(C:\Users\Bear\Desktop\导图\spring2.png)]

bean的作用域和生命周期

Spring中,容器中一共提供了6种bean 的作用域,其中有4种作用域只有在为web应用程序中才有效,并且Spring还支持自定义作用域。上面介绍了两种默认的作用域属性。

以下为spring的另外四种作用域属性

属性 作用
request 将bean定义作用于单个HTTP请求的生命周期,且该实例仅在这个HTTP
请求的生命周期里有效。每个HTTP请求都有自己的bean实例,该作用域仅适用于WebApplicationContext
环境。
session 将单个bean定义作用于HTTP的生命周期Session
,每个HTTP Session
都有一个实例,且该实例仅在这个HTTP Session
的生命周期里有效。该作用域仅作用于WebApplicationContext
环境。
application 将单个bean定义的作用范围扩展到ServletContext
该作用域仅作用于WebApplicationContext
环境。
websocket 将单个bean定义的作用范围扩展到WebSocket
。该作用域仅作用于WebApplicationContext
环境。

下图声明了bean的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7CcPkmxW-1627552286745)(C:\Users\Bear\Desktop\导图\bean的声明周期.png)]

实例化bean三种方式

无参构造

通过无参构造方法创建bean,此时只是一个普通的对象类型,并没有初始值,但可通过set方法设置属性值。

  1. <!-- 无参构造创建,指定id唯一标识,和目标对象全限定类名 -->
  2. <bean id="user" class="com.spring.practice.User" ></bean>

调用getBean方法获取对象的实例

  1. @Test
  2. public void testUser() {
  3. ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
  4. User user = context.getBean(User.class);
  5. System.out.println(user);
  6. }

使用无参方式实例化bean并没有设置对象的属性值,所以打印均为空。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3zmzu3pE-1627552286746)(C:\Users\Bear\Desktop\导图\constructor.png)]

工厂静态方法

使用静态工厂方法实例化bean时需要在工厂类中指定一个static方法,并在bean属性中定义factory-method属性。

首先创建一个工厂类,提供一个静态方法,并添加对应构造方法参数返回一个对象类型。

  1. public class UserFactory {
  2. public static User userFactory(String name, Integer age,String sex) {
  3. return new User(name,age,sex);
  4. }
  5. }

配置bean实例,指定factory-method 的实例并通过构造方法设置属性值。

  1. <!-- 指定静态工厂类路径,和静态方法的名称 -->
  2. <bean id="user" class="com.spring.practice.UserFactory" factory-method="userFactory" >
  3. <!-- 通过构造器设置属性值,index表示索引值,按照成员变量顺序排列。 -->
  4. <constructor-arg index="0" value="trump"/>
  5. <constructor-arg index="1" value="3"/>
  6. <constructor-arg index="2" value="woman"/>
  7. </bean>

由于使用的是静态工厂实例化,所以class属性必须指定工厂类的路径,不能指定原对象的路径,factory-method 属性则是指定静态工厂里的静态方法名称,也就是返回对象实例的方法名称。

测试

  1. @Test
  2. public void testUser() {
  3. //创建一个Spring容器对象
  4. ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
  5. User user = context.getBean(User.class);
  6. System.out.println(user);
  7. }

打印结果为配置文件里构造器属性设置的值。

工厂实例方法

类似于上面的静态方法实例,使用实例工厂方法实例化,从容器中调用其他的bean的非静态方法来创建新的实例。而如果要使用此机制则需要保留class属性为空,在factory-bean属性指定工厂实例bean的名称,因为该容器包含要调用以创建对象的实例方法。而factory-method属性则是指定工厂类的非静态方法名称。

首先创建一个工厂实例类,提供非静态方法。

  1. public class UserFactoryInstance {
  2. public User userFactoryInstance(String name, Integer age,String sex) {
  3. return new User(name,age,sex);
  4. }
  5. }

配置XML文件注入bean和属性

首先创建工厂类的bean,为对象类的bean容器提供对象实例

  1. <bean id="userFactoryInstance" class="com.spring.practice.UserFactoryInstance"></bean>

id表示唯一标识,可任意取名,class是指定工厂类的类名。

接着指定User对象的bean容器。

  1. <bean id="userInstance" factory-bean="userFactoryInstance" factory-method="userFactoryInstance">
  2. <constructor-arg index="0" value="Bear"/>
  3. <constructor-arg index="1" value="18"/>
  4. <constructor-arg index="2" value="man"/>
  5. </bean>

上面的factory-bean需要对应工厂bean的id,factory-method则需要对应对象实例的非静态方法名称。

接下来调用容器对象测试

  1. @Test
  2. public void testUser() {
  3. //创建一个Spring容器对象
  4. ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
  5. //指定User对象bean的唯一标识id。
  6. User user = context.getBean("userInstance",User.class);
  7. System.out.println(user);
  8. }

打印结果均为bean中配置的构造属性。

总结:工厂静态方法和工厂实例方法大致相同,唯一区别的是工厂静态方法只需要提供一个静态的对象方法,供User对象的bean指定其factory-method即可,而工厂实例方法则不需要提供静态的对象方法,并且需要多创建一个bean容器为User对象bean实例化内容。

依赖注入(DI)

依赖注入概述

依赖注入(DI)是一个过程,对象仅通过set、构造方法、工厂方法的参数或在从工厂方法构造或返回对象实例后在对象实例上设置的属性来定义它们的依赖关系。然后容器在创建bean时注入属性依赖项。

依赖注入方式

set方法注入

通过类中提供的set方法注入属性值,上面spring快速入门中已做实例,此处不再编写。

构造方法注入

通过有参数的构造方法进行属性的注入。

提供User类中的全参构造,通过lombok注解

  1. @Data
  2. @AllArgsConstructor //生成全参构造
  3. public class User {
  4. public User(){}
  5. private String name;
  6. private Integer age;
  7. private String sex;
  8. }

配置XML文件

  1. <bean id="user" class="com.spring.practice.User">
  2. <constructor-arg index="0" value="Ted"/>
  3. <constructor-arg name="age" value="20"/>
  4. <constructor-arg type="java.lang.String" value="男"/>
  5. </bean>

通过构造器注入时有三个指定类型,分别为index,name,type属性,三个都可表示对象的成员变量,不同的是index代表索引编号,也就是成员变量所在的位置顺序,name则需要指定成员变量的名称,type指定成员变量的数据类型。

创建容器对象测试

  1. @Test
  2. public void testUser() {
  3. //创建一个Spring容器对象
  4. ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
  5. User user = context.getBean("user",User.class);
  6. System.out.println(user);
  7. }

p命名空间注入

P命名空间可以使用bean元素的属性而不是嵌套的<property/>标签元素来注入属性值。

使用p标签则需要导入以下内容到xml头文件当中

  1. xmlns:p="http://www.springframework.org/schema/p"

注入bean的属性

  1. <!-- p命名空间注入,可以直接注入属性 p大约等于Property属性-->
  2. <bean id="user" class="com.spring.practice.User" p:name="fuckTrump" p:age="3" p:sex="女"></bean>

上面省去了繁琐的<property>标签,通过p标签就可以根据set注入属性值

c命名注入

c标签类似于p标签,但它是对构造方法进行属性注入的。在Spring3.1中引入的c-命名空间允许内联属性来配置构造函数参数,而不是嵌套的constructor-arg属性元素。

使用c标签也需要导入下面标签到xml头文件当中

  1. xmlns:c="http://www.springframework.org/schema/c"

配置bean属性

  1. <!-- 使用c命名空间构造方法注入,c标签类似于Constructor构造器缩写 -->
  2. <bean id="user2" class="com.spring.practice.User" c:_0="Bear" c:age="19" c:sex="男"/>

依赖注入数据类型

spring配置文件可以很方便的为对象创建实例并注入属性,避免过多的使用new关键字从而降低了耦合度,通过配置可以注入以下三种不同数据类型的属性,各自有对应不同的属性标签。

普通属性

首先创建一个有多个数据类型的User类对象

  1. @Data //提供get&set方法
  2. @AllArgsConstructor //全参构造
  3. public class User {
  4. private String name;//字符串类型
  5. private Integer age; //Integer类型
  6. private Address address; //组合注入对象类型
  7. private String[] books; //数组类型
  8. private List<String> hobby; //集合类型
  9. private Map<String,String> card; //map集合
  10. private Set<String> games; //set集合
  11. private Properties info; //properties类型
  12. }

测试注入普通类型属性,配置xml的bean属性

  1. <bean id="user" class="com.spring.practice.User">
  2. <property name="name" value="ted"/>
  3. <property name="age" value="19"/>
  4. </bean>

创建容器对象测试

  1. ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
  2. User user = context.getBean("user",User.class);
  3. System.out.println(user);

对象属性

由于对象类型比较特殊,所以需要额外装配一个bean先注入属性值,再让所在类对象去引用,如下。

  1. <!-- 根据p标签设置Address的成员变量属性值 -->
  2. <bean id="address" class="com.spring.practice.Address" p:addressName="beijing"/>
  3. <!-- 再利用ref标签去引用上面配置的bean -->
  4. <bean id="user" class="com.spring.practice.User">
  5. <property name="address" ref="address"/>
  6. </bean>

集合属性

不同的集合有着不同的标签,但注入的方式也相对比较简单。

  1. <bean id="user" class="com.spring.practice.User">
  2. <!-- Array数组类型注入-->
  3. <property name="books">
  4. <!-- 需要使用array标签 -->
  5. <array>
  6. <!-- value标签设置数组的值 -->
  7. <value>TED</value>
  8. <value>Bear</value>
  9. <value>Trump</value>
  10. </array>
  11. </property>
  12. <!-- list集合注入 -->
  13. <property name="hobby">
  14. <!-- 需要用list标签 -->
  15. <list>
  16. <value>List1</value>
  17. <value>List2</value>
  18. <value>List3</value>
  19. </list>
  20. </property>
  21. <!-- map集合注入 -->
  22. <property name="card">
  23. <map>
  24. <!-- key是唯一标识,不可重复 -->
  25. <entry key="1" value="Bear"/> <!-- 设置键和值 -->
  26. <entry key="2" value="Ted"/>
  27. </map>
  28. </property>
  29. <!-- set集合注入 -->
  30. <property name="games">
  31. <set>
  32. <value>Dog</value>
  33. <value>Cat</value>
  34. <value>Bear</value>
  35. </set>
  36. </property>
  37. <!-- null值注入 -->
  38. <property name="name">
  39. <!-- 指定null标签即可 -->
  40. <null/>
  41. </property>
  42. <!-- Properties注入 -->
  43. <property name="info">
  44. <props>
  45. <!-- Properties类型的值必须设置在尖括号中间 -->
  46. <prop key="1">prop1</prop>
  47. <prop key="2">prop2</prop>
  48. <prop key="3">prop3</prop>
  49. </props>
  50. </property>
  51. </bean>

测试类

  1. ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
  2. User user = context.getBean("user",User.class);
  3. System.out.println(user);

基于注解的Spring配置

概述

spring还提供了一种使用Java配置类和注解的方式来对对象进行操作,此方式省去了繁琐的XML文件配置,仅需几个注解即可完成对象的注入功能。

注解是代码中的特殊标记,用于标记某项属性的功能。

格式为:@注解名称(属性名称=属性值, 属性名称=属性值…)

spring常用注解:

注解 作用
@Component、@Controller、@Service、@Repository、@Configuration、@ComponentScan
这六个注解功能一致,都是把对象交给spring容器管理让容器创建对象的实例,不同的名称代表不同的含义,@Component表示任意组件,@Controller代表控制层、@Service代表业务层、@Repository代表数据层、@Configuration和标签功能一致,不同的是@ComponentScan可以扫描包下多个对象并加入到容器当中。
@Autowired、@Resource、@Value
@Autowired和@Resource都表示自动装配属性,@Autowired是按照类型注入,@Resource是按照名称注入。
@Qualifier
指定某个bean的名称被注入
@Import
导入其它的配置类
@Scope
表示一个bean的行为和作用域的范围
@PropertySource
加载指定的properties属性文件
@Bean
为对象创建一个bean,类似于配置文件的标签

快速开始

下面来演示基于注解操作的spring配置。

  1. 创建一个新项目,导入如下依赖
  1. <dependencies>
  2. <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
  3. <dependency>
  4. <groupId>org.springframework</groupId>
  5. <artifactId>spring-context</artifactId>
  6. <version>5.2.9.RELEASE</version>
  7. </dependency>
  8. <dependency>
  9. <groupId>org.projectlombok</groupId>
  10. <artifactId>lombok</artifactId>
  11. <version>1.18.10</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>junit</groupId>
  15. <artifactId>junit</artifactId>
  16. <version>4.12</version>
  17. <scope>test</scope>
  18. </dependency>
  19. </dependencies>
  1. 先创建一个Person类,作为被spring容器管理的对象
  1. @Data //提供get&set方法
  2. @AllArgsConstructor //全参构造
  3. @NoArgsConstructor //无参构造
  4. public class Person {
  5. private String name;
  6. private Integer age;
  7. private String sex;
  8. private Integer height;
  9. private String hobby;
  10. }
  1. 根据Java配置类进行对象实例的创建和依赖注入
  1. @Configuration //表示这是一个Java配置类,类似于XML中的<beans/> 大的标签
  2. public class AnnotationConfiguration {
  3. @Bean //标识为一个bean,类似于XML中的<bean/> 小标签
  4. public Person person() {
  5. //new一个Person对象,根据全参构造提供属性值
  6. return new Person("bear",2,"man",180,"eat");
  7. }
  8. }
  1. 编写测试类,需要用到AnnotationConfigApplicationContext注解容器对象。
  1. @Test
  2. public void AnnotationTest() {
  3. ApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfiguration.class);
  4. //person为Java配置类中返回的对象方法名
  5. Person person = context.getBean("person", Person.class);
  6. System.out.println(person);
  7. }

测试结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cF4p8HJZ-1627552286747)(C:\Users\Bear\Desktop\导图\anno.png)]

以上可以看到只需要一个Java配置类和两个注解即可对Person类进行管理和属性注入,不再需要如<beans> <bean> propety等标签来进行过多的操作,极大地减少了操作的步骤。

容器管理注解

将对象反转交给spring容器管理的注解有很多种。

分别为:@Component、@Controller、@Service、@Repository、@Configuration、@ComponentScan

而如下配置类则都可以使用这六种注解进行管理对象,不同的是@ComponentScan需要指定包路径

  1. @ComponentScan("com.spring") //扫描包下的所有对象,交给spring容器管理
  2. public class AnnotationConfiguration {
  3. @Bean //标识为一个bean
  4. public Person person() {
  5. return new Person("bear",2,"man",180,"eat");
  6. }
  7. }

@Value

此注解是用于注入对象成员的属性值,可以放置到字段或者set方法上。

修改Person类对象,在成员变量和set方法上演示Value注解

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class Person {
  5. private String name;
  6. @Value("3")
  7. private Integer age;
  8. @Value("女")
  9. private String sex;
  10. @Value("150")
  11. private Integer height;
  12. @Value("高尔夫")
  13. private String hobby;
  14. @Value("Trump")
  15. public void setName(String name) {
  16. this.name = name;
  17. }
  18. }

替换Java配置类中返回对象中的构造参数。。。

  1. @ComponentScan("com.spring")
  2. public class AnnotationConfiguration {
  3. @Bean
  4. public Person person() {
  5. return new Person();
  6. }
  7. }

测试类

  1. @Test
  2. public void AnnotationTest() {
  3. ApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfiguration.class);
  4. Person person = context.getBean("person", Person.class);
  5. System.out.println(person);
  6. }

测试结果如下:可以看到显示的值为@Value注入的属性值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3VPyzCkR-1627552286748)(C:\Users\Bear\Desktop\导图\value.jpg)]

@Scope

这个注解可以指定bean容器中对象的作用域,默认为singleton单例模式,即多个对象共享一个实例,另外根据非web环境还可以设置为prototype 原型多例模式,即每个对象都有一个不同的实例。

示例:

在配置类对象方法里添加@Scope注解,设置为prototype 多例模式

  1. @ComponentScan("com.spring")
  2. @Import(User.class) //导入User对象的配置类
  3. public class AnnotationConfiguration {
  4. @Bean
  5. @Scope("prototype") // 修改为prototype类型
  6. public Person person() {
  7. return new Person();
  8. }
  9. }

在测试类里获取两个bean实例对象作比较。

  1. @Test
  2. public void AnnotationTest() {
  3. ApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfiguration.class);
  4. Person person1 = context.getBean(Person.class);
  5. Person person2 = context.getBean(Person.class);
  6. //比较两个对象是否还在共享一个实例
  7. System.out.println(person1 == person2);
  8. }

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-25tKGYQD-1627552286749)(C:\Users\Bear\Desktop\导图\scope.jpg)]

可以看到打印结果为false,这就是@Scope注解的作用,改变bean实例对象的作用域。

@Import

当想在一个Java配置类中导入其它对象的配置时就可以用到这个注解。

测试案例:新建一个User类和一个User的Java配置类。

  1. /*
  2. User对象
  3. */
  4. @Data
  5. public class User {
  6. @Value("Teddy_Bear")
  7. private String name;
  8. @Value("21")
  9. private Integer age;
  10. }
  11. /*
  12. User的配置类
  13. */
  14. @Configuration
  15. public class UserConfig {
  16. //返回一个User对象,注入bean
  17. @Bean
  18. public User getUser() {
  19. return new User();
  20. }
  21. }

如果想要让以上User对象实例融合其它的bean实例则需要在目标类中加入@Import注解,参数为被引入对象的字节码形式。

  1. @ComponentScan("com.spring")
  2. @Import(User.class) //导入User对象的配置类
  3. public class AnnotationConfiguration {
  4. @Bean
  5. @Scope("prototype")
  6. public Person person() {
  7. return new Person();
  8. }
  9. }

测试类

  1. @Test
  2. public void AnnotationTest() {
  3. ApplicationContext context = new
  4. AnnotationConfigApplicationContext(AnnotationConfiguration.class);
  5. Person person= context.getBean("getPerson",Person.class);
  6. //获取User对象的实例,参数为配置类中的方法名和字节码对象。
  7. User user = context.getBean("getUser",User.class);
  8. System.out.println(person);
  9. System.out.println(user);
  10. }

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mDG1A2d5-1627552286750)(C:\Users\Bear\Desktop\导图\import.jpg)]

可以看到引入的User对象实例也打印出了@Value指定的属性。

@Resource

@Resource注解是用来装配对象的bean实例的,默认是按照对象实例名称进行自动装配,可以通过name属性进行目标指定,如果没有指定名称则按照注入的默认字段名称进行查找,如果注解写在set方法上默认则取属性名进行装配。

注解演示:模拟测试插入一条用户数据

  1. 新建UserDao和UserDaoImpl类
  1. /*
  2. 模拟数据层
  3. */
  4. public interface UserDao {
  5. //模拟插入用户
  6. void saveUser();
  7. }
  8. /*
  9. UserDao实现类
  10. */
  11. @Repository("userDao") //指定bean实例的名称
  12. public class UserDaoImpl implements UserDao {
  13. @Override
  14. public void saveUser() {
  15. String user = "Bear";
  16. System.out.println("插入了一条" + user + "用户");
  17. }
  18. }
  1. UserService业务层
  1. public interface UserService {
  2. //模拟插入用户
  3. void saveUser();
  4. }
  5. /*
  6. 实现类
  7. */
  8. @Service
  9. public class UserServiceImpl implements UserService{
  10. @Resource(name = "userDao") //指定UserDaoImpl对象被容器管理的名称
  11. private UserDao userDao;
  12. @Override
  13. public void saveUser() {
  14. //使用dao层调用插入用户方法
  15. userDao.saveUser();
  16. }
  17. }

@Resource也可以根据set方法进行bean的装配

例如:

  1. private UserDao userDao;
  2. @Resource(name = "userDao") //注入在set方法上
  3. public void setUserDao(UserDao userDao) {
  4. this.userDao = userDao;
  5. }
  1. 配置类

这里把UserService曾配置到spring容器中去管理,通过创建此对象实例去调用dao层方法

  1. @ComponentScan("com.spring")
  2. public class UserServiceConfiguration {
  3. @Bean
  4. public UserService userService (){
  5. return new UserServiceImpl();
  6. }
  7. }
  1. 测试类
  1. @Test
  2. public void test() {
  3. ApplicationContext context = new
  4. AnnotationConfigApplicationContext(UserServiceConfiguration.class);
  5. UserService service = context.getBean("userService",UserService.class);
  6. //调用方法打印信息
  7. service.saveUser();
  8. }
  1. 测试类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PAEe8AwT-1627552286751)(C:\Users\Bear\Desktop\导图\resource.jpg)]

最终调用UserService的bean对象实例成功调用dao层方法打印了一条信息,@Resouce

@Autowired

@Autowired注解和@Resource功能一致,不同的是@Autowired是按照类型来自动装配的。

例如上面中的UserService实例:

  1. @Autowired
  2. private UserDao userDao;

@Autowired注会在spring容器中自动匹配类型为UserDao的类去进行属性注入。

@Qualifier

此注解是为了解决多个相同的bean的时候无法指定其中某一个,这种情况就会导致报错现象,@Qualifier 则可以指定某个单独的bean的名称注入。类似于@Resource中根据name进行寻找bean的实例。

下面创建另一个User2Dao的实现类,内容和另一个实现类一致

  1. @Repository("user2")
  2. public class User2DaoImpl implements UserDao{
  3. @Override
  4. public void saveUser() {
  5. System.out.println("插入了一条新用户:Trump");
  6. }
  7. }

如果此时直接运行程序则会报错,因为在UserService层使用了@Autowired注解,此注解是按照类型查找,而创建了两个实现类,spring容器不知道要调用哪一个实例,此时就要用到@Qualifier注解来区分,service层如下。

  1. @Service
  2. public class UserServiceImpl implements UserService{
  3. @Autowired
  4. @Qualifier("user2") //指定User2DaoImpl中的bean实例名称,spring容器就会调用这个实现类的方法
  5. private UserDao userDao;
  6. @Override
  7. public void saveUser() {
  8. userDao.saveUser();
  9. }
  10. }

测试结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lOMffrFg-1627552286752)(C:\Users\Bear\Desktop\导图\qualifier.jpg)]

Bean的自动装配

自动装配是spring注入bean依赖的简化方式

spring会在上下文中自动寻找,并自动给bean装配属性

byName自动装配

示例:

  1. <bean id="people" class="com.pojo.People" autowire="byName">
  2. <property name="name" value="shit"/>
  3. <!-- use autowire 自动装配对象 -->
  4. <!-- <property name="dog" ref="dog"/>--> <!-- 使用了byName属性就不用ref引入对象id,spring会自动寻找对象 -->
  5. <!-- <property name="cat" ref="cat"/>-->
  6. </bean>

byType属性

  1. <bean <!--id="cat"--> class="com.pojo.Cat"/>
  2. <bean <!--id="dog"--> class="com.pojo.Dog"/> <!-- 使用byType属性可以省略对象的id -->
  3. <bean id="people" class="com.pojo.People" autowire="byType">
  4. <property name="name" value="Bear"/><!-- use autowire 自动装配对象,并省去了id的配置 -->
  5. </bean>

注解实现自动装配

1.导入约束头文件。

2.配置注解的支持

示例:

导入头文件

  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. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
  8. http://www.springframework.org/schema/context //注解的依赖
  9. https://www.springframework.org/schema/context/spring-context.xsd
  10. http://www.springframework.org/schema/aop //aop的依赖
  11. https://www.springframework.org/schema/aop/spring-aop.xsd">
  12. <!-- 开启注解支持 -->
  13. <context:annotation-config/>
  14. </beans>

使用@Autowired注解注入

直接在成员变量或者set方法上标记

示例:

  1. @Autowired(required = false)
  2. private Cat cat;
  3. @Autowired
  4. private Dog dog;
  5. @Autowired
  6. public void setCat(Cat cat) {
  7. this.cat = cat;
  8. }
  9. @Autowired
  10. public void setDog(Dog dog) {
  11. this.dog = dog;
  12. }

xml配置

  1. <bean id="cat" class="com.pojo.Cat"/>
  2. <bean id="dog" class="com.pojo.Dog"/>
  3. <!-- 不用指定property属性设置值 -->
  4. <bean id="people" class="com.pojo.People"/>

使用了@Autowired注解后可以不用设置set方法

@Resource和@Autowired的区别

这两个都是用来实现自动装配的,都可以放在属性字段上面

@Autowired通过byName的方式实现,而且必须要求这个对象存在

@Resource默认通过byName方式实现,如果找不到名字则通过byType方式实现注入

Spring面向切面编程(AOP)

AOP概述

AOP全称为Aspect Oriented Programming的缩写,译为:面向切面编程是通过预编译方式和运行期间基于动态代理实现程序功能的一种技术。AOP是OOP的延续,是软件开发中的一个 热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑 的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时可提高开发的效率。

AOP相关术语

下面是spring官网原文给出的aop概念。

  • Aspect:表示切面,是切入点和通知的声明。
  • JoinPoint:程序执行过程中方法的连接点,如方法的执行或异常的处理。在AOP中连接点只表示方法类型
  • Advice:在特定通知连接点声明,通知包括前置、后置、环绕
  • Pointcut:切入点,通过表达式指定方法的切入点位置
  • Introduction:在不修改代码的情况下可以动态的添加方法或字段。
  • Target object:目标对象,也表示为一个代理对象
  • AOP proxy:表示一个Aop代理类对象
  • Weaving: 将切面与其他应用程序类型或对象进行连接,以创建代理对象

通知类型

spring Aop通知类型(advice)有以下五种:

  1. Before advice:前置通知,在方法执行前被调用,前置通知不会影响连接点的执行,除非抛出异常。
  2. After returning advice:返回通知后,在连接点正常完成后运行执行的通知,引发异常则不执行
  3. After throwing advice:异常通知后,如果方法通过抛出异常退出,将执行通知。
  4. After (finally) advice:最后通知,不管方法是否执行成功或有没有异常都会执行。
  5. Around advice:环绕通知,在方法执行前后都会执行。

基于XML的AOP配置

方式一:通过spring接口实现

使用aop功能需要导入的头文件。

  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:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. https://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/aop
  8. https://www.springframework.org/schema/aop/spring-aop.xsd">
  9. </beans>

下面使用增加日志的方式来演示Aop的功能。

  1. 创建Maven项目并导入如下依赖。
  1. <dependencies>
  2. <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
  3. <dependency>
  4. <groupId>org.springframework</groupId>
  5. <artifactId>spring-context</artifactId>
  6. <version>5.2.9.RELEASE</version>
  7. </dependency>
  8. <dependency>
  9. <groupId>org.aspectj</groupId>
  10. <artifactId>aspectjweaver</artifactId>
  11. <version>1.9.5</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>junit</groupId>
  15. <artifactId>junit</artifactId>
  16. <version>4.12</version>
  17. <scope>test</scope>
  18. </dependency>
  1. 编写UserService类,提供模拟的增删改查方法以作演示。
  1. public interface UserService {
  2. /*
  3. * 模拟增删改查方法
  4. */
  5. String insert(String name);
  6. int delete(int id);
  7. String update(String name);
  8. int query(int id);
  9. }
  1. 编写实现类,打印具体的功能,并最后利用aop技术添加额外的日志信息
  1. public class UserServiceImpl implements UserService{
  2. @Override
  3. public String insert(String name) {
  4. System.out.println("添加了一个"+ name + "数据");
  5. return name;
  6. }
  7. @Override
  8. public int delete(int id) {
  9. System.out.println("删除了" + id + "号数据");
  10. return id;
  11. }
  12. @Override
  13. public String update(String name) {
  14. System.out.println("更新了" + name + "数据");
  15. return name;
  16. }
  17. @Override
  18. public int query(int id) {
  19. System.out.println("查询" + id + "号数据");
  20. return id;
  21. }
  22. }
  1. 编写两个类,一个实现MethodBeforeAdvice接口,一个实现 AfterReturningAdvice接口,分别实现前置通知和后置返回通知的效果。
  1. /*
  2. 前置通知类
  3. */
  4. public class BeforeLog implements MethodBeforeAdvice {
  5. /**
  6. * @param method:执行的目标对象方法
  7. * @param args:目标对象参数
  8. * @param target :目标对象
  9. * @throws Throwable :异常
  10. */
  11. @Override
  12. public void before(Method method, Object[] args, Object target) throws Throwable {
  13. System.out.println(target.getClass().getName()+"的" + method.getName() + "执行了");
  14. }
  15. }
  16. /*
  17. 后置通知类
  18. */
  19. public class AfterLog implements AfterReturningAdvice {
  20. /**
  21. * @param returnValue:执行后的返回值
  22. * @param method:目标对象方法
  23. * @param args:目标对象参数
  24. * @param target:目标对象
  25. * @throws Throwable:异常
  26. */
  27. @Override
  28. public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
  29. System.out.println("执行了" + method.getName() + "方法,返回结果:" + returnValue);
  30. }
  31. }
  1. 将编写的类注入到spring bean容器中,然后配置aop相关内容。

首先注册三个类到bean当中。

  1. <!-- 注册三个bean,id是唯一标识 -->
  2. <bean id="userService" class="com.spring.aop.service.UserServiceImpl"/>
  3. <bean id="beforeLog" class="com.spring.aop.log.BeforeLog"/>
  4. <bean id="afterLog" class="com.spring.aop.log.AfterLog"/>

第二步配置切入点和通知类,通知类为对目标类输出的信息,配置的方式有如下两种↓

方式一:直接在<aop:advisor/> 标签中指定通知类和切入点。

  1. <aop:config>
  2. <aop:advisor advice-ref="beforeLog" pointcut="execution(* com.spring.aop.service.UserServiceImpl.*(..))"/>
  3. <aop:advisor advice-ref="afterLog" pointcut="execution(* com.spring.aop.service.UserServiceImpl.*(..))"/>
  4. </aop:config>

方式二:根据<aop:pointcut/> 标签首先配置切入点,然后让配置通知再引用切入点

  1. <aop:config>
  2. <aop:pointcut id="pointcut" expression="execution(* com.spring.aop.service.UserServiceImpl.*(..))"/>
  3. <!-- 配置切入的日志类,引用上面的切面 -->
  4. <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
  5. <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
  6. </aop:config>
  7. <!--
  8. 参数解释:
  9. id:表示唯一标识
  10. expression表达式:execution表示要执行的类路径位置,*表示匹配所有的类型 .*表示执行类里面的所有方法,(..)表示匹配任意参数
  11. advice-ref:引用注入的bean唯一标识id
  12. pointcut-ref:引用配置好的切面,内容为配置切面的id
  13. expression="execution(* *..*.*(..))":expression表达式还可以这么用,表示匹配所有包下的类。
  14. -->

由上可见配置切面和通知的方法有两种,第一种少了一行配置,但每次都要写表达式指定切入点,而第二种方式则只需配置一次切入点即可让多个通知去引用这个切入点。

  1. 编写测试类

由于是根据spring接口实现的,所以获取bean的类型也必须为实现的UserService接口类型,不能是实现类,否则报错

  1. @Test
  2. public void test() {
  3. ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
  4. UserService service = context.getBean("userService",UserService.class);
  5. service.insert("Bear");
  6. service.delete(2);
  7. service.update("Bear");
  8. service.query(1);
  9. }

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HRS8RI5j-1627552286753)(C:\Users\Bear\Desktop\导图\2.png)]

由上可见四个方法分别都执行了前置通知和后置通知的打印日志信息,并且后置通知还执行了返回结果。

方式二:自定义类实现

第一种方式是通过类实现spring提供的接口来实现Aop的功能,优点是通过接口提供的反射机制可以使用更多的功能,缺点就是需要创建许多类去一一实现,由此还可以使用自定义类和通知方法的方式实现功能,把主要功能交给XML中配置的标签去实现,而自定义类中只需要编写要添加的日志方法或信息即可。

  1. 首先新建一个自定义的方法类。
  1. public class General {
  2. public void before() {
  3. System.out.println("--------方法执行前的通知--------");
  4. }
  5. public void after() {
  6. System.out.println("--------方法执行后的通知--------");
  7. }
  8. public Object around(ProceedingJoinPoint point) throws Throwable {
  9. Object Value;
  10. Object[] args = point.getArgs();//得到方法执行所需的参数
  11. System.out.println("around前置执行了");
  12. Value = point.proceed(args); //明确调用业务层切入点方法
  13. System.out.println("around后置执行了");
  14. return Value;
  15. }
  16. }

配置环绕通知需要使用ProceedingJoinPoint接口,该接口有一个方法proceed(),此方法相当于明确调用切入点的方法,该接口可作为怀绕通知的方法参数,在程序执行时会调用该接口的实现类使用。

  1. 配置xml文件
  1. <!-- 方法2:自定义类实现aop-->
  2. <!-- 注入自定义类的bean实例 -->
  3. <bean id="definition" class="com.spring.aop.log.General"/>
  4. <aop:config>
  5. <!-- 配置切面,把自定义的方法横切进目标类当中-->
  6. <aop:aspect ref="definition">
  7. <!-- 配置切入点,也就是要增加功能的目标类 -->
  8. <aop:pointcut id="point" expression="execution(* com.spring.aop.service.UserServiceImpl.*(..))"/>
  9. <!-- 配置通知方法,指定自定义类中的三个方法 -->
  10. <aop:before method="before" pointcut-ref="point"/>
  11. <aop:after method="after" pointcut-ref="point"/>
  12. <aop:around method="around" pointcut-ref="point"/>
  13. </aop:aspect>
  14. </aop:config>
  1. 测试类
  1. @Test
  2. public void test() {
  3. ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
  4. UserService service = context.getBean("userService", UserService.class);
  5. service.insert("Bear");
  6. service.delete(2);
  7. service.update("Bear");
  8. service.query(1);
  9. }

测试结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FWeOkgdv-1627552286753)(C:\Users\Bear\Desktop\导图\aop.png)]

上面测试结果显示了三种功能的通知都成功的执行在了输出的方法中,按照顺序,前置通知在最上面,后置通知在最下面,而环绕通知则被前置和后置通知包括在里面,真正的业务方法则在最里面,实现了不改动业务代码的前提下往代码中添加日志方法。

基于注解的AOP配置

通过注解实现可以实现自定义切面切入点和advice通知的配置,相比XML配置文件来说减少了更多繁杂的配置,只需少量的配置即可实现同样的功能。

下面基于xml配置的内容来演示。

首先自定义切面信息需要创建一个类,然后添加其注解和切入点还有通知方式。

  1. 业务类和其实现类。
  1. public interface UserService {
  2. /*
  3. * 模拟增删改查方法
  4. */
  5. String insert(String name);
  6. int delete(int id);
  7. String update(String name);
  8. int query(int id);
  9. }
  10. /*
  11. 实现类
  12. */
  13. public class UserServiceImpl implements UserService{
  14. @Override
  15. public String insert(String name) {
  16. System.out.println("添加了一个"+ name + "数据");
  17. return name;
  18. }
  19. @Override
  20. public int delete(int id) {
  21. System.out.println("删除了" + id + "号数据");
  22. return id;
  23. }
  24. @Override
  25. public String update(String name) {
  26. System.out.println("更新了" + name + "数据");
  27. return name;
  28. }
  29. @Override
  30. public int query(int id) {
  31. System.out.println("查询" + id + "号数据");
  32. return id;
  33. }
  34. }
  1. 新建自定义功能类
  1. @Aspect //表示切面的注解
  2. @Component //注入到spring容器当中
  3. public class AopAnnotation {
  4. //定义一个切入点方法,统一其他方法的定义
  5. @Pointcut("execution(* com.spring.aop.service.UserServiceImpl.*(..))")
  6. public void log(){}
  7. @Before("log()") //前置通知
  8. public void before() {
  9. System.out.println("--------方法执行前的通知--------");
  10. }
  11. @After("log()") //后置通知
  12. public void after() {
  13. System.out.println("--------方法执行后的通知--------");
  14. }
  15. @Around("log()") //环绕通知
  16. public Object around(ProceedingJoinPoint point) throws Throwable {
  17. Object Value;
  18. Object[] args = point.getArgs(); //得到方法执行所需的参数
  19. System.out.println("around前置执行了");
  20. Value = point.proceed(args); //明确调用业务层切入点方法
  21. System.out.println("around后置执行了");
  22. return Value;
  23. }
  24. }
  1. 注入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. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xmlns:context="http://www.springframework.org/schema/context"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. https://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/aop
  9. https://www.springframework.org/schema/aop/spring-aop.xsd
  10. http://www.springframework.org/schema/context
  11. https://www.springframework.org/schema/context/spring-context.xsd">
  12. <!-- 注入UserService的bean -->
  13. <bean id="userService" class="com.spring.aop.service.UserServiceImpl"/>
  14. <!-- 扫描自定义类注入的组件 -->
  15. <context:component-scan base-package="com.spring.aop.log"/>
  16. <!-- 开启对注解的支持 -->
  17. <aop:aspectj-autoproxy/>
  18. </beans>
  1. 测试类。
  1. @Test
  2. public void test() {
  3. ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
  4. UserService service = context.getBean("userService", UserService.class);
  5. service.insert("Bear"); //这里只测试一个插入方法。
  6. }

测试结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LU2q2xAA-1627552286754)(C:\Users\Bear\Desktop\导图\aop3.png)]

可以看到在执行插入方法之前和之后分别都执行了Around环绕通知和前置后置通知。