1
、知道框架能做什么
2
、学习框架的语法,一般框架完成一个功能需要一定的步骤
3
、框架的内部实现原理(扩展)
4
、尝试实现一个框架(提升)
1.2 Spring是什么
是一个是分层的 Java SE/EE full-stack 全栈 开源的轻量级的 Java 开发框架
Spring
具有
控制反转(IOC)
和
面向切面(AOP)
两大核心。
通过声明方式灵活进行
事务管理
,提高开发效率和质量。
1.3 Spring的优势
1、方便解耦,简化开发
Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给 Spring 管理。
2、方便集成各种优秀框架
Spring
不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如
Struts2
、
Hibernate
、
MyBatis
等)的直接支持。
3、降低 Java EE API 的使用难度
Spring
对
Java EE
开发中非常难用的一些
API
(
JDBC
、
JavaMail
、远程调用等)都提供了封装,
使这些
API
应用的难度大大降低。
4、方便程序的测试
Spring
支持
JUnit4
,可以通过注解方便地测试
Spring
程序。
5、AOP 编程的支持
Spring
提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。
6、声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无须手动编程。
1.4 Spring的体系结构
Spring
为我们提供了一站式解决方案(想要的解决方案都要),但
它把这些解决方案进行模块化
的,在实际开发中可以根据自己的需求去引入需要的模块/方案,不需要全部引入
Spring
框架提供约
20
个模块,可以根据应用程序的要求来选择。
1.4.1 核心容器
核心容器由
Spring-core
,
Spring-beans
,
Spring-context
,
Spring-context-support
和
Spring-
expression
(
SpEL
,
Spring
表达式语言,
Spring Expression Language
)等模块组成
core核心模块:spring中的IoC和DI都是由它实现的
beans模块:管理Java对象直接依赖关系
context模块:在由
core
和
beans 模块的基础上建立起来的,ApplicationContext
接口是
Context
模块的焦点
SpEL模块:
提供了强大的表达式语言,用于在运行时查询和操作对象
它们的完整依赖关系如下图所示:
1.4.2 数据访问/集成
JDBC=Java Data Base Connectivity
ORM=Object Relational Mapping
OXM=Object XML Mapping
JMS=Java Message Service
JDBC
模块提供
JDBC
抽象层,它消除冗长
JDBC
编码和对数据库供应商特定错误代码的解析。
ORM
模块提供了对流行的对象关系映射
API
的集成,包括
JPA
、
JDO
和
Hibernate
等。通过此模
块可以让这些
ORM
框架和
Spring
的其它功能整合,比如前面提及的事务管理。
OXM
模块提供了对
OXM
实现的支持,比如
JAXB
、
Castor
、
XML Beans
、
JiBX
、
XStream
等。
JMS
模块包含生产(
produce
)和消费(
consume
)消息的功能。从
Spring 4.1
开始,集成了
Spring-messaging
模块。
事务
模块为实现特殊接口类及所有的
POJO
支持编程式和声明式事务管理。
1.4.3 Web
Web
模块提供面向
web
的基本功能和面向
web
的应用上下文,比如多部分(
multipart
)文件上
传功能、使用
Servlet
监听器初始化
IoC
容器等。它还包括
HTTP
客户端以及
Spring
远程调用中与web 相关的部分。
Web-MVC
模块为
web
应用提供模型视图控制(
MVC
)和
REST Web
服务的实现。
Spring
的
MVC
框架可使领域模型代码和
web
表单完全分离,且可与
Spring
框架的其它所有功能进行成。
Web-Socket
模块为
WebSocket-based
提供了支持,而且在
web
应用程序中提供了客户端和服
务器端之间通信的两种方式。
Web-Portlet
模块提供了用于
Portlet
环境的
MVC
实现,并反映了
Spring-webmvc
模块的功能
1.4.4 其他
AOP
模块提供了面向方面(切面)的编程实现,允许你定义方法拦截器和切入点对代码进行干净
地解耦,从而使实现功能的代码彻底的解耦出来。
Aspects
模块提供了与
AspectJ
的集成,这是功能强大且成熟的面向切面编程(
AOP
)框架。
Instrumentation
模块在一定应用服务器中提供了类
instrumentation
的支持和类加载器的实现。
Messaging
模块为
STOMP
提供了支持作为在应用程序中
WebSocket
子协议的使用。它也支持一 个注解编程模型,它是为了选路和处理来自 WebSocket
客户端的
STOMP
信息。
测试
模块支持对具有
JUnit
或
TestNG
框架的
Spring
组件的测试。
2、Spring核心之IoC控制反转
什么是控制反转IoC、什么是依赖注入DI,之间有什么关系
2.1 什么是IoC
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。
IoC
是指在程序开发中,实例创建不再由调用者管理,而是由
Spring
容器创建。
Spring
容器会负责控制程序之间的关系,而不是由程序代码直接控制,因此,
控制权由程序代码转移到了 Spring 容器中,控制权发生了反转
,这就是 Spring
的
IoC
思想,spring容器是通过DI依赖管理实现IOC。
生成对象几种方式:构造方法new 反射 克隆 序列化 动态代理
javaweb servlet --doGet doPost --实例方法 没有new Servlet 由tomcat容器创建
2.2 Spring入门案例
2.2.1 创建maven的java项目
2.2.2 pom.xml文件添加依赖和插件
<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--maven编译插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
2.2.3 创建一个实体类
2.3.4 创建Spring的配置文件application.xml
2.3.5 使用Spring容器创建对象
配置文件中创建对象
2.3.6 两种方式获取Spring IoC容器和区别
Spring 提供了两种 IoC 容器
分别为
BeanFactory
和
ApplicationContext.
主要区别:
BeanFactory获取容器后,只有在getBean获取对象的时候才生成对象
ApplicationContext获取容器后就会生成对象
2.3.6.1 BeanFactory接口
BeanFactory
是基础类型的
IoC
容器
,
是一个管理
Bean
的工厂,它主要负责初始化各种
Bean
,并调用它们的生命周期方法。
BeanFactory 接口有多个实现类,最常见的是
XmlBeanFactory
它是根据 XML 配置文件中的定义装配Bean 的。
BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource(Spring配置文件
的名称));
2.3.6.2 ApplicationContext接口
ApplicationContext
是
BeanFactory
的子接口,也被称为
应用上下文
。
它在
BeanFactory
基础上,还添加了对 i18n
(国际化)、资源访问、事件传播等方面的良好支持。
ApplicationContext
接口有两个常用的实现类
:
2.6.2.2.1
ClassPathXmlApplicationContext
——
常用
该类从类路径
ClassPath
中寻找指定
XML
文件,找到并装载完成
ApplicationContext
实例化工作
2.6.2.2.2
FileSystemXmlApplicationContext
它与
ClassPathXmlApplicationContext
区别是
在读取
Spring
的配置文件时,FileSystemXmlApplicationContext 不再从类路径中读取配置文件,而是通过参数指定配置文件的位置,它可以获取类路径之外的资源,如“D:\application.xml”
。
简单说就是一个需要指定具体的xml路径,一个只需要xml文件名称即可。
2.3.7 通过上下文对象获取容器中的对象
package com.bowei.test;
import com.bowei.pojo.Team;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.FileSystemResource;
/**
* 使用Spring容器BeanFactory和ApplicationContext创建对象
*/
public class Test1 {
//1.指定spring配置文件名称
String springConfig="application.xml";
/**
* 方式一:使用BeanFactory容器创建对象,已过时,不推荐,只了解
* 它是根据 XML 配置文件中的定义装配Bean 的
*/
@Test
public void test01(){
//1、创建spring容器的对象:
BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource("I:..src\\main\\resources\\application.xml"));
//2.根据ID从IOC容器获取对象
Object team1 = beanFactory.getBean("team1");
}
/**
* 方式二:使用ApplicationContext容器创建对象
* 1.ClassPathXmlApplicationContext 从类路径中寻找指定XML文件,完成实例化工作 常用
* 2.FileSystemXmlApplicationContext 指定具体xml路径 只了解
*/
@Test
public void test02(){
//2、创建spring容器的对象:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(springConfig);
//3.根据ID从IOC容器获取对象
Team team1 = (Team) applicationContext.getBean("team1");
//4.容器其它api
int beanDefinitionCount = applicationContext.getBeanDefinitionCount();
System.out.println("spring容器中对象的个数:"+beanDefinitionCount);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
System.out.println("spring容器中所有对象名称:");
for (String name : beanDefinitionNames) {
System.out.println(name);
}
//另一种方式
//ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("I:..src\\main\\resources\\application.xml");
}
}
2.3.8 创建非自定义对象和容器其它api
pox.xml文件中补充
上面的测试方法中添加如下内容:
2.3.9 bean标签的属性
application.xml
配置文件添加如下内容:
创建ApplicationContext容器后,对象就会被生成,如何去控制对象生成时间,达到现用现创建?
在bean标签中设置对象为单例对象(默认),然后添加lazy-init:true 懒加载属性
或者直接设置对象为多例对象。
2.3 Spring容器创建对象的方式
2.3.1 使用默认的构造方法
2.3.2 使用带参数的构造方法
实体类
配置文件
测试
结果
2.3.3 使用工厂类
实体类还是Team
创建工厂类
createType.xml中bean标签
测试类
2.4 基于XML的DI
2.4.0.什么是DI,与IoC之间的关系
什么是DI
DI—Dependency Injection
,即
“
依赖注入
”:Spring容器动态的将某个依赖关系注入到组件之中
依赖注入的目的
:
是为了提升组件重用的频率;
通过依赖注入机制,我们只需要通过
简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资
源来自何处,由谁实现。
比如A、B、C都是service对象,D是需要的dao对象,对象创建完成后再把dao对象注入到需要dao的service中
IoC与DI之间关系
Ioc
和
DI
是同一个概念的不同角度描述
。
IoC
是一种思想,概念,
DI
是实现它的手段。
Spring
框架使用依赖注入实现IoC
比如工厂方法创建对象过程中就使用了依赖注入,将创建好的factory对象注入到team4中使用。
Spring
容器是一个超级大工厂,除了负责创建
Java
对象外,还管理着这些java Bean之间的依赖关系,Spring
使用
“
依赖注入
”
的方式来管理
Bean
之间的依赖关系,使对象之间实现了松耦合。
2.4.1 注入分类
bean
实例在调用无参构造器创建对象后,就要对
bean
对象的属性进行初始化。初始化是由容器自动完成的,称为注入。
注入就是给类中引入你想要的资源然后使用,常见就是给类中属性引入对象进行属性的初始化操作。
set
注入也叫设值注入是指,
在创建调用者的实例service时通过 setter 方法传入被调用者dao的实例
。这种注入方式简单、直观,因而在Spring 的依赖注入中大量使用。
创建
TeamDao.java
之前我们在service中是通过new 方式创建dao来在service中使用的,但在框架中是有spring创建管理对象的,这种方法就不可用了。
TeamDao teamDao=new TeamDao();
创建TeamService.java
这需要我们通过spring容器创建TeamService对象时给属性teamDao通过set方式注入一个dao对象
创建配置文件
DI.xml
此时TeamService中的属性teamDao就是个实例了,可以调用对象
构造注入是指,
在创建调用者实例service的同时,通过构造方法完成被调用者dao的实例化。使用构造器设置依赖关系。
需要在service中添加构造器
在DI.xml中配置bean
测试
2.4.1.3.
自动注入
对于引用类型属性的注入,也可不在配置文件中显示的注入。可以在标签中添加
autowire
(自动装配)属性,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属 性)。根据
自动注入判断标准的不同,可以分为两种
:
1
、
byName
2
、
byType
查找与属性类型相同的容器中对象,但是要求类型相同的bean唯一,否则抛出异常,不知用哪一个进行自动注入
Spring容器通过构造方法和工厂方法这两种创建对象的方式就是使用依赖注入的方式进行bean初始化和资源的引入。基本类型的引入使用value,对象的引入常用ref。
2.5、基于注解实现IoC--重要
问题一:如何通过注解告知容器,哪些类的对象创建交由spring容器处理?--基于注解实现IoC
1.在类上标识 声明Bean 的注解
2.在配置文件中配置进行包扫描,找到含Component注解的类交由spring创建对象
问题二:spring中创建对象的注解有哪些?/或声明Bean的注解
@Component
@Repository
@Service
@Controller
之前都是在spring配置文件xml中声明bean,由spring容器创建bean对象,并通过依赖注入方式管理bean之间的依赖关系,(注入方式引入资源,初始化属性操作),通过依赖注入实现了IoC,也就是对象的创建管理交由spring容器负责。
使用注解实现IOC,就是通过注解方式实现spring容器创建对象并通过DI管理bean之间的依赖关系。
对于
DI
使用注解,将不再需要在
Spring
配置文件中声明
bean
实例。
Spring
中使用注解,需要在原有Spring 运行环境基础上再做一些改变。
2.5.1 声明Bean的注解 @Component
在类上添加注解
@Component
表示该类创建对象的权限交给
Spring
容器。注解的
value
属性用于指定bean的
id
值,
value
可以省略。
@Component
不指定
value
属性,
bean
的
id
是类名的首字母小写。
除此之外,Spring中还提供了其他3个用于创建对象/声明Bean的注解:
@Repository :
用于
dao实现类的的注解 [rɪˈpɒzətri]
@Service:
用于
service
实现类的注解
@Controller:
用于
controller
实现类的注解
这三个注解与
@Component
都可以创建对象,但这三个注解还有其他的含义
@Service
创建业务层对象,业务层对象可以加入事务功能。
@Controller
注解创建的对象可以作为处理器接收用户的请求
@Repository
,
@Service
,
@Controller
是对
@Component
注解的细化,子字注解,标注不同层的对象。即持久层对象,业务层对象,控制层对象。
@Component注解常用于实体类等非持久层、业务层和处理层的类上。
2.5.2 包扫描
需要在
Spring
配置文件中
配置组件扫描器
,用于在指定的基本包中扫描注解。
如果没有包扫描,添加的创建对象的注解不生效。
需要告诉注解你在哪些类上加了注解,将哪些对象交给Spring容器,就需要包扫描,扫描指定包中的类上如果加上了@Component注解,这些加了注解的类就交给Spring容器创建。
示例:
1. 标识@Component注解的类
2.包扫描
配置文件中配置组件扫描器component-scan 注意要添加context命名空间
测试
多个包的扫描方式:
1
、使用多个
context:component-scan
指定不同的包路径
2
、指定
base-package
的值使用分隔符
3
、
base-package
是指定到父包名
base-package
的值表是基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到子包下级的子包。所以 base-package
可以指定一个父包就可以。
但不建议使用顶级的父包,扫描的路径比较多,导致容器启动时间变慢。指定到目标包和合适的。也就是注解所在包全路径
属性注入的注解实现
2.5.3 属性注入@Vaule
我们在配置文件中通过bean标签声明对象的方式创建对象时,是通过标签的方式实现属性注入,比如set注入、构造注入、自动注入(byName、byType)。
那么当使用注解声明bean由容器创建对象时,是如何实现属性的注入?(属性的初始化)
当容器启动后,进行包扫描,找到还有@Component注解的类,交由容器调用默认构造方法创建对象,此时对象的属性没有值,可以通过value注解给属性注入值,@Value写在属性是上,它其实是通过set方法进行的属性注入,可以将@value注解写在set方法上。
Team实体类(只截了部分)
配置文件设置组件扫描器进行包扫描
测试
2.5.4 @Autowired 自动注入 方式一:byType
需要在引用属性上使用注解
@Autowired
,该注解默认使用按类型自动装配
Bean
的方式。使用该注解完成属性注入时,类中无需 setter
。当然,若属性有
setter
,则也可将其加到
setter
上。
这种根据属性类型去获取容器中类型相同的对象注入到属性的方式,当有多个相同类型的对象时就会报出异常,不知道选择哪一个对象。
TeamDao类
TeamService类
配置文件中进行包扫描
测试
2.5.5 @Autowired自动注入 方式二:byName
需要在引用属性上联合使用注解
@Autowired
与
@Qualifier
。
@Qualifier 的 value
属性用于指定要匹配的 Bean
的
id
值。类中无需
set
方法,也可加到
set
方法上。
@Autowired 还有一个属性 required
,默认值为
true
,表示当匹配失败后,会终止程序运行。若将其值设置为 false
,则匹配失败,将被忽略,未匹配的属性值为
null
。
2.5.6 自动注入@Resource
@Autowired 是Spring提供的自动注入的注解
@Resource是jdk中提供的自动注入的注解
@Resource
注解
既可以按名称匹配Bean,也可以按类型匹配 Bean
。
默认是按名称注入
。
@Resource
可在属性上,也可在 set
方法上。
1
、
byType
注入引用类型属性
@Resource
注解若不带任何参数,默认先按名称的方式注入,按名称不能注入
bean
,则会按照类型进行 Bean
的匹配注入。就是先去容器中有没有和属性名teamService相同的对象,有就注入给属性,没有那么和属性类型相同的对象也可以注入进来。
2
、
byName
注入引用类型属性
@Resource
注解也可以指定要注入对象的名称,此时最好加上type属性声明对象的类型,避免同名对象。
高版本的jdk中无法使用Resource,通过maven引入
4、Spring核心之AOP
4.1.说说你对AOP的理解
AOP
为
Aspect Oriented Programming
的缩写,意思为
面向切面编程
,是
通过预编译方式和运行期动态代理实现程序功能
的统一维护的一种技术。
AOP面向切面编程就是将核心业务代码和服务性代码(日志、权限、事务)拆开编写,编程人员可以各自做自己擅长的部分,程序运行的时候是通过spring工厂自动实现将服务性代码切面的方式加入到核心业务代码中实现完整的功能,实现了程序运行期间对方法进行功能增强。(通过动态代理将核心代码和服务器性代码编织在一起)
运行阶段:
spring
框架会在运行的时候将核心业务和
AOP
代码通过动态代理的方式编织在一起
代理方式的选择:目标对象是否实现了接口:有接口就选择
JDK
动态代理;没有就选择
CGLIB
动态代理。
好处:
1
、减少代码的重复,提高开发效率,便于维护。
2
、专注核心业务的开发。
4.2 AOP的实现机制-动态代理
4.2.1 什么是代理模式
代理:自己不做,找人帮你做。
代理模式:在一个原有功能的基础上添加新的功能。
分类:静态代理和动态代理
4.3静态代理
4.3.1 原有方式:核心业务和服务方法都编写在一起
4.3.2 基于类的静态代理
基于类的静态代理是如何实现的?
在被代理类中编写核心业务,代理类要继承被代理类,在代理类中编写服务代码,并
通过super
关键字调用被代理类中核心业务代码。
弊端:一个代理类只能代理一个被代理对象(单继承)
被代理类(编写核心业务)
代理类(编写服务性代码)
4.3.3 基于接口的静态代理
基于接口的静态代理是如何实现的?
为核心业务类创建一个接口,代理类和被代理类都实现了同一个接口,通过接口暴露被代理的方法,在代理类中通过含参构造方法传进来被代理对象,调用被代理对象中方法完成核心业务。代理帮我们把非核心内容做了,被代理对象还是去干核心的任务。
一个代理类可以代理多个被代理对象,分别执行被代理对象中方法
定义接口声明核心业务方法
被代理类1
被代理类2
代理类
测试:一个代理类代理多个被代理对象
二级代理:将多个服务性业务与核心业务编织在一起
小明(被代理对象)会演戏,他找了经纪人(代理人1)负责他的演戏事宜,小明还想发展演唱业务,所以他的经纪人再去找能负责演唱业务的经纪人(代理人2),最后小明既可以进行演戏业务,又可以进行演唱业务。
多级代理,最后实现的功能越强大。
接口定义核心方法,通过接口暴露被代理的方法
被代理类
事务代理类
日志代理类
二级代理 测试:
二级代理对象中add方法的核心业务是通过一级代理对象中的add方法完成核心业务,而一级代理对象中的add方法的核心业务是通过被代理对象中的add方法完成的。
4.3.4 提取出切面代码,作为AOP接口
从上面可以看到,代理类中服务业务和核心业务还是混合在一起的,如何将服务业务和核心业务拆开?---提取出切面代码,作为AOP接口。
共有4个位置可以将切面代码编织进入核心业务代码中。
编写AOP切面接口
编写日志切面
编写事务切面
现在切面代码已经单独编写好了,要将切面代码编织到核心业务中,这里我们使用的是面向接口的静态代理,被代理类和代理类实现同一个接口,通过接口暴露你要代理的方法,在代理类中传入被代理对象和切面,在代理类中将切面代码和核心业务编织在一起。
接口
被代理类
代理类
测试:
二级代理:核心业务中既有日志切面又有事务切面,被代理对象被多个代理类层级代理
切面代码和核心业务代码都是分开单独编写,最后通过代理将它们编织在一起,代理的时候传进来代理对象和切面,然后选择合适的位置将切面切入核心业务中。
总结静态代理:
1
)可以做到在不修改目标对象的功能前提下,对目标对象功能扩展。
2
)缺点:
因为代理对象,需要与目标对象(被代理对象)实现一样的接口。所以会有很多代理类,类太多。
一旦接口增加方法,目标对象与代理对象都要维护。
4.4 动态代理
4.4.0.基于JDK/CGLIB的动态代理区别
静态代理:要求代理类存在(需要我们去编写代理类),
动态代理:程序运行的时候,根据要被代理的对象动态生成代理类(不需我们编写)。
类型:
1
、基于
JDK
的动态代理:
代理对象不需要我们去实现(代理)接口(动态生成代理类时已经指定了接口),但目标对象(被代理对象)需要实现(代理)接口,否则不能用JDK动态代理(被代理类和代理类必须实现同一个接口),如果想要功能扩展,但目标对象没有实现接口,怎样功能扩展?
JDK
的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有
实现接口的类,就可以使用
CGLIB
实现
2
、基于
CGLIB
的动态代理
Cglib
代理,也叫做子类代理。不需要实现代理接口,是在在内存中构建一个子类对象从而实现对目标对象功能的扩展。
CGLIB
是一个强大的高性能的代码生成包,它可以在运行期扩展
Java
类与实现
Java
接口。它广泛的被许多AOP
的框架使用,例如
Spring AOP
和
dynaop
,为他们提供方法的
interception
。
4.4.1
基于
JDK
的动态代理
就是jdk中给我们提供好的代理类Proxy,该类中提供静态newProxyInstance方法根据要被代理的对象动态生成代理类,生成代理对象,该方法创建代理对象需要指定3个参数:
1.类加载器:借助目标对象的类加载器,因为代理对象是动态生成的,现在没有
2.接口类对象的集合:针对接口的代理,针对哪个接口做代理,一般使用的就是被代理对象的接口。如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
3.句柄/回调函数,通过重写InvocationHandler接口中invoke方法编写代理规则,向核心业务中切入需要的切面,代理事务、日志等
4.4.1.1
直接编写测试类
被代理对象的接口
被代理类
基于JDK的动态代理类--
4.4.1.2 结构化设计--提取切面代码,作为AOP接口
方式一:
将动态代理中使用匿名内部类编写代理规则的部分抽出来编写在类中并实现InvocationHandler
然后提取切面代码,作为AOP接口,将服务性代码以切面的方式切入到核心业务中。
切面接口
事务切面
句柄/回调程序--编写代理规则(代理事务、日志等)
测试:
相对上面而言,我们获取代理对象需要编写的代码就减少了,我们可以再简化,创建一个工厂类专门来获取代理对象。
方式二: 工厂类继续简化
准备切面接口AOP(见上面):
准备切面(事务、日志、权限等略)
准备代理接口:通过接口暴露代理的方法
准备被代理类(核心业务)
准备获取动态代理对象的工厂类
测试:
4.4.2 基于CGLIB的动态代理
Cglib
代理,也叫做子类代理。在内存中构建一个子类对象从而实现对目标对象功能的扩展。
cglib动态代理和jdk动态代理相似,通过Enhancer类中静态方法create进行创建代理对象,在接口MethodInterceptor中重写intercept定义代理规则。
JDK
的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有
实现接口的类,就可以使用
CGLIB
实现。
CGLIB
是一个强大的高性能的代码生成包,它可以在运行期扩展
Java
类与实现
Java
接口。它广泛的被许多AOP
的框架使用,例如
Spring AOP
和
dynaop
,为他们提供方法的
intercept
。
CGLIB
包的底层是通过使用一个小而快的字节码处理框架
ASM
,来转换字节码并生成新的类。不鼓励直接使用ASM
,因为它要求你必须对
JVM
内部结构包括
class
文件的格式和指令集都很熟悉。
4.4.2.1
直接编写测试类
首先导入cglib依赖
被代理类(不需要实现接口)
cglib动态代理实现AOP
4.4.2.2
结构化设计方式--简化代码
切面接口AOP
事务切面
被代理类
代理工厂类(如果有多个目标对象,写个父接口来接收目标对象,这里只能接收NBAService及其子类的目标对象)
测试
4.5 Spring AOP
4.5.1.说说你对Spring AOP的理解
Spring AOP就是基于cglib动态代理实现的AOP框架。
Spring
的
AOP
实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强
我们先来介绍
AOP
的相关术语:
Target(
目标对象
)
要被增强的对象,一般是业务逻辑类的对象(就是被代理对象,如service)
Proxy
(代理)
一个类被
AOP
织入增强后,就产生一个结果代理类。(明星找经纪人,两人强强联合,就是代理类,相当于是编织了服务代码后的service)
表示增强的功能,就是一些代码完成的某个功能,非业务功能。是切入点和通知的结合。
所谓连接点是指那些被拦截到的点。在
Spring
中
,
这些点指的是方法(一般是类中的业务方法)
,
因为Spring只支持方法类型的连接点。(就是被拦截的要被切入切面的业务方法,一个service中业务方法有很多)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。(切入点定义切入的位置)
比如有10个service,每个service中有3个业务方法,这30个业务方法中要拦住哪些方法进行切入,这就是切入点。
被标记为
final
的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的
所谓通知是指拦截到
Joinpoint 之后所要做的事情就是通知。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。(就是拦截到业务方法后,通知切面切入到核心业务的时间,通知类型不同,切入时间不同)
通知的类型:前置通知
,
后置通知
,
异常通知
,
最终通知
,
环绕通知。
Weaving(
织入
).
是指把增强应用到目标对象来创建新的代理对象的过程。
spring
采用动态代理织入,而
AspectJ
采用编译期织入和类装载期织入
切面的三个关键因素:
1
、切面的功能
--
切面能干啥
2
、切面的执行位置
--
使用
Pointcut
表示切面执行的位置
3
、切面的执行时间
--
使用
Advice
表示时间,在目标方法之前还是之后执行。
4.5.2 AspectJ 对 AOP 的实现
对于
AOP
这种编程思想,很多框架都进行了实现。
Spring
就是其中之一,可以完成面向切面编程。 AspectJ 也实现了
AOP
的功能,且其实现方式更为简捷而且还支持注解式开发。所以,
Spring
又将 AspectJ 的对于
AOP
的实现也引入到了自己的框架中。
在
Spring
中使用
AOP
开发时,一般使用
AspectJ
的实现方 式
AspectJ
是一个优秀面向切面的框架,它扩展了
Java
语言,提供了强大的切面实现。
4.5.2 .1 AspectJ
的通知类型
AspectJ
中常用的通知有
5种类型:通知定义切入的时间
1.
前置通知
2.
后置通知
3.
环绕通知
4.
异常通知
5.
最终通知
AspectJ
定义了专门的表达式用于指定切入点。
execution(
访问权限
方法返回值
方法声明
(
参数
)
异常类型
)
切入点表达式要匹配的对象就是目标方法的方法名。所以,
execution
表达式中就是方法的签名。
PS:
表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
4.5.3 Spring中通过注解方式实现AOP
运行阶段:
spring
框架会在运行的时候将核心业务和
AOP
代码通过动态代理的方式编织在一起
代理方式的选择:是否实现了接口:有接口就选择
JDK
动态代理;没有就选择
CGLIB
动态代理。
通过注解实现的AOP,所有东西都在底层封装好了,这个底层就是上面的动态代理,动态代理想要理解还要去理解静态代理。
spring如何将核心业务和切面代码编织起来,通知spring哪些是核心业务类,哪些是切面类,哪些切面要切入到哪些业务方法中的哪些位置?
在定义好切面
Aspect 切面
后,需要通知
Spring
容器,让容器生成
“
目标类
+
切面
”
的代理对象。这个代理是由容器自动生成的。只需要在 Spring
配置文件中注册一个基于
aspectj
的自动代理生成器,其就会自动扫描到@Aspect
注解找到切面类,并按通知类型与切入点,将其织入,并生成代理。
1
、创建项目引入依赖
引入spring-context spring-AspectJ依赖
单元测试
maven编译插件(默认1.7)
2. 创建配置文件,在<beans>标签中引入aop和context约束
3.创建核心业务类(目标对象实现了接口)
3.1.编写代理接口,通过接口暴露要代理的方法
3.2.编写被代理类,编写核心业务
4.编写AOP切面内容
4.1.编写切面接口(可以不用接口,直接用类,不需5个方法都写)
4.2.编写事务切面类
前置通知
后置通知
环绕通知
异常通知
最终通知
至此,核心业务和AOP已经编写完成。
spring如何将核心业务和切面代码编织起来,通知spring哪些是核心业务类,哪些是切面类,哪些切面要切入到哪些业务方法中的哪些位置?
在定义好切面
Aspect
后,需要通知
Spring
容器,让容器生成
“
目标类
+
切面
”
的代理对象。这个代理是由容器自动生成的。只需要在 Spring
配置文件中注册一个基于
aspectj
的自动代理生成器,其就会自动扫描到@Aspect
注解找到切面类,并按通知类型与切入点,将其织入,并生成代理。
5
、
spring.xml
配置文件中开启包扫描和 注册
aspectj
的自动代理
aop:aspectj-autoproxy
的底层是由
AnnotationAwareAspectJAutoProxyCreator
实现的
, 是基于 AspectJ
的注解适配自动代理生成器。
其工作原理是,
aop:aspectj-autoproxy
通过扫描找到
@Aspect
定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
测试:
对上述切面类中切入点表达式进行简化,给方法标注@Pointcut注解用方法表示切入点表达式,这样当修改切入点表达式时就可以统一修改一次就可以
例如:
4.5.4 XML方式实现AOP
对象还是容器创建的,还是使用AspectJ自动代理(底层是cglib动态代理),@AspectJ还是要去标识切面类,配置文件中在config标签下通过aop:pointcut标签声明切入点表达式,在aop:aspect标签中声明切面、切面方法以及其对应的通知类型和切入点
5、Spring整合JDBC
5.1 使用spring-jdbc操作数据库
主要内容
:
学习使用JdbcTemplate API和 如何使用Spring管理 JdbcTemplate
5.2. Spring管理JdbcTemplate
spring
整合
jdbc
的时候我们都是直接让
dao
继承
Spring
提供的
JdbcDaoSupport类,该类中提供了 JdbcTemplate模板可用。JdbcDaoSupport类中JdbcTemplate没有值,我们需要去创建一个JdbcTemplate对象,然后创建一个数据源注入到JdbcTempate中,再将JdbcTemplate对象注入到dao中使用,这些对象的创建和依赖注入都是有spring管理的,需要在配置文件中通bean标签声明对象。
spring
的配置文件
application.xml
中需要创建数据源和给
TeamDao
中的
jdbcTemplate
赋值
测试
6、Spring事务管理
事务原本是数据库中的概念,在
Dao
层。但在实际开发中,一般将事务提升到业务层,即
Service
层。这样做是为了能够使用事务的特性来管理具体的业务
6.1 Spring事务管理API
Spring
的事务管理,主要用到两个事务相关的接口。
6.1.1 事务管理器接口
事务管理器是
PlatformTransactionManager
接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
PlatformTransactionManager
接口常用的实现类
:
DataSourceTransactionManager
:使用
JDBC
或
MyBatis
进行数据库操作时使用。
Spring
的回滚方式
Spring
事务的默认回滚方式是:发生运行时异常和
error
时回滚,发生受查
(
编译
)
异常时提交。不过, 对于受查异常,程序员也可以手工设置其回滚方式。
6.1.2 事务定义接口
事务定义接口
TransactionDefinition
中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作
6.1.2.1
事务隔离级别常量
这些常量均是以
isolation_开头。即形如isolation_xxx。
➢
default
:采用
DB
默认的事务隔离级别。
MySql 默认为repeatable_read ;
Oracle
默认为read_committed 。
➢
read_uncommitted
:读未提交。未解决任何并发问题。
➢
read_committed
:读已提交。解决脏读,存在不可重复读与幻读。
➢
repeatable_read
:可重复读。解决脏读、不可重复读,存在幻读
➢
serializable
:串行化。不存在并发问题
6.1.2.2
事务传播行为常量
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,
A
事务中的方法 doSome()
调用
B
事务中的方法
doOther()
,在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
事务传播行为常量都是以
propagation_ 开头,形如propagation_XXX。[prɒpə'ɡeɪʃ(ə)n]
Propagation.required
当前没有事务时,就创建一个新事务;如果当前有事务,就直接加入该事务,比较常用设置
Propagation.supports
支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就以非事务方式执行
Propagation.mandatory [ˈmændətəri]强制的
支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就抛出异常
Propagation.requires_new
创建新事务,无论当前是否有事务都会创建新的
PROPAGATION_nested
PROPAGATION_never
PROPAGATION_not_support
6.1.2.3
默认事务超时时限
常量
timeout_default
定义了事务底层默认的超时时限,
sql
语句的执行时长。
注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。(在sql执行时间很长的时候使用)
6.2 声明式事务控制
Spring
提供的对事务的管理,就叫做声明式事务管理。
如果用户需要使用
spring
的声明式事务管理,在配置文件中配置即可:不想使用的时候直接移除配置。 这种方式实现了对事务控制的最大程度的解耦。
声明式事务管理,核心实现就是基于
AOP,以切面方式加入事务
。(AOP核心实现是动态代理)
Spring
中提供了对事务的管理。开发者只需要按照
spring
的方式去做就行。
事务必须在service层统一控制。
事务的粗细粒度:
细粒度:对方法中的某几行的代码进行开启提交回滚;
粗粒度:对整个方法进行开启提交回滚;
Spring
中的
aop
只能对方法进行拦截,所有我们也只能对方法进行事务的控制。
增删改是一定要开启事务的,查询的话,
如果只有单条的查询语句,可以省略事务;如果一次执行的是多条查询语句,例如统计结果、报表查询。必须
开启事务
6.3 基于注解的事务
1.在配置文件中添加tx约束,然后通过DataSourceTransactionManager类创建事务管理器transactionManager,并向其注入数据源dataSource, 然后通过annotation-driven 标签去启动事务的注解。
2.在方法上添加事务注解@Transactional,该注解中常用属性
readOnly:
是否只读
rollbackFor={Exception.class}
: 遇到什么异常会回滚
propagation事务的传播,常用
Propagation.required
isolation=Isolation.DEFAULT
:事务的隔离级别:默认是数据库的隔离级别
基于注解的事务
在xml中添加tx约束,启动事务注解
在方法上添加Transactional注解
dao 中insert
测试
6.4 基于XML的事务
添加依赖
xml中添加aop约束和tx约束、声明事务通知、切面方式切入