本笔记为本人在 B 站学习时整理而成,为了更好的学习,将其整理成笔记,以防忘记相关知识点。
Spring 概述
概述
Spring:出现在 2002 年左右,降低企业级开发难度。帮助进行模块之间、类与类之间的管理,帮助开发人员创建对象,管理对象之间的关系。
2003 年传入国内,被大量使用。
2017 出现新的流行框架 SpringBoot,核心思想与 Spring 相同。
核心技术:IoC、AOP,能使模块之间、类之间解耦合。
依赖:class A 使用 class B 的属性或方法,称之为 class A 依赖 class B。
优点
(1)轻量:Spring 的所需要的 jar 包都非常小,一般 1M 以下,几百 kb。核心功能所需要的 jar 包总共 3M 左右。Spring 框架运行占有资源少,运行效率高,不依赖其他 jar。
(2) 针对接口编程,解耦合
(3) AOP 编程的支持
(4) 方便集成各种优秀框架
(2)(3)(4)的优点将会在接下来学习中体会。
Spring 体系结构
Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、 Web、面向切面编程(AOP, Aspects)、提供 JVM 的代理 (Instrumentation)、消息发送(Messaging)、 核心容器(Core Container)和测试(Test)。
spring 全家桶:spring , springmvc ,spring boot , spring cloud 等。
框架怎么学: 框架是一个软件,其它人写好的软件。
1)知道框架能做什么, mybatis 能访问数据库, 对表中的数据执行增删改查。
2)框架的语法, 框架要完成一个功能,需要一定的步骤支持的,
3)框架的内部实现, 框架内部怎么做。 原理是什么。
4)通过学习,可以实现一个框架。
IoC 控制反转
控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代 码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对 象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值, 依赖的管理。
Ioc 的实现:
➢ 依赖查找:DL( Dependency Lookup ),容器提供回调接口和上下文环境给组件。
➢ 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。
Spring 框架使用依赖注入(DI)实现 IoC。
Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。
Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式 来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦合。
控制反转的理解
控制反转, 是一个理论,概念,思想。
控制: 创建对象,对象的属性赋值,对象之间的关系管理。
正转:由开发人员在代码中,使用 new 构造方法创建对象, 开发人员主动管理对象。
把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。 由容器代替开发人员管理对象,创建对象,给属性赋值。
为什么要使用 ioc : 目的就是减少对代码的改动, 也能实现不同的功能。 实现解耦合。
底层实现是反射机制。
Spring 的第一个程序
实现步骤:
(1)添加 module,新建 maven 工程,选择maven-archetype-quickstart
骨架模板
(2)填写工程坐标:
1 | GroupId:com.kwxy |
(3)Module name 设置为:ch01-hello-spring,点击 finish,等待 maven 项目的构建,控制台显示 BUILD SUCCESS 字样,则构建成功
(4) 将src/main
和src/test
下的java
目录右键Mark Directory as-->Sources Root
(5)在src/main
下新建resources
目录,右键Mark Directory as-->Resources Root
(6)删除默认创建的 APP 类文件 ,这些文件分别在 main 和 test 目录中
(7)删除 pom.xml 文件中无关的配置,将编译和运行 jdk 版本改为1.8
(8)在build标签
中添加’指定资源位置’的配置,配置大致如下:
1 |
|
(9)添加 Spring 依赖(加完最好右键pom.xml->Maven->Reimport
)
1 | <!--Spring的依赖--> |
每个包的作用:
参考文章:https://blog.csdn.net/yjc0403/article/details/84832812
spring 官网给出了一张 spring3 的结构图
core
- spring-core:依赖注入 IoC 与 DI 的最基本实现
- spring-beans:Bean 工厂与 bean 的装配
- spring-context:spring 的 context 上下文即 IoC 容器
- spring-expression:spring 表达式语言
aop
- spring-aop:面向切面编程
- spring-aspects:集成 AspectJ
- spring-instrument:提供一些类级的工具支持和 ClassLoader 级的实现,用于服务器
- spring-instrument-tomcat:针对 tomcat 的 instrument 实现
data access
- spring-jdbc:jdbc 的支持
- spring-tx:事务控制
- spring-orm:对象关系映射,集成 orm 框架
- spring-oxm:对象 xml 映射
- spring-jms:java 消息服务
web
- spring-web:基础 web 功能,如文件上传
- spring-webmvc:mvc 实现
- spring-webmvc-portlet:基于 portlet 的 mvc 实现
- spring-struts:与 struts 的集成,不推荐,spring4 不再提供
test
test 部分只有一个模块,我将 spring-context-support 也放在这吧
- spring-test:spring 测试,提供 junit 与 mock 测试功能
- spring-context-support:spring 额外支持包,比如邮件服务、视图解析等
下面介绍 spring4,与 spring3 结构基本相同,下面是官网给出的结构图
可以看到,图中去掉了 spring3 的 struts,添加了 messaging 和 websocket,其他模块保持不变,因此,spring4 的 jar 有 20 个
- spring-websocket:为 web 应用提供的高效通信工具
- spring-messaging:用于构建基于消息的应用程序
(1) spring-core.jar
这个 jar 文件包含 Spring 框架基本的核心工具类,Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类。
(2) spring-beans.jar
这个 jar 文件是所有应用都要用到的,它包含访问配置文件、创建和管理 bean 以及进行 Inversion of Control / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的 IoC/DI 支持,引入 spring-core.jar 及 spring-beans.jar 文件就可以了。
(3) spring-aop.jar
这个 jar 文件包含在应用中使用 Spring 的 AOP 特性时所需的类。使用基于 AOP 的 Spring 特性,如声明型事务管理(Declarative Transaction Management),也要在应用里包含这个 jar 包。
(4) spring-context.jar
这个 jar 文件为 Spring 核心提供了大量扩展。可以找到使用 Spring ApplicationContext 特性时所需的全部类,JDNI 所需的全部类,UI 方面的用来与模板(Templating)引擎如 Velocity、FreeMarker、JasperReports 集成的类,以及校验 Validation 方面的相关类。
(5) spring-dao.jar
这个 jar 文件包含 Spring DAO、Spring Transaction 进行数据访问的所有类。为了使用声明型事务支持,还需在自己的应用里包含 spring-aop.jar。
(6) spring-hibernate.jar
这个 jar 文件包含 Spring 对 Hibernate 2 及 Hibernate 3 进行封装的所有类。
(7) spring-jdbc.jar
这个 jar 文件包含对 Spring 对 JDBC 数据访问进行封装的所有类。
(8) spring-orm.jar
这个 jar 文件包含 Spring 对 DAO 特性集进行了扩展,使其支持 iBATIS、JDO、OJB、TopLink,因为 Hibernate 已经独立成包了,现在不包含在这个包里了。这个 jar 文件里大部分的类都要依赖 spring-dao.jar 里的类,用这个包时你需要同时包含 spring-dao.jar 包。
(9) spring-remoting.jar
这个 jar 文件包含支持 EJB、JMS、远程调用 Remoting(RMI、Hessian、Burlap、Http Invoker、JAX-RPC)方面的类。
(10) spring-support.jar
这个 jar 文件包含支持缓存 Cache(ehcache)、JCA、JMX、邮件服务(Java Mail、COS Mail)、任务计划 Scheduling(Timer、Quartz)方面的类。
(11) spring-web.jar
这个 jar 文件包含 Web 应用开发时,用到 Spring 框架时所需的核心类,包括自动载入 WebApplicationContext 特性的类、Struts 与 JSF 集成类、文件上传的支持类、Filter 类和大量工具辅助类。
(12) spring-webmvc.jar
这个 jar 文件包含 Spring MVC 框架相关的所有类。包含国际化、标签、Theme、视图展现的 FreeMarker、JasperReports、Tiles、Velocity、XSLT 相关类。当然,如果你的应用使用了独立的 MVC 框架,则无需这个 JAR 文件里的任何类。
(13) spring-mock.jar
这个 jar 文件包含 Spring 一整套 mock 类来辅助应用的测试。Spring 测试套件使用了其中大量 mock 类,这样测试就更加简单。模拟 HttpServletRequest 和 HttpServletResponse 类在 Web 应用单元测试是很方便的。
(10)创建业务接口和实现类
接口:
1 | package com.kwxy.service; |
实现类:
1 | package com.kwxy.service.impl; |
(11)创建 Spring 配置文件
如同在 Servlet 中我们需要在 web.xml 中注册我们希望服务器自动创建管理的 servlet 对象一样,在 Spring 中也需要有类似的配置,来自动创建刚才的 SomeServiceImpl 对象。
在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名称为 applicationContext.xml
。
IDEA 已经为我们设计好了 Spring 配置文件的模板:右击resources–>new–>XML configuration file–>Spring Config
模板如下:
1 |
|
注意
,Spring 配置文件中使用的约束文件为 xsd 文件。作用与 Mybatis 的 sql 映射文件的 dtd 约束文件类似,但 xsd 约束作用更强:
1)定义一个 xml 文档中都有什么元素
2)定义一个 xml 文档中都有什么属性
3)定义一个 xml 文档中元素可以有哪些子元素,以及元素的顺序
4)定义一个 xml 文档中元素和属性的数据类型
<beans/>
是配置文件的根标签。在 Spring 中,java 对象称之为 bean。在这个标签下进行 java 对象的注册:
1 | <bean id="someService" class="com.kwxy.service.SomeServiceImpl" scope="prototype"/> |
1.声明 java 对象交给 Spring 创建和管理,这个步骤称之为声明bean
。
等同于:
SomeService someService = new com.kwxy.service.SomeServiceImpl();
然后将创建的对象是放入到 Spring 的容器(Map<id,对象>):
private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap(16);
factoryBeanObjectCache.put(“service”,someService);
一个 bean 标签声明一个对象。
2.<bean/>
标签的属性:
class:类的全限定名称,不能是接口(Spring 使用反射创建对象);
class可以是非自定义的对象,例如”java.util.Date“,依然可以被Spring创建对象
。
id:自定义的对象名称,要求是唯一值。 表示在 Spring 中的对象名称,通过这个名称可以从 Spring 中找到对象,获取对象。
scope:指定 bean 对象的作用域(对象的存在范围和可见性)。
scope 的可取值为:
1)单例
:singleton
, 默认值,表示叫这个名称的对象在 spring 容器中只有一个
。
2)原型
:prototype
, 表示每次使用 getBean()都创建一个新的对象
。
(12)新建测试类,在测试类中创建测试方法
1 | public class MyTest { |
Bean 的装配
默认装配方式
当我们创建 ApplicationContext 对象时,Spring 会读取配置文件的<bean/>
并执行对应类的无参构造器。在这些类的无参构造器中加一条输出语句可以验证以上结论。
ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。
以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。
容器中 Bean 的作用域
当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 的实例化,还可以通过 scope 属性,为 Bean 指定特定的作用域。Spring 支持多种作用域。
(1)singleton:单例模式。即在整个 Spring 容器中,使用 singleton 定义的 Bean 将是单例的,叫这个名称的对象只有一个实例。默认为单例的。
(2)prototype:原型模式。即每次使用 getBean 方法获取的同一个的实例都是一个新的实例。
(3)request:对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例。
(4)session:对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例。
注意:
对于 scope 的值 request、session 只有在 Web 应用中使用 Spring 时,该作用域才有效。
对于 scope 为 singleton 的单例模式,该 Bean 是在容器被创建时即被装配好了;
对于 scope 为 prototype 的原型模式,Bean 实例是在代码中使用该 Bean 实例时才进行装配的。
我们只是自动创建了对象,没有给对象的属性赋值。后面介绍两种给属性赋值的方法。
基于 XML 的 DI
通过在 xml 配置文件对对象的属性进行赋值。
设值注入(掌握)
又称为 set 注入,通过调用类中属性的 setter 给属性赋值。
简单类型(java 中的基本类型和 String 类型)
在<bean/>
标签下添加:<property name="属性名" value="简单类型的属性值" />
每一个 property 标签,完成一个属性的赋值。
注意:
Spring 执行 property 标签的原理,是执行 name 属性值对应的 set 方法。而并不关心 set 方法的具体实现和属性是否真的存在。
引用类型
当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的 值必须为某 bean 的 id 值。
例如:创建一个学校类:
1 | package com.kwxy.domain; |
在学生类中添加 School 类型的属性:
1 | package com.kwxy.domain; |
Spring 配置文件中添加如下 bean:
1 | <!--声明School--> |
构造注入(理解)
执行类的有参构造,在构造对象的同时给属性赋值。
1.给 Student 类添加有参构造器:
1 | //定义有参数构造方法 |
2.对 xml 配置文件进行相关配置:
1 | <!--使用name属性--> |
<constructor-arg />
标签中用于指定参数的属性有:
➢ name:指定参数名称,指的是构造方法中的形参。
➢ index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。
value:构造方法的形参类型是简单类型的,使用 value
ref:构造方法的形参类型是引用类型的,使用 ref
引用类型的自动注入
对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为<bean/>
标签 设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)。
根据自动注入判断标准的不同,可以分为两种:
(1)byName:根据名称自动注入
(2)byType: 根据类型自动注入
byName 方式自动注入
当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用 byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。
容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
例如,在 Student 类中,School 类型的属性名是”school“,那么在 xml 配置文件中:
1 | <bean id="mySchool" class="com.kwxy.domain.School" autowire="byName"> |
byType 方式自动注入
使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类, 要与代码中调用者 bean 类的某引用类型属性类型同源。
即要么相同,要么有 is-a 关系(子 类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。
1 | <!-- |
为应用指定多个 Spring 配置文件
在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变 得非常庞大、臃肿。
为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。
包含关系的配置文件: 多个配置文件中有一个总文件,总配置文件将各其它子文件通过<import/>
引入。
在 Java 代码中只需要使用总配置文件对容器进行初始化即可。
例如:有 spring-school.xml、spring-student.xml 配置文件和一个 total.xml 总配置文件。
在 total.xml 中:
1 |
|
注意: 主的配置文件名称不能包含在通配符的范围内。
基于注解的 DI
通过以下四个步骤实现 DI:
(1)创建 Maven 项目,在 pom.xml 加入 AOP 依赖
1 | <dependency> |
如果你在 pom.xml 中添加了 spring-context,那此依赖自动包含 spring-aop,无需再次添加。
(2)需要更换配置文件头,加入 spring-context.xsd 约束
约 束 在 %SPRING_HOME%\docs\spring-framework-reference\html\xsd-configuration.html 文件中。
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
如果使用 IDEA 进行编辑,这一步骤可以省略,在执行第四步时根据提示自动添加即可。
(3)在类中添加注解
1 | package com.kwxy.domain; |
(4)声明组件扫描器。在 Spring 配置文件的<beans/>
标签下:
1 | <!--声明组件扫描器(component-scan),组件就是java对象 |
定义 Bean 的注解@Component(掌握)
@Component: 创建类的对象,等同于<bean />
,默认是单例对象
属性: value 表示对象的名称(bean 的 id)
位置: 在类定义的上面,表示创建此类的对象。
例如:@Component(value = "myStudent")
等价于<bean id="myStudent" class="com.kwxy.domain.Student"/>
另外,Spring 还提供了 3 个创建对象的注解:
➢ @Repository 用于对 DAO 实现类进行注解
➢ @Service 用于对 Service 实现类进行注解
➢ @Controller 用于对 Controller 实现类进行注解 这三个注解与@Component 都可以创建对象
但这三个注解还有其他的含义,@Service 创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处理器接收用户的请求。
@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象。
@Component 不指定 value 属性,bean 的 id 是类名的首字母小写。
若想使用这些注解形式的 DI,必须在 spring 配置文件中声明组件扫描器
:
1 | <context:component-scan base-package="注解类所在包名" /> |
简单类型属性注入@Value(掌握)
1 | "myStudent") ( |
byType 自动注入@Autowired(掌握)
需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。
使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加 到 setter 上。
@Autowired:spring框架提供的注解,实现引用类型的赋值,默认使用byType自动注入;
当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。
即Autowired默认先按byType,如果发现找到多个bean,则,又按照byName方式比对,如果还有多个name相同,则报出异常。
当不确定注入哪个 bean 的时候,可以联合使用注解@Autowired 与@Qualifier 指定注入某个 bean 的 id。
例如 School 类:
1 | "mySchool") ( |
Student 类:
1 | "myStudent") ( |
为了防止空指针异常
的产生,推荐@AutoWired 注解加在带参构造器上。具体原因参见:阿丙博客
byName 自动注入@Autowired 与@Qualifier(掌握)
需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用 于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。
例如:Student 要自动注入 School 类,只需在 school 属性前添加注解:
1 |
|
@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
JDK 注解@Resource 自动注入(掌握)
Spring 提供了对 jdk 中@Resource 注解的支持。
@Resource 注解既可以按名称匹配 Bean, 也可以按类型匹配 Bean。
默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。
@Resource 可在属性上,也可在 set 方法上。
本质就是用@Resource一个注解代替@Autowired和@Qualifier两个注解。
(1)byType 注入引用类型属性
@Resource 注解若不带任何参数,采用默认按名称
的方式注入,按名称不能注入 bean, 则会按照类型进行 Bean 的匹配注入。
如果 Student 要自动注入 School 类,只需在 school 属性前添加注解:
1 |
|
(2)byName 注入引用类型属性
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
如果 Student 要自动注入 School 类,只需在 school 属性前添加注解:
1 | "mySchool") (name = |
如果 JDK 是 11 版本的,学到@Resource 注解的时候,加上以下依赖,否则会报错。因为高版本中移除了改模块。
1 | <dependency> |
当一个接口有多个实现类时
当一个接口有多个实现时,直接使用@Autowired 可能会报异常,原因 Autowired 默认按照 byType 注入,找到了多个符合条件的 bean,但是不知道注入哪一个,就会报异常。
方式 1:
联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用 于指定要匹配的 Bean 的 id 值。
方式 2:
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
注解与 XML 的对比
注解
优点是:
(1)方便
(2)直观
(3)高效(代码少,没有配置文件的书写那么复杂)
其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的
XML方式
优点是:
(1)配置和代码是分离的
(2)在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载
xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。
xml与注解方式还有一个区别在于,xml方式可以通过bean标签创建一个类的多个对象,但注解方式并不能,因为一个类不能存在两个同名的注解。
AOP 面向切面编程
AOP 概述
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP 底层,就是采用动态代理模式实现的。
采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理
,AOP 可以看作动态代理的规范化与标准化
。
面向切面编程
,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到 主业务逻辑中。
所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、 事务、日志、缓存等。
若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。
例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事 务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。
但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑—转账。
AOP 的相关术语
(1) 切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面 是通知(Advice)。实际就是对主业务逻辑的一种增强。
(2) 连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
(3) 切入点(Pointcut)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
(4) 目标对象(Target)
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。上例中的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。
(5) 通知(Advice)
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。
AOP 的实现
AOP 的技术实现框架:
Spring:Spring 的 AOP 实现较为笨重,一般用在事务处理。
aspectJ:一个开源,专门做 AOP 的框架,隶属于 Eclipse 基金会。
Spring 框架集成了 aspectJ 的功能。
aspectJ 框架实现 AOP 有两种方式:
1)使用 xml 配置文件,一般用于配置全局事务;
2)使用注解。一般在项目开发中使用这种方式。
AspectJ 对 AOP 的实现
AspectJ 的通知类型
切面的执行时间, 这个执行时间在规范中叫做 Advice(通知,增强)
AspectJ 中常用的通知有五种类型,体现在五个不同的添加在切面的注解:
(1)前置通知 @Before
(2)后置通知 @AfterReturning
(3)环绕通知 @Around
(4)异常通知 @AfterThrowing
(5)最终通知 @After
AspectJ 的切入点表达式
表示切面执行的位置,使用的是切入点表达式
AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
1 | execution ( |
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名
。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。
在其中可以使用以下符号:
*
:0 至多个任意字符
..
:用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包
+
:用在类名后,表示当前类及其子类;用在接口名后,表示当前接口及其实现类
例如:
execution(* *..service.*.*(..))
上面表达的意思是返回值任意, 指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
技巧:
execution(访问权限 方法返回值 方法声明(参数) 异常类型) 方法返回值和方法声明是必需的。
其实类似于声明一个方法: public void 方法名(参数) throws 异常
AspectJ 的开发环境
(1)引入 AspectJ 依赖:
1 | <!--aspectj的依赖--> |
(2)引入约束(第 4、7、8 行)
1 |
|
在 IDEA 中开发,可以省略这一步。在添加 aop 标签时会自动引入约束文件。
AspectJ 基于注解的 AOP 实现
AspectJ 基于注解的 AOP 实现步骤
(1)定义业务接口与实现类
1 | package com.kwxy.service; |
1 | package com.kwxy.service.impl; |
(2)定义切面类
1 | package com.kwxy.aspect; |
(3)声明目标对象与切面类对象,注册 AspectJ 的自动代理
1 |
|
<aop:aspectj-autoproxy/>
通过扫描找到@Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
(4)创建测试方法测试,获取代理对象根据目标对象的 id。
1 |
|
AspectJ 通知注解
通知注解:
在切面类中修饰方法的注解,这些注解体现了通知类型。例如上面例子中@Before 就是一个通知注解。通知注解修饰的方法称之为通知方法。
一共有五种通知类型,就对应了五种通知注解。下面一一介绍。
@Before 前置通知-方法有 JoinPoint 参数
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参 数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、 目标对象等。
1 | "execution(public void com.kwxy.service.impl.SomeServiceImpl.doSome(..))") (value = |
不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。
@AfterReturning 后置通知-注解有 returning 属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。
所以,被注解为后 置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变 量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
例如:在业务接口中定义一个有返回值的抽象方法:
1 | String doOther(String name,int age); |
再在业务类中实现:
1 |
|
然后在切面类中定义一个切面:
1 | "execution(public String com.kwxy.service.impl.SomeServiceImpl.doOther(..))", returning = "result") (value = |
测试方法:
1 |
|
@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数
在目标方法执行之前之后执行。
被注解为环绕增强的方法要有返回,Object 类型。并 且方法可以包含一个 ProceedingJoinPoint 类型的参数。
接口 ProceedingJoinPoint 继承于 JoinPoint,因此可以根据它获取方法的信息。
其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。
最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
接口增加方法:
1 | String doFirst(String name,int age); |
在实现类中实现该方法:
1 |
|
增加切面:
1 | "execution(* *..SomeServiceImpl.doFirst(..))") (value = |
环绕通知能够控制目标方法是否执行,创建测试方法:
在上述代码中,如果 name 是 zs,则执行,否则不执行。
1 |
|
将 name 改为 zs 后,运行结果为:
1 | /* |
@AfterThrowing 异常通知-注解中有 throwing 属性
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。
当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。
在效果上相当于一个 try…catch 语句。目标方法的方法体在 try 语句块中,而切面方法的方法体放在了 catch 子句中。
@After 最终通知
无论目标方法是否抛出异常,该增强均会被执行。
在执行效果上,相当于将切面方法的方法体放在了 try…catch…finally…语句的 finally 子句中。
@Pointcut 定义切入点
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均 可使用该方法名作为切入点。
代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。 方法体内部也无需添加代码。
例如:
1 | "mypt()") (value = |
设置 AspectJ 实现 AOP 的方式
在 Spring 配置文件中,通过<aop:aspectj-autoproxy/>
的 proxy-target-class 属性设置选择通过 JDK 动态代理还是 cglib 动态代理实现 AOP。
1 | <!--声明自动代理生成器:使用aspectj把spring容器中目标类对象生成代理 |
Spring 集成 myBatis
概述
Spring 集成 myBatis,其本质工作就是:将使用 mybatis 框架时用到的一些需要自己创建的对象,交由 Spring 统一管理。
把 mybatis 框架和 spring 集成在一起,像一个框架一样使用。
用的技术是:ioc 。
为什么是 ioc:能把 mybatis 和 spring 集成在一起,像一个框架, 是因为 ioc 能创建对象。
可以把 mybatis 框架中的对象交给 spring 统一创建, 开发人员从 spring 中获取对象。
开发人员就不用同时面对两个或多个框架了, 就面对一个 spring。
说明:
1 | /* |
通过以上的说明,我们需要让 spring 创建以下对象:
(1)数据源 dataSource。就是保存数据库连接信息的对象。在实际业务开发中,我们放弃使用 Mybatis 自带的数据库连接池,而采用阿里的 Druid,更加高效;
(2)生成 sqlSession 对象的 sqlSessionFactory;
(3)Dao 接口的实现类对象。
需要学习就是上面三个对象的创建语法,使用 xml 的 bean 标签。
Spring 集成 myBatis 创建项目的流程
(1)新建 mysql 数据库,准备数据。( student 表)
建表语句
1 | DROP TABLE IF EXISTS `student`; |
添加数据
1 | INSERT INTO `student` VALUES ('1001', '李晶', 'lijing@163.com', '23'); |
1 | mysql> select *from student; |
(2)新建 maven 的 module
(3)加入依赖
1)spring 依赖
2)mybatis 的依赖
3)mybatis-spring 依赖, 这个 jar 是从 mybatis 官网下载的, mybatis 提供在 spring 中创建对象的类
4)mysql 的驱动
5)druid,数据库连接池的依赖
1 |
|
(4)新建实体类 Student
1 | package com.kwyx.domain; |
记得在pom.xml中添加资源插件!
(5)新建 Dao 接口和 sql 映射文件
1 | package com.kwyx.dao; |
1 |
|
(6)新建 mybatis 主配置文件 mybatis-config.xml
由于使用阿里的数据库连接池,所以不需要<environments/>
标签
1 |
|
(7)新建 Service 接口和实现类, 在实现类中有 Dao 的属性
在实际项目中,我们在对数据库前需要一些其他的业务代码,例如逻辑判断、身份认证等,这些放在 Service 中
1 | package com.kwyx.service; |
1 | package com.kwyx.service.impl; |
(8)创建数据库连接配置文件 jdbc.properties
1 | jdbc:mysql://localhost:3306/ssm = |
不需要配置数据库驱动
(9)新建 Spring 的配置文件 applicationContext.xml(重要)
1)声明 Druid 的数据源 DruidDataSource 对象
2)声明 SqlSessionFactoryBean,创建 SqlSessionFactory 对象
3)声明 MyBatis 的扫描器 MapperScannerConfigurer,创建 Dao 接口的实现类对象
4)声明自定义的 Service ,把 3)中的 Dao 对象注入赋值给 Service 的属性
1 |
|
(10)新建测试类, 从 spring 容器中获取 Service,调用 Service 的方法,完成数据库的操作
1 | public class MyTest { |
Spring 与事务
理论知识
(1)什么是事务
讲 mysql 的时候,提出了事务。 事务是指一组 sql 语句的集合, 集合中有多条 sql 语句,
可能是 insert , update ,select ,delete, 我们希望这些多个 sql 语句都能成功,
或者都失败, 这些 sql 语句的执行是一致的,作为一个整体执行。
(2)在什么时候想到使用事务
当我的操作,涉及得到多个表,或者是多个 sql 语句的 insert,update,delete。
需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的。
在 java 代码中写程序,控制事务,此时事务应该放在哪里呢?
service 类的业务方法上,因为业务方法会调用多个 dao 方法,执行多个 sql 语句。
(3)通常使用 JDBC 访问数据库, 还是 mybatis 访问数据库怎么处理事务
jdbc 访问数据库,处理事务 Connection conn ; conn.commit(); conn.rollback();
mybatis 访问数据库,处理事务, SqlSession.commit(); SqlSession.rollback();
hibernate 访问数据库,处理事务, Session.commit(); Session.rollback();
(4)上面事务的处理方式,有什么不足
不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理
要掌握多种数据库中事务的处理逻辑,什么时候提交事务,什么时候回顾事务
处理事务需要多种方法
(5)怎么解决不足
spring 提供一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。
使用 spring 的事务处理机制,可以完成 mybatis 访问数据库的事务处理。
使用 spring 的事务处理机制,可以完成 hibernate 访问数据库的事务处理。
spring 处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给 spring 就可以了。
事务管理器接口
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
PlatformTransactionManager 接口有两个常用的实现类:
➢ DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
➢ HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
Spring 的回滚方式
Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。
运行时异常
,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。
如, NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于运 行时异常。
这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。
但,只要代码 编写足够仔细,程序足够健壮,运行时异常是可以避免的。
受查异常
,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理, 则无法通过编译。
如 SQLException,ClassNotFoundException,IOException 等都属于受查异常。 RuntimeException 及其子类以外的异常,均属于受查异常。
事务定义接口
事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作。
(1)定义了五个事务隔离级别常量
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
➢ DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默 认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读 。
➢ SERIALIZABLE:串行化。不存在并发问题。
(2)定义了七个事务传播行为常量
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情 况。
如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的 维护情况,就称为事务传播行为。
事务传播行为是加在方法上的。
事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
1)PROPAGATION_REQUIRED:
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事 务,则创建一个新事务。
这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。 如该传播行为加在 doOther()方法上。
若 doSome()方法在调用 doOther()方法时就是在事 务内运行的,则 doOther()方法的执行也加入到该事务内执行。
若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。
2)PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
3)PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
使用 Spring 的事务注解管理事务
(1)开启注解驱动,告诉 Spring 框架现在使用注解处理事务,在 spring 配置文件 applicationContext 中添加如下代码:
1 | <!--开启注解驱动,开启事务支持,以tx开头,可以看出是用于事务的--> |
(2)声明事务管理器
1 | <!--声明事务管理器--> |
可视为固定写法,其中 property 标签的 ref 是配置文件中数据源对象的 id 属性值。
(3)业务层 public 方法加入事务注解
1 | /** |
@Transactional 的所有可选属性
如下所示:
➢ propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED。
➢ isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。
➢ readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 为 false。
➢ timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。在实际业务开发中一般不设置
。
➢ rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有 一个异常类时,可以不使用数组。
➢ rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。
➢ noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若 只有一个异常类时,可以不使用数组。
➢ noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空 数组。当然,若只有一个异常类时,可以不使用数组。
需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。
对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,
但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction注解。
若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
使用 AspectJ 的 AOP 配置管理事务
一般大型项目使用。在不更改源代码的条件下管理事务。代码和事务的配置完全是分离的,不需要在代码上加注解,全部是在 xml 中配置。
(1)添加 Maven 依赖
1 | <!--aspectj的依赖--> |
(2)声明事务管理器
1 | <!--声明事务管理器对象--> |
(3)配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。
1 | <!--声明业务方法它的事务属性(隔离级别,传播行为,超时时间) |
(4)配置 aop,配置增强器
指定哪些哪类要创建代理
1 | <!--配置aop--> |
Spring 与 Web
这一章主要介绍了一个核心知识点:解决不同 Servlet 中重复创建 ApplicationContext 对象,造成内存浪费的问题,即重复创建 Spring 容器。
解决这个问题的一个思路是,创建一个 ServletContextListener,在 ServletContext 初始化的时候创建 ApplicationContext 对象,并将它保存在 ServletContext 中。
这样,在每个 servlet 中,只要调用当前 servlet 的 ServletContext 对象 getAttribute 方法就可以获取这个 webapp 中共享的一个 ApplicationContext 对象。
spring-web 框架已经帮我们创建好了这样一个监听器。我们只需要在 web.xml 注册这个监听器就可以使用了。
(1)maven 创建 web 模块,加入 servlet,jsp 依赖,拷贝之前 spring 和 mybatis 中所用的依赖
1 | <!--以下是新增依赖,其它依赖到之前做的项目中拷贝--> |
(2)需要为监听器提供 Spring 的配置文件路径信息
1 | <!--注册spring框架提供的监听器 |
(3)在 Sevlet 中获取 ApplicationContext 对象
1 | WebApplicationContext ctx = null; |
webApplicationContext 是 ApplicationContext 的子类,是在 web 项目中使用的 Spring 容器对象。
为了不使用框架给出的难记的 key 值获取 webApplicationContext,这个框架还提供了一个工具类。
使用工具类获取 webApplicationContext:
1 | //获取ServletContext中的容器对象,spring提供了一个方法,获取容器对象 |
发布时间: 2020-07-22
最后更新: 2024-06-24
本文标题: Spring学习笔记
本文链接: https://blog-yilia.xiaojingge.com/posts/52ba89f4.html
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可。转载请注明出处!
