# Spring-Notes
**Repository Path**: tonited/Spring-Notes
## Basic Information
- **Project Name**: Spring-Notes
- **Description**: 在imooc上看Spring入门网课时的笔记
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2020-05-06
- **Last Updated**: 2022-03-15
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
---
titile: Spring入门笔记
author: Tonited
picture-resources: imooc
IDE: IDEA2019.2
java-version: 11.0.3
date: 2019.08.15
---
# Spring入门笔记
*Tonited*
# 前言
picture-resources: imooc
IDE: IDEA2019.2
java-version: 11.0.3
date: 2019.08.15
这篇笔记是本人零基础看Spring入门网课时整理的笔记,同时加入了一些自己的理解,方便大家学习和使用,内容可能存在错误或者在理解上有一定问题,如果有任何问题、意见、见解或关于侵权请联系我,QQ710260712,申请理由请写“Spring入门笔记勘误”。
——Tonited
# 一. 概述
## 1. Spring的概述
### (1)什么是Spring
- Spring是一个开源框架
- Spring为简化企业级应用开发而生,使用Spring可以使简单的JavaBean实现以前只有EJB才能实现的功能
- Spring是JavaSE/EE的一站式框架
- 方便解耦,简化开发
- Spring就是一个大工厂,可以将所有对象创建和依赖关系维护交给Spring管理
- AOP编程的支持
- Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
- 声明事务的支持
- 只通过配置就可以完成对事务的管理,而无需手动编程
### (2)Spring的优点
- 方便程序的测试
- Spring对Junit4支持,可以通过注解方便的测试Spring程序
- 方便集成各种优秀框架
- Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis等)的直接支持
- 降低JavaEE API的使用难度
- Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用程度大大降低
## 2. Spring IOC快速入门案例
### 1)配置流程
#### 一般配置
1. 下载Spring最新开发包
2. 复制Spring开发jar到工程
3. 理解IOC控制反转和DI依赖注入
4. 编写Spring核心配置文件
5. 在程序中读取Spring配置文件,通过Spring框架获得Bean,完成相应操作
6. 官方下载Spring 4.X 最新开发版本
7. Spring 4.2 版本目录结构

8. 导入Spring核心开发包到创建工程

#### Maven pom依赖
- 使用maven创建spring项目时需要在pom中引入以上依赖
- 还要引入单元测试依赖

- **本笔记所记录的示例均为使用maven方式创建和管理spring项目,以下记录均为maven记录**
### 2)HelloTest类中使用UserServive类对象
#### · 传统方式

#### · Spring方式实现

#### · 读取磁盘中的配置文件
### 3)IOC(Inverse of Control 控制反转)的概念
- 就是将原本在程序中手动创建UserService对象控制权,交由Spring框架管理
- 简单说,就是创建UserService对象控制权被反转到了Spring框架
## 3. 提问
- 关于IOC说法
- 控制反转
- SpringIOC底层用那种设计模式完成的
- 工厂模式
- 反射模式
- Spring中依赖注入的目的
- 在代码之外管理组件之间的依赖关系
# 二. Spring Bean管理
## 1. Spring的工厂类

- 工厂类

- 传统方式工厂类:BeanFactory

## 2. Spring的Bean管理(XML方式)
### 1) 三种实例化Bean的方式
#### · ApplicationContext——XML配置

#### · 使用类构造器实例化(默认无参数)
ApplicationContext在创造时调用类的构造函数

#### · 使用静态工厂方法实例化(简单工厂模式)
创建工厂类,ApplicationContext在创造时通过工厂生成Bean
**这个Bean由静态方法创建,只会创造一个Bean,获取时获取的是同一个Bean**

#### · 使用实例工厂方法实例化(工厂方法模式)
与上图简单工厂模式相同,区别是工厂方法创建的Bean不是单例

### 2)Bean的配置

- id和name
- 一般情况下,装配一个Bean时,通过指定一个id属性作为Bean的名称
- id属性在IOC容器中必须是唯一的,name可以不唯一
- (id和name的作用相同)
- 如果Bean的名称中含有特殊字符,就需要使用name属性
- calss
- class用于设置一个类的完全路径名称,主要作用时IOC容器生成类的实例
### 3) Bean的作用域
| 类别 | 说明 |
| :-------: | :----------------------------------------------------------: |
| singleton | 在SpringIOC容器中仅存在一个Bean实例,Bean一单实例的方式存在(单例模式) |
| prototype | 每次调用getBean()时都会返回一个新的实例(既非单例模式) |
| request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适于WebApplicationContext环境 |
| session | 同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean。该作用域仅适于WebApplicationContext环境 |
### 4)Spring中Bean的生命周期
#### (1. Bean创造拆卸生命周期
Spring初始化Bean或销毁Bean时,有时需要作一些处理工作,因此spring可以在创建和拆卸Bean的时候调用Bean的两个生命周期方法

注意:destory只有在scope=singleton时有效
#### (2. Bean完整11个生命周期
顺序:
1. instantiate (bean对象实例化
- **调用构造函数**


2. populate properties (封装属性
- **根据AccountContext.xml配置文件封装属性**



3. 如果Bean实现BeanNameAware接口,执行接口的**setBeanName**方法
- **告诉被创造的Bean自己在ApplicationContext.xml中的名字(id/name)**



4. 如果Bean实现BeanFactoryAware或者ApplicationContextAware,执行 设置工厂**setBeanFactory**方法 或者上 下文对象**setApplicationContext**方法
- **使被创建的Bean了解工厂信息**


5. 如果**存在类**实现 BeanPostProcessor 接口(Bean后处理器),执行**postProcessBeforeInitialization **方法
- **实现Bean后处理器接口的类并不是被创建的Bean**
- **该生命周期会在每个Bean实例化都会被调用**


6. 如果Bean实现 InitalizingBean 接口,执行 **afterPropertiesSet**方法


7. 调用 指定初始化方法 **init**



8. 如果存在类实现 BeanPostProcessor接口 (Bean后处理器),执行 **postProcessAfterInitialization** 方法
- 与第5步相同,为Bean后处理器
- 实现Bean后处理器接口的类并不是被创建的Bean
- 该生命周期会在每个Bean实例化都会被调用


9. **执行业务逻辑**



10. 如果 Bean 实现 DisposableBean 执行 destroy
- 关闭才会执行

11. 调用 指定的销毁方法 **customerDestory**


#### (3. beanpostprocessor的作用(Java动态代理示例)
- 可以在创造Bean时产生代理
- 可以对Bean的方法增强
- 下面的例子使用了Java的动态代理

## 3. Spring的几种属性注入
- 对于类成员变量,注入方式有三种
- **构造函数注入**
- **属性setter方法注入**
- 接口注入
- **Spring支持前两种**
### 1)Spring属性注入-构造方法注入
- 通过构造方法注入Bean的属性值或依赖的对象,它保证了Bean实例在实例化之后就可以使用
- 构造器注入在元素里生命的属性


### 2)Spring属性注入-set方法注入
- 在Spring配置文件ApplicationContext.xml中,通过设置注入的属性


### 3)Spring属性注入-p名称空间
- 为了简化XML文件配置,Spring从2.5开始引入一个新的p名称空间
- 要在中加入
```xml
xmlns:p="http://www.springframework.org/schema/p"
```
- p:<属性名>="XXX"引入常量值
- p:<属性名>-ref="XXX"引用其他Bean对象
- **需要set**


### 4)Spring属性注入-SpEL注入
- SpEL:spring expression language,spring表达式语言,对依赖注入进行简化
- **不需要set**
- 语法:#{表达式}
-
- #{}
- #{ 'hello' } :使用字符串
- #{topicId3}:使用另一个bean
- #{topic4.content.toUpperCase()}:使用指定名属性,并使用方法
- {T(java.lang.Math).PI}:使用静态字段或方法

### 5)Spring属性注入-复杂类型的属性注入
- 写在之间
- 数组类型的属性注入

- List集合类型的属性注入

- Set集合类型的属性注入

- Map集合类型的属性注入

- Properties类型的属性注入

## 4. Spring的Bean管理(注解方式)
### 0)必要配置
- 使用注解时
- 想使用注解要引入依赖**spring-aop **
- 替换ApplicationContext.xml头为
```xml
```
- **开启注解扫描**
- 这将自动开启属性注入
```xml
```
### 1)使用注解定义Bean
- Spring2.5引入使用注解去定义Bean
- @Component 描述Spring框架中的Bean

- 除了@Component外,Spring提供了3个功能基本和@Component等效的注解
- @Repository 用于对DAO实现类进行标注
- @Service 用于对Service实现类进行标注
- @Controller 用于对Controller实现类进行标注
- 这三个注解是为了让标注类本身的用途清晰,Spring在后续会对其增强
- @Value可以直接注入属性
- 如果属性提供了get,@Value就要写在set上
- 如果没提供get,@Value就要写在属性上

### 2)Spring的属性注入-注解方式
- 使用@Autowired进行自动注入
- **@Autowired按照默认类型进行注入**
- **如果存在两个Bean类型相同,则按照名称注入**
- **注入bean类型和被注入bean类型必须都被Spring管理(上面被注解@Component或另外三个)**(图中粉色框)
- **按照名称注入指的是:被注入的变量的名称(图1红色框)和注入类的注解命名一致**(图2红色框)


**@Autowired注入针对成员变量或者set方法都可以**
- 通过@Autowired的required属性,设置一定要找到匹配的Bean
- @Qualifier指定注入Bean的名称,即实现了required属性

- **使用@Qualifier 指向Bean名称时,Bean必须指定相同名称**

- Spring提供对 JSR-250中定义@Resource标准注解的支持
- @Resource相当于@Autowried和@Qualifier的组合形式
- 想要使用@Resource要引入依赖
```xml
javax.annotation
jsr250-api
1.0
```

### 3) Spring的其他注解
- Spring初始化bean或销毁bean时,有时需要做一些处理工作,因此Spring可以在创建和拆卸bean的时候调用bean的两个生命周期方法


### 4)Bean的作用范围
- @Scope注解用于指定Bean的作用范围
- 使用注解配置的Bean和配置的一样,默认作用范围都是singleton

## 5. 传统XML配置和注解配置的混合使用
- XML方式的优势
- 结构清晰,易于阅读
- 注解方式的优势
- 开发敏捷,属性注入方便
- XML与注解的整合开发
- 引入context命名空间:修改ApplicationContext.xml文件为
```xml
```
- 开启属性注入
- 在配置文件中添加
```xml
```
- 组合开发
- ApplicationContext.xml中只需配置的 id 和 class ,不需要配置属性了

- 属性通过注解注入,不需要set方法了

- 因为在ApplicationContext.xml中已经配置了bean的id,所以不需要每个类上面用@Component等标注id了


## 6. 提问
- 在Spring中,加载类路径下的配置文件使用的是
- ApplicationContext
- 在Spring中,已知Student类的定义,下方式属于无参构造器的方式来实例化Bean
-
- 在Spring中,Bean在配置时,初始化和销毁方法配置在 中的标签名是
- init-method
- destory-method
- 在Spring中,假设某个bean要使用某种类型的资源,那么一般情况下,应该把资源的释放放到bean生命周期中的阶段为
- 销毁阶段
- 关于p名称空间的说法
- 形如 p:age="18" 引入常量值
- 形如 p:test-ref="test" 引入Bean对象
- 以下SpEL表达式语法使用错误
- 语法:${表达式}
- 在SpringBean注入中,如果需要对Map进行数据注入,需要用到以下标签
- property
- map
- entry
- Spring2.5以后引入了注解定义Bean,那么定义Bean可以使用下列注解
- @Repository
- 在Spring的Bean注入中,如果使用注解的方式对属性进行注入,则有以下说法
- 类中如果有setter方法,那么注解需要加到setter方法上方
# 三. Spring AOP
## 1. AOP的概述
### 0)纵向继承和AOP的横向抽取机制
假设现在我们有以下实现了一个名为UserDao接口的类,我们想在save()方法被调用前做权限校验,可以在类中写一个用于权限校验的方法checkPrivilege(),并在save()的逻辑执行前调用这个类的checkPrivilege()方法。

如果现在我们有100实现了UserDao接口的类,想在每一个类中的某个方法被调用之前都进行一步权限调用,就需要给100个类中的每一个类都写一个checkPrivilege()方法。
这时你可能想到:要将权限校验的方法checkPrivilege()写在接口中,让每一个继承它的类都实现这一方法。
这种做法就是所谓的**纵向继承**,”纵向“指的是从接口继承方法这种上下父子关系
可是你也能很容易的发现,这样写的代码量很大,最重要的是:会出现大量重复代码
为了解决这一问题,出现了AOP,关于AOP的具体概念稍后会具体讲解。
AOP采用的是**横向抽取机制**
- 所谓的横向抽取机制是指将可以复用的代码抽取出来以便复用
- AOP横向抽取机制采用的是代理机制,可以通过访问代理达到访问目标对象的目的,同时通过执行代理中的复用代码段解决代码服用问题

### 1)什么是AOP
- AOP Aspect Oriented Programing 面向切面编程
- AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能检测、事务管理、安全检查、缓存)
- Spring AOP底层使用纯Java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类织入增强代码
### 2)AOP的相关术语

- Joinpoint(连接点):所谓连接点是指那些可以被拦截到的点。在Spring中,这些点指的是方法,**因为Spring只支持方法类型的连接点**
- Pointcut(切入点):所谓切入点是指我们要对哪些Jointpoint进行拦截的定义
- Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做到的事情就是通知
- 通知分为:前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
- Introduction(引介):引介是一种特殊的通知,在不修改代码的前提下,Introduction可以在运行期为类动态地添加一些方法或者Field
- **Spring并不支持,可先不考虑**
- Targrt(目标对象):代理的目标**对象**
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程
- Spring采用动态代理织入
- **AspectJ(一种织入工具,后面会讲到)采用编译器织入和类装载器织入的方式**
- Proxy(代理):一个类被AOP织入增强后,就会产生一个结果代理类
- Aspect(切面):切入点和通知(引介)的结合
## 2. AOP的底层实现
### 1)JDK动态代理
#### (1 使用方法
- **JDK的动态代理只能对实现了接口的类进行代理**
- 首先我们要创建一个代理对象,用于稍后传给 InvocationHandler 的 invoke() 函数,创建对象使用 Proxy.newProxyInstant 函数,它需要三个参数
- 被代理类的类加载器
- 被代理类所实现的接口
- 执行代理的类,这个类需要实现 InvocationHandler 接口的 invoke() 方法

- 本例让此代理类实现了此接口,同时作为代理类和执行代理类,所以这时参数为 this
```java
public Object createProxy(){
Object proxy = Proxy.newProxyInstance(
userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(),
this
);
return proxy;
}
```
- InvocationHandler接口的 invoke(Object proxy, Method method, Object[] args)
- 它包含三个参数
- Object proxy:代理对象,刚刚已创建过
- Method method:被执行的方法
- Object[] args:方法所带的参数
```java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {}
```
- 返回值需要返回“方法的执行:method.invoke()”, 它包含两个参数
- 被代理的类实例
- - invoke参数列表中的args(args内部是method方法执行时的参数)
```java
return method.invoke(userDao,args);
```
#### (2. 一个JDK动态代理完整代码示例
- 代理类
```java
public class MyJdkProxy implements InvocationHandler{
// UserDao是接口
private UserDao userDao;
public MyJdkProxy(UserDao userDao){
this.userDao = userDao;
}
public Object createProxy(){
Object proxy = Proxy.newProxyInstance(
userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(),
this);
return proxy;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("save".equals(method.getName())){
System.out.println("权限校验..");
return method.invoke(userDao,args);
}
return method.invoke(userDao,args);
}
}
```
- 使用
```java
public class SpringDemo1 {
public void demo1(){
// UserDaoImpl是实现UserDao接口的类
UserDao userDao = new UserDaoImpl();
UserDao proxy = (UserDao)new MyJdkProxy(userDao).createProxy();
proxy.save(); //UserDao中的方法
}
}
```
### 2)使用CGLIB生成代理
#### (1. CGLIB原理
- **对于不使用接口的业务类,无法使用JDK动态代理**
- CGlib采用非常底层的字节码技术,可以为一个类创建子类,解决无接口代理问题
#### (2. 使用
- 首先创造生成代理函数,经历以下四步
1. 创建核心类:创建Enhancer核心类,接下来的三步设置都是对这个核心类操作
```java
//1. 设置核心类
Enhancer enhancer = new Enhancer();
```
2. 设置被代理类:用 Enhancer 的 setSuperClass() 方法告诉enhancer所要代理的类,参数为被代理类
- 本质上是设置父类,因为CGlib生成的代理实际上是继承了这个类
```java
// 2,设置父类
enhancer.setSuperclass(productDao.getClass());
```
3. 设置回调:用 Enhancer 的 setCallBack() 设置代理执行类,这个类要实现 MethodInterceptor 接口,参数为代理执行类
- 本例中的代理类实现了 MethodInterceptor 接口,既是代理类也是代理执行类,所以这里的参数为 this
```java
// 3.设置回调
enhancer.setCallback(this);
```
4. 生成代理,前三步完成了对代理的设置,现在使用 Enhancer 的 create() 方法创建代理并将代理返回
```java
// 4.生成代理
Object proxy = enhancer.create();
return proxy;
```
- 创造生成代理函数完整代码示例
```java
public Object createProxy(){
// 1.创建核心类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(productDao.getClass());
// 3.设置回调
enhancer.setCallback(this);
// 4.生成代理
Object proxy = enhancer.create();
return proxy;
}
```
- MethodInterceptor接口的intercept() 方法
- 需要四个参数
- Object proxy:代理对象,刚刚已创建过
- Method method:被执行的方法,**获取方法信息时使用**
- Object[] args:方法所带的参数
- MethodProxy methodProxy:方法代理类,**执行时使用**
- 返回值需要返回“方法代理执行父类:methodProxy.invokeSuper()”, 它包含两个参数
- 参数列表中的代理
- 参数列表中参数数组
- intercept完整代码示例
```java
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if("save".equals(method.getName())){
System.out.println("权限校验===================");
return methodProxy.invokeSuper(proxy,args);
}
return methodProxy.invokeSuper(proxy,args);
}
```
#### (3. CGLIB代理完整示例
- 代理类
```java
public class MyCglibProxy implements MethodInterceptor{
private ProductDao productDao;
public MyCglibProxy(ProductDao productDao){
this.productDao = productDao;
}
public Object createProxy(){
// 1.创建核心类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(productDao.getClass());
// 3.设置回调
enhancer.setCallback(this);
// 4.生成代理
Object proxy = enhancer.create();
return proxy;
}
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if("save".equals(method.getName())){
System.out.println("权限校验===================");
return methodProxy.invokeSuper(proxy,args);
}
return methodProxy.invokeSuper(proxy,args);
}
}
```
- 使用
```java
public class SpringDemo2 {
public void demo1(){
ProductDao productDao = new ProductDao();
ProductDao proxy = (ProductDao) new MyCglibProxy(productDao).createProxy();
proxy.save();
proxy.update();
proxy.delete();
proxy.find();
}
}
```
### 3) 代理知识总结
- **Spring在运行期,生成动态代理对象,不需要特殊的编译器**
- **Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术为目标Bean执行横向织入**
- **若目标对象实现了若干接口,Spring使用JDK的java.lang.reflect.Proxy类代理**
- **若目标对象没有实现任何接口,spring使用CGlib库生成目标对象的子类**
- **程序中应优先对接口创建代理,便于程序解耦维护**
- **标记为final的方法,不能被代理,因为无法进行覆盖**
- **JDK动态代理,是针对接口生成子类,接口中方法不能使用final修饰**
- **CGLib是针对目标类生成子类,因此类或方法不能使用final**
- **Spring只支持方法连接点,不提供属性连接**
## 3. Spring的传统AOP
### 1)Spring AOP 增强类型
- AOP不是Spring特有的,AOP联盟提出AOP,为通知Advice定义了Advice——org.aopalliance.aop.Interface.Advice
- 增强类型也称通知类型
- Spring按照“通知Advice在目标类方法的连接点位置”来分为五类
- 前置通知 org.springframework.aop.MethodBeforeAdvice
- 在目标方法执行前实施增强
```java
public class MyBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置增强======================");
}
}
```
- 后置通知 org.springframework.aop.AfterReturningAdvice
- 在目标方法执行后实施增强
- 环绕通知 org.aopalliance.intercept.MethodInterceptor
- 在目标方法执行前后实施增强
```java
public class MyAroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕前增强===================");
Object obj = invocation.proceed();
System.out.println("环绕后增强===================");
return obj;
}
}
```
- 异常抛出通知 org.springframework.aop.ThrowsAdvices
- 在方法抛出异常之后实施增强
- 引介通知 org.springframework.aop.IntroductionInterceptor
- 在目标类中添加一些新的方法和属性、
- **增强类要实现这些接口**
### 2)Spring AOP 切面类型
- Advisor:代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截
- PointcutAdvisor:代表具有切点的切面,可以指定拦截目标类哪些方法
- IntroductionAdvisor:代表引介切面,针对引介同通知而使用切面(不要求掌握)
### 3)切面案例
#### (0. 引入依赖
- Aop联盟:aoppalliance

- Spring AOP:spring-aop

#### (1. Advisor切面案例
- 配置目标类
- 配置切面
- 当通知类中方法对目标类的全部方法拦截时,通知类就是切面(不带有切入点的切面)
- 否则就要单独配置切面(带有切入点的切面)
- 这时的切面其实就是一个只有id/name和class的Bean
- class必须是org.springframework.aop.support.RegexpMethodPointcutAdvisor
- 属性pattern:使用正则限制切入点
- **注意:切入点的限制在切面配置中,而不是在产生代理ProxyFactoryBean中**
```xml
```
- 属性advice:所需要使用的切面
```xml
```
- 配置产生代理——ProxyFactoryBean
- 产生代理ProxyFactoryBean也是一个只有id/name和class的类
- class为 org.springframework.aop.framework.ProxyFactoryBean
- 常用属性
- target:代理的目标对象
- proxyInterfaces:代理实际要实现的接口
- 如果多个接口使用以下格式
```xml
..
```
- proxyTargetClass:是否对类代理而不是接口,即是否使用CGLib代理,值为true/false
- interceptorName:需要织入目标的Advice
- 当通知类中方法对目标类的全部方法拦截时,通知类就是切面(不带有切入点的切面)
- 否则就要单独配置切面(带有切入点的切面),并且interceptor是切面这个单独配置的切面
- singleton:返回代理是否为单例,默认为单例
- optimize:设置为true时,强制使用CGLib
- Advisor切面完整配置示例
```xml
```
#### (2. 自动创建代理
- 前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中,非常多的Bean每个都配置ProxyFactory开发维护量巨大
- **解决方案:自动创建代理**
- BeanNameAutoProxyCreator 根据Bean名称创建代理
- DefaultAdvisorAutoProxyCreator 根据Advisor本身包含信息创建代理
- **AnnotationAwareAspectJAutoProxyCreator 基于 Bean 中的 AspectJ注解进行自动代理**
- 自动创建代理是ProxyFactory的替换
- (切入点的限制在切面配置中,而不是在产生代理ProxyFactoryBean中)
- BeanNameAutoProxyCreator存在属性beanNames,其value用来配置被代理Bean的名称,支持正则,,与ProxyFactory用法上大致相同,有些许不同
- DefaultAdvisorAutoProxyCreator 会自动扫描切面和Bean,并根据切面的pattern进行代理
##### 1)BeanNameAutoProxyCreator 示例
- 对所有以DAO结尾Bean的所有方法使用代理
```xml
```
- 测试使用
```java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class SpringDemo5 {
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(name="customerDao")
private CustomerDao customerDao;
@Test
public void demo1(){
studentDao.find();
studentDao.save();
customerDao.find();
customerDao.save();
}
}
```
##### 2) DefaultAdvisorAutoProxyCreator 示例
- 配置环绕代理案例
```xml
```
- 测试使用
```java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext4.xml")
public class SpringDemo6 {
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(name="customerDao")
private CustomerDao customerDao;
@Test
public void demo1(){
studentDao.find();
studentDao.save();
customerDao.find();
customerDao.save();
}
}
```
## 4. 问题
- 关于Spring AOP的说法
- AOP是 Aspect Oriented Programming的英文缩写
- AOP编程技术可以完成的功能:性能监视、事务管理以及安全检查、缓存
- 关于Spring AOP术语的说法
- Joinpoint指的是可以被拦截的点,例如增删改查这些方法
- 如果只想对update()方法做增强,那么update()方法就是Pointcut
- 在Spring AOP中,关于Introduction(引介)的说法
- 引介是一种特殊的通知,在不修改类代码的前提下,引介可以在**运行期**为类动态的添加一些方法或Field
- 在Spring AOP中,以下关于Target(目标对象)和Weaving(织入)的说法**错误**
- AspectJ 采用动态代理织入
- Spring采用编译器织入和类装载器织入的方式
- 以下关于SpringAOP增强类型说法错误
- SpringAOP按照通知Advice在目标类方法的连接点位置,可以分为4种通知类型,分别为前置通知、后置通知、环绕通知、引介通知
- 关于SpringAOP中对Advisor切面中属性有以下描述
- interceptorNames:需要织入目标的Advice
- optimize:设置为true时,强制使用CGLib
# 四. 基于AspectJ的AOP开发
## 1. AspectJ 简介
- AspectJ 是一个基于Java语言的AOP框架
- Spring2.0 以后新增了对AspectJ切点表达式的支持
- 注意:切点表达式不是AspectJ特有的,而是Spring支持的
- @AspectJ 是 AspectJ1.5 新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面
- 新版本Spring框架建议使用 AspectJ 方式开发AOP
- 使用AspectJ 需要导入Spring AOP 和 AspectJ 相关jar包(依赖)
- **spring-aop**
- (com.springsource.org.**aopalliance**)
- (**spring-aspects**)
- com.springsource.org.aspectj.weaver (**aspectjweaver**)
## 2. 基于AspectJ的注解AOP开发
### 1)环境准备
- 配置Spring配置文件:applicationContext.xml
- 开启 AspectJ 自动代理
```xml
```
### 2) @AspectJ提供的通知类型
- @Before 前置通知,相当于 BeforeAdvice
- @AfterReturning 后置通知,相当于 AfterReturningAdvice
- @Around 环绕通知,相当于 MethodInterceptor
- @AfterThrowing 异常抛出通知,相当于ThrowAdvice
- @After 最终final通知,不管是否异常,该通知都会执行
- @DeclareParents 引介通知,相当于IntroductionInterceptor(不要求掌握)
### 3)切点表达式——通知中通过value属性定义切点
- 通过execution函数,可以定义切点的方法切入
- 语法
- “ * ”表示任意字符
- “ . ” 表示任意参数
```xml
execution( [<访问修饰符>] <返回类型> <方法名>([<参数>]) <异常> )
```
- 例如
```xml
- 匹配所有类的public方法
execution(public * *(.))
- 匹配指定包下所有类方法(不包含子包)(第一个*是返回类型)
execution (* com.dao.*(.))
- 匹配指定包下所有类方法(包含包、子包下的所有类)(在包名后变为两个点即可)
execution(* com.dao.*(.))
- 匹配指定类所有方法
execution(* com.service.UserService.*(.))
- 匹配实现特定接口所有类方法
execution(* com.dao.GenericDAO+.*(.))
- 匹配所有save开头的方法
execution(* save*(.))
```
### 4)使用方法示例
- 使用@Aspect声明一个类为切面类
```java
@Aspect
public class MyAspectAnno {}
```
- 配置文件配置目标类,配置切面
```xml
```
- 通知中通过value属性定义切点
```java
@Aspect
public class MyAspectAnno
{
@Before(value="execution(* com.aspectJ.demo1.ProductDao.save(.))")
public void before(JoinPoint joinPoint){
System.out.println("前置通知");
}
}
```
### 5) 注解通知类型使用
- @Before 前置通知
- 可以在方法中传入JoinPoint对象,用来获得切点信息
```java
//要增强的代码
@Before(value="execution(* com.aspectJ.demo1.ProductDao.save(.))")
//通知
public void before(JoinPoint joinPoint){
System.out.println("前置通知");
}
```
- @AfterReturning 后置通知
- @AfterReturning 存在参数returning,值为字符串,作用是把切入点的返回值注入变量,变量名就是returning的值,这个变量作为通知函数的参数
```java
@AfterRuning(
value="execution(* com.aspectJ.demo1.ProductDao.save(.))",
returning="resu"
)
public void afterReturning(Object resu){
System.out.println("后置通知");
}
```
- @Around 环绕通知
- Around方法的返回值就是目标代理方法执行返回值
- 参数为ProceedingJoinPoint,是目标方法
- **要手动调用ProceedingJoinPoint的proceed()方法,才会执行目标方法,否则目标方法的执行就会被拦截**
```java
@Around("execution(* com.aspectJ.demo1.ProductDao.save(.))")
public Object around( ProceedingJoinPoint joinPoint )
{
System.out.println("==前环绕通知==");
Object obj = joinPoint.proceed();
System.out.println("==后环绕通知==");
return obj;
}
```
- @AfterThrowing 异常抛出通知
- 通过设置throwing属性,可以设置发生异常对象参数
```java
@AfterThrowing(
value="execution(* com.aspectJ.demo1.ProductDao.save(.))",
throwing = "e"
)
public void afterThrowing(Throwable e){
System.out.println("异常抛出通知=============="+e.getMessage());
}
```
- @After 最终通知
- 无论是否出现异常,最终通知总是会被执行
```java
@After(value="execution(* com.aspectJ.demo1.ProductDao.save(.))")
public void after(){
System.out.println("最终通知==================");
}
```
### 6)通过@Pointcut为切点命名
- 在每个通知内定义切点,会造成工作量大、不易维护,对于切点,可以使用@Pointcut进行定义
- 切点方法限制:private void 无参数方法 无内容 方法名为切点名
- 当通知多个切点时,可以使用||进行连接
```java
@Before(value="myPointcut1()")
public void before(JoinPoint joinPoint){
System.out.println("前置通知=================="+joinPoint);
}
@Pointcut(value="execution(* com.aspectJ.demo1.ProductDao.save(.))")
private void myPointcut1(){}
```
### 7)注解AOP开发完整代码示例
- Spring配置文件
```xml
```
- 切面类
```java
/**
* 切面类
*/
@Aspect
public class MyAspectAnno {
@Before(value="myPointcut1()")
public void before(JoinPoint joinPoint){
System.out.println("前置通知=================="+joinPoint);
}
@AfterReturning(value="myPointcut2()",returning = "result")
public void afterReturing(Object result){
System.out.println("后置通知=================="+result);
}
@Around(value="myPointcut3()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知================");
Object obj = joinPoint.proceed(); // 执行目标方法
System.out.println("环绕后通知================");
return obj;
}
@AfterThrowing(value="myPointcut4()",throwing = "e")
public void afterThrowing(Throwable e){
System.out.println("异常抛出通知=============="+e.getMessage());
}
@After(value="myPointcut5()")
public void after(){
System.out.println("最终通知==================");
}
@Pointcut(value="execution(* com.aspectJ.demo1.ProductDao.save(.))")
private void myPointcut1(){}
@Pointcut(value="execution(* com.aspectJ.demo1.ProductDao.update(.))")
private void myPointcut2(){}
@Pointcut(value="execution(* com.aspectJ.demo1.ProductDao.delete(.))")
private void myPointcut3(){}
@Pointcut(value="execution(* com.aspectJ.demo1.ProductDao.findOne(.))")
private void myPointcut4(){}
@Pointcut(value="execution(* com.aspectJ.demo1.ProductDao.findAll(.))")
private void myPointcut5(){}
}
```
### 3. 基于AspectJ的XML方式的AOP开发
XML开发方式与注解方式只是用了不同给方式完成相同功能,两者使用上有相似之处
- 编写切面类(切面类上不使用注解)
- 完成目标类切面类的配置
```xml
```
- 配置AOP完成增强
- 关闭自动代理
- AOP配置写在标签中
- 定义切入点:哪些类的哪些方法需要增强,使用标签
- id属性
- expression属性:使用切点表达式,与注解方法的value相同
- 配置切面,使用标签
- ref属性,指向切面类的id/name
- 切面种类标签
- 包括
- method属性,值为切面种类
- pointcut-ref属性,值为切入点的id/name,指向被增强的切入点
- 其他属性:如afterThrowing的throwing属性、afterReturing的returing属性,使用方法与注解的同名属性相同
#### XML方式的AOP开发完整示例
- 切面类
```java
public class MyAspectXml {
// 前置通知
public void before(JoinPoint joinPoint){
System.out.println("XML方式的前置通知=============="+joinPoint);
}
// 后置通知
public void afterReturing(Object result){
System.out.println("XML方式的后置通知=============="+result);
}
// 环绕通知
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("XML方式的环绕前通知==============");
Object obj = joinPoint.proceed();
System.out.println("XML方式的环绕后通知==============");
return obj;
}
// 异常抛出通知
public void afterThrowing(Throwable e){
System.out.println("XML方式的异常抛出通知============="+e.getMessage());
}
// 最终通知
public void after(){
System.out.println("XML方式的最终通知=================");
}
}
```
- Spring配置文件
```xml
```
# 五. Spring JDBC Template
**——使用Spring组件JDBC Template简化持久化操作**
## 1. 简介
- 为了持久化操作,Spring再JDBC API之上提供了JDBC Template组件
- 程序员代码 -> JDBC API -> JDBC驱动 -> 数据库
- 程序员代码 -> JDBC Template -> JDBC API -> JDBC驱动 -> 数据库
- JdbcTemplate是线程安全的
- JDBC Template 提供统一的模板方法,在保留代码灵活性的基础上,尽量减少持久化代码
```java
// JDBC API
Statement statement = conn.createStatement();
ResultSet resultset = statement.executeQuery("select count(*) COUNT from student");
if(resultSet.next()){
Integer count = resultSet.getInt("COUNT");
}
// JDBC Template
Integer count = jt.queryForObject("select count(*) from student", Integer.class);
```
## 2. 本章示例的数据库表结构

## 3. 项目Spring配置及Maven依赖
- Maven
- Mysql驱动
```xml
mysql
mysql-connector-java
8.0.17
```
- Spring组件:core、beans、context、aop
```xml
4.2.4.RELEASE
org.springframework
spring-core
${spring.version}
org.springframework
spring-context
${spring.version}
org.springframework
spring-aop
${spring.version}
org.springframework
spring-beans
${spring.version}
```
- JDBC Template:jdbc、tx
```xml
org.springframework
spring-tx
${spring.version}
org.springframework
spring-jdbc
${spring.version}
```
- 单元测试
```xml
junit
junit
4.12
```
- Spring 配置
- 数据源配置
```xml
```
- Jdbc Template
```xml
```
- 不要忘记开启自动扫描,用于稍后注入JdbcTemplate
```xml
```
## 4. JDBC Template 基本使用
### 1)execute方法
- execute方法可以直接执行sql语句,常用于执行对表的操作
```java
public void testExecute(){
jdbcTemplate.execute("create table user1(id int, name varchar(20))");
}
```
### 2)update和batchUpdate方法
- update
- 对数据进行增删改查操作,返回值为被影响的行数
- sql为sql语句字符串
- args为参数
```java
int update(String sql, Object[] args)
int update(String sql, Object.. args)//不定参数
```
- 重载一
```java
public void testUpdate(){
String sql = "insert into student(name,sex) value(?,?)";
jdbcTemplate.update(sql, new Object[]{"张飞","男"});
}
```
- 重载二
```java
public void testUpdate2(){
String sql = "update student set sex=? where id=?";
jdbcTemplate.update(sql, "男",1);
}
```
- batchUpdate方法
- 批量增删改查,返回值为每个操作影响的行数
```java
int[] batchUpdate(String[] sql)
int[] batchUpdate(String sql, List