# spring-demo
**Repository Path**: zoranwang/spring-demo
## Basic Information
- **Project Name**: spring-demo
- **Description**: spring框架学习记录
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2022-03-16
- **Last Updated**: 2022-06-12
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Spring知识点积累
## 一、Spring概述
### 1.1 简介
- Spring是一个开源框架
- Spring为简化企业级开发而生,使用Spring开发可以将Bean对象、Dao组件对象、Service组件对象等交给Spring容器来管理,这样使得很多复杂的代码在Spring中开发变得非常优雅和简洁,大大**降低了代码的耦合度**
,极大的方便项目的**后期维护、升级和扩展**
- Spring是一个IOC(DI)和AOP容器框架
- 优良特性:
- Spring开发的应用中,对象可以不依赖Spring的API
- 控制反转:IOC,将对象的创建权交给Spring框架
- 依赖注入:DI,依赖不需要手动调用setXXX方法设置,而是通过赋值配置(xml配置或者注解配置即可)
- 面向切面:AOP
- 容器:Spring是一个容器,包含并且管理应用对象的生命周期
- 组件化:Spring中,可以使用简单的组件配置合成一个复杂的应用,使用XML或者Java注解组合这些对象
- 一站式:Spring框架可以整合各种企业应用的开源框架和第三方类库
### 1.2 Spring框架模块介绍
Spring框架分为四大模块
Core核心模块,负责管理组件的Bean对象
- spring-beans
- spring-context
- spring-core
- spring-expression
AOP面向切面编程模块
- spring-aop
- spring-aspects
数据库操作模块
- spring-jdbc
- spring-orm
- spring-oxm
- spring-tx
- spring-jms
Web模块
- spring-web
- spring-webmvc
- spring-websocket
- spring-webmvc-portlet
## 二、HelloWorld工程
> 代码见`spirng-helloworld`
### 2.1 通过IOC容器创建对象,并为属性赋值
创建一个普通的Java项目
需要的jar包:
- junit_4.12.jar
- org.hamcrest.core_1.3.0.jar
- spring-beans-5.2.5.RELEASE.jar
- spring-context-5.2.5.RELEASE.jar
- spring-core-5.2.5.RELEASE.jar
- spring-expression-5.2.5.RELEASE.jar
- spring-jcl-5.2.5.RELEASE.jar
创建Spring配置文件
- 在**src根目录**,右键 -> New -> XML configuration File -> Spring Config
创建Bean对象
在applicationContext.xml配置文件中使用bean标签注入对象
测试类测试
- 获取Spring容器对象,由接口ApplicationContext表示
- 从容器中获取Bean对象
## 三、IOC
> 代码见`spring-ioc&di`,依赖和`spring-helloworld`一致
### 3.1 简介
- IOC(Inverse Of Control) 控制反转:不用自己new对象,对象的创建交给Spring容器负责
- DI(Dependency Injection)依赖注入:依赖不需要手动调用setXXX方法设置,而是通过赋值配置(xml配置或者注解配置即可)
### 3.2 IOC容器获取对象的方式
#### 3.2.1 通过id获取对象
- 返回Object,需要强转
- Bean默认在创建Application容器时一起创建
- 默认**单例**,线程不安全,可以使用多例或者ThreadLocal解决
#### 3.2.2 通过类型获取对象
- 如果通过class类型找到唯一一个,就返回
- 如果没有找到或者找到多个,报错
#### 3.2.3 通过id和类型获取对象
- `applicationContext.getBean`时指定id和类型即可
### 3.2 DI依赖注入
#### 3.2.1 getXxx方法注入
配置文件中,在`property`标签内,使用`name`和`value`注入
p名称空间(p名称空间可以以非常简短的形式通过调用setXxx方法给属性赋值)
1. 在`applicationContext.xml`配置文件的头文件中加上`xmlns:p="http://www.springframework.org/schema/p"`即可启用p名称空间
2. 使用:在`bean`标签内使用`p:xxx="xxx"`注入属性值,简化`property`标签
```xml
```
`null`值的处理
```xml
```
#### 3.2.2 构造方法注入
通过构造方法形参名注入
通过index注入
构造方法注入可以指定类型
#### 3.2.3 工厂方法注入bean
工厂方法注入
```java
public class PersonFactory {
public static Person createPerson1() {
return new Person(11, "工厂方法创建的对象", "2222", 104);
}
public Person createPerson2() {
return new Person(12, "工厂方法创建的对象", "1111", 104);
}
}
```
配置类注入
- 无论是静态方法注入还是普通方法注入,都默认单例,
工厂静态方法注入对象直接注入静态方法
```xml
```
工厂普通方法注入对象,先注入工厂,再注入对象
```xml
```
```xml
```
#### 3.2.4 FactoryBean接口方式创建对象
创建FactoryBean接口的实现类,重写方法
```java
public class PersonFactoryBean implements FactoryBean {
/**
* 创建Bean对象时创建的方法
* @return
* @throws Exception
*/
@Override
public Object getObject() throws Exception {
return new Person(66, "FactoryBean创建的对象", "123456566", 16);
}
/**
* 获取对象Class类型的方法
* @return
*/
@Override
public Class> getObjectType() {
return Person.class;
}
/**
* 是否为单例
* @return
*/
@Override
public boolean isSingleton() {
return true;
}
}
```
配置
```xml
```
#### 3.2.5 Bean对象的继承
- 通过继承实现bean对象配置信息的复用
- 子bean赋值会覆盖掉父bean的值
```xml
```
#### 3.2.6 抽象Bean
- 抽象bean只能用于继承,不能被实例化
- 使用:在`bean`标签内加`abstract="true"`即可
#### 3.2.7 对不同类型属性的赋值
对自定义JavaBean(内部Bean)属性的赋值
- 在`property`标签内写`bean`标签即可
- 内部`bean`不能被IOC容器直接获取
```xml
```
对List属性的赋值(list或者array标签)
```xml
华为
荣耀
一加
```
对Map属性的赋值(map标签)
```xml
```
对Properties属性的赋值
#### 3.2.8 util名称空间
util名称空间,可以定义全局公共的集合信息,方便容器直接获取,或者给属性赋值使用
```xml
1111
2222
3333
```
异常:通配符的匹配很全面,但是无法找到元素`util:list`的声明
- 原因:没有正确配置utils的引用资源
- 解决:在`schemaLocation`中加两个地址
- `http://www.springframework.org/schema/util`
- `http://www.springframework.org/schema/util/spring-util-4.0.xsd`
级联属性赋值
- 如果要使用级联属性赋值,要先给上级对象赋值
```xml
```
#### 3.2.9 IOC容器中bean对象的创建顺序
- 在Spring容器中,Bean对象的创建顺序默认是他们在配置文件中,从上到下的顺序决定
- 可以在`bean`标签中使用`depend-on`指定bean创建的顺序
```xml
```
结果:
```
B的无参构造器...
C的无参构造器...
A的无参构造器...
```
#### 3.2.10 bean的单例和多例
(略)
#### 基于xml配置文件的自动注入
> 可以参考`spring-ioc&di`工程的`context-autowire.xml`配置文件
自动注入指的是按照某种指定的算法,自动给子对象赋值
使用`autoware`属性设置一种自动注入的算法
- `default`和`no`都表示不自动赋值子对象
- `byName`表示按子对象的属性名作为`id`,在容器中查找并注入
- 如果找到就赋值,没有找到就为`null`
- `byType`表示按子对象的类型在容器中查找并注入
- 找到一个就注入
- 找到多个就报错
- 没有找到就位`null`
- `constructor`表示按构造器参数进行查找并注入
- 先按类型找,再按属性名找,如果找不到或者指代不明,就为`null`
### 3.3 Bean对象的生命周期
spring容器中,单例对象的声明周期和spring容器的声明周期一致;而多例对象不随着spring的创建而创建,而是被使用时才会创建
无论是`bean`的自定义初始化方法和销毁方法,还是spring容器中的自定义后置处理器方法,都是针对于`bean`对象而言的生命周期,不是spring容器的生命周期
`pojo`类的初始化方法和销毁方法
- 初始化方法一般用于初始化赋值,销毁方法一般用于资源释放(容器关闭时调用,只对**单例**有效)
```java
public void init(){
System.out.println("init...");
}
public void destroy(){
System.out.println("destroy...");
}
```
```xml
```
`BeanPostProcessor`后置处理器的实现类
- 在每个对象创建开始和结束时调用,对每个对象都有作用
```java
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 对象初始化前方法,对象初始化后执行
*
* @param bean 当前的初始化对象实例
* @param beanName 当前初始化对象的id值
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置处理器器初始化..." + "," + bean + "," + beanName);
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
/**
* 对象初始化后方法,对象初始化后执行
*
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置处理器销毁..." + "," + bean + "," + beanName);
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
```
以上四个方法的顺序:
1. 后置处理器对象初始化前方法(bean创建时,可以获取到bean对象和id,需要把自定义的后置处理器注入到spring容器中,对每个bean对象都起作用)
2. `bean`对象自定义`init`方法(bean创建时,可以用于对象属性的初始化,需要在注入时配置`init-method`)
3. 后置处理器对象初始化后方法(bean创建完成时)
4. `bean`对象自定义`destroy`方法(单例对象在容器销毁时调用)
## 四、Spring管理数据库连接池
需要新增的依赖:
- druid-1.1.9.jar
- mysql-connector-java-5.1.37-bin.jar
### 4.1 数据库连接池
配置文件`jdbc.properties`
```properties
username=zoran
password=zoran
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.149.128:3306/spring?characterEncoding=UTF-8
```
配置数据库连接池
```xml
```
```xml
```
### 4.2 JdbcTemplate
略,使用mybatis取代
## 五、Spring EL表达式
### 5.1 语法
用于创建`bean`对象时,为`bean`对象赋一些比较复杂的值
基本语法: SpEL使用`#{…}`作为定界符,所有在大框号中的字符都将被认为是SpEL表达式
Spring EL字面量
- 整数:#{8}
- 小数:#{8.8}
- 科学计数法:#{3e5}
- 字符串(单引号或者双引号):#{'111'}
- 布尔类型:#{true}
支持的运算符
- 算术运算符、字符串连接
- 比较运算符
- 逻辑运算符
- 三目运算符
- 正则表达式:matches
### 5.2 场景
引用其他`bean`
```xml
```
引用其他`bean`的属性值
```xml
```
调用非静态方法
```xml
```
```xml
```
调用静态方法
```xml
```
## 六、注解
引入支持AOP的依赖:
- spring-aop-5.2.5.RELEASE.jar
- spring-aspects-5.2.5.RELEASE.jar
### 6.1 常用注解
注解 | 含义
--- | ---
`@Component` | 配置Web层,Servcie层,DAO层之外的Bean对象
`@Controller` | 配置Web层的组件
`@Service` | 配置Service层的组件
`@Repository` | 配置DAO组件
`Scope` | 配置Bean的作用域(单例,多例),用于类上或者有`@Bean`的方法上
`@PostConstruct` | init-method
`@PreDestory` | destroy-method
`@Value("abc")` | 给基本类型注入
`@Value("${user}")` | 注入配置文件的值
`@Value("${user:root}")` | 注入配置文件的user,如果没有user,注入root
`@Configuration` | 表明当前类是一个配置文件类
`@ComponentScan(basePackages={})` | 扫描其他包下的注解
`@Import` | 导入其他配置文件类
`@Bean` | 注入当前方法的返回值,默认Id是方法名
### 6.2 扫描包和排除组件
扫描注解包
```xml
```
指定扫描包时包含的类和不包含的类(不常用)
注意排除的是有指定注解和其**子注解**、指定类和其**子类**的组件类
```xml
```
```xml
```
`type="annotation"`可以替换成如下值
类别 | 示例 | 说明
--- | --- | ---
annotation | XxxAnnotation | 过滤所有标注了XxxAnnotation的类。这个规则根据目标组件是否标注了指定类型的注解进行过滤
assignafble | BaseXxx | 过滤所有BaseXxx类的子类,这个规则根据目标组件是否是指定类型的子类的方式进行过滤
aspectj | zoran.wang.*Service+ | 所有类名是以Service结束的,或这样的类的子类,这个规则根据AspectJ表达式进行过滤
regex | zoran.wang.anno.* | 所有zoran.wang.anno包下的类,这个规则根据正则表达式匹配到的类名进行过滤
custom | zoran.wang.XxxTypeFilter | 使用XxxTypeFilter类通过编码的方式自定义过滤规则,该类必须实现org.springframework.core.type.filter.TypeFilter接口
### 6.3 自动装配
使用`@Autowired`注解实现自动装配
注入规则:
1. 先根据类型注入
2. 如果有多个类型, 根据`id`注入
当类型匹配到多个且`id`都不匹配时,可以使用组合注解`@Qualifier`指定`bean`对象的`id`
```java
@Autowired
@Qualifier(value = "personService")
private PersonService personService;
```
### 6.4 Spring的专有测试
引入依赖:
- spring-test-5.2.5.RELEASE.jar
Spring整合junit简化测试,在扩展的Junit类中,有一个Spring容器,不再需要我们自己去实现这个容器
使用注解`@ContextConfiguration`指定配置文件路径,使用注解`@RunWith指定Spring扩展的测试类`
使用Spring提供的扩展Junit测试,可以使用Spring的依赖注入功能
```java
@ContextConfiguration(locations = "/applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
@Autowired
private Person person;
@Test
public void test01() {
System.out.println("获取到了 === " + person);
}
}
```
## 七、AOP切面编程
### 7.1 jdk动态代理和Cglib动态代理
#### 7.1.1 jdk动态代理
jdk动态代理
- 目的:动态修改class字节码
- 功能:增强(前置增强、后置增强、异常增强、返回增强)
注意使用jdk动态代理,需要有接口
示例代码:
接口:
```java
public interface Calculation {
/**
* 相加
* @param a
* @param b
* @return
*/
int add(int a, int b);
/**
* 除法 a/b
* @param a
* @param b
* @return
*/
int div(int a, int b);
}
```
实现类:
```java
public class Calculator implements Calculation {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int div(int a, int b) {
return a / b;
}
public int add_3(int a, int b, int c) {
return a + b + c;
}
}
```
代理类:
```java
public class CalculatorProxy {
public static Object createJdkProxyInstance(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
LogUtils.logBefore(method.getName(), args);
Object result = null;
try {
if (args.length > 0) {
result = method.invoke(target, args);
} else {
result = method.invoke(target);
}
LogUtils.logAfterReturning(method.getName(), result);
} catch (Exception e) {
LogUtils.logAfterThrowing(method.getName(), e);
throw new RuntimeException(e);
}
return result;
}
}
);
}
}
```
测试:
```java
public class CalculateTest {
public void test01() {
//1. 使用jdk动态代理,必须要接口
Calculator target = new Calculator();
//2. 创建动态代理实例
Calculation jdkProxyInstance = (Calculation) CalculatorProxy.createJdkProxyInstance(target);
result = jdkProxyInstance.div(1, 1);
System.out.println("result = " + result);
}
}
```
#### 7.1.2 Cglib动态代理
Cglib动态代理不需要对象有实现接口
Cglib动态代理是通过修改目标对象的字节码程序产生一个子类,生成一个代理对象实例
Cglib产生的代理对象是目标对象的子类
实现类`Calculator`和jdk动态代理一样
Cglib代理工厂类:
```java
public class CglibProxyFactory {
public static Object createCglibProxy(Object target) {
//1. 增强器,负责产生一个Cglib代理对象实例
Enhancer enhancer = new Enhancer();
//2. 指定目标对象
enhancer.setSuperclass(target.getClass());
//3. 设置方法拦截器
enhancer.setCallback(new MethodInterceptor() {
/**
* 只要代理对象方法调用,就会执行intercept方法
* @param proxy 代理对象实例
* @param method 调用方法的反射对象
* @param args 调用方法时传递的参数
* @param methodProxy 方法反射对象的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//前置增强
LogUtils.logBefore(method.getName(), args);
Object result = null;
try {
//通过反射调用方法
result = method.invoke(target, args);
LogUtils.logAfterReturning(method.getName(), result);
} catch (Exception e) {
LogUtils.logAfterThrowing(method.getName(), e);
throw new RuntimeException(e);
}
return result;
}
});
//返回Cglib代理对象的实例
return enhancer.create();
}
}
```
测试:
```java
public class CalculateTest {
public static void main(String[] args) {
//测试Cglib动态代理
Calculator cglibProxy = (Calculator) CglibProxyFactory.createCglibProxy(target);
//add_3是Calculator独有的方法
result = cglibProxy.add_3(1, 2, 3);
System.out.println("result = " + result);
}
}
```
### 7.2 Spring AOP
#### 7.2.1 术语
- 通知(Advice):增强的代码(前置、后置、异常增强的代码)
- 切面(Aspect):包含通知代码的类
- 横切关注点:添加通知的位置
- 目标(target):被代理的对象
- 代理(proxy):代理对象
- 连接点(Joinpoint):横切关注点和程序代码的连接
- 切入点(pointcut):用户真正处理的连接点
#### 7.2.2 使用Spring AOP实现简单的切面编程
> 模块`spring-springaop`
实现Spring AOP编程,需要
- Spring配置文件开启切面生效配置
- 切面(写通知代码的类)
- 切入点表达式(指定真正处理的方法)
- 连接点JoinPoint(也可以无参)
spring配置文件
```xml
```
切面,即写通知代码的类,注意`@Before`下方法的参数
```java
@Component
@Aspect//切面类,包含通知代码的类
public class LogUtils {
@Pointcut(value = "execution(* zoran.wang.calculate.impl.Calculator.*(..))")
private void pointcut(){};
//也可以把pointcut直接写到这里
@Before("pointcut()")
public static void before(){
System.out.println("before...");
}
}
```
PointCut切入点表达式语法格式:
```
execution(访问权限 返回值类型 方法全限定名(参数类型列表))
在spring中只有public权限能拦截到,访问权限可以省略
```
连接点JoinPoint
- 获取连接点信息:`joinPoint`
- 获取方法名 `joinPoint.getSignature().getName()`
- 获取参数 `joinPoint.getArgs()`,日志中可以写为`Arrays.asList(joinPoint.getArgs())`
- 获取返回值
```java
@AfterReturning(value = "pointcut()", returning = "result")
public static void logAfterReturning(JoinPoint joinPoint, Object result) {
//...
}
```
- 获取异常信息
```java
@AfterThrowing(value = "pointcut()", throwing = "e")
public static void logAfterThrowing(JoinPoint joinPoint, Exception e) {
//...
}
```
执行顺序:
前置通知 -> 执行方法 -> 后置通知 -> 返回通知或者异常通知
#### 7.2.3 环绕通知
- 使用`@Around`表示环绕通知
- 环绕通知需要使用`ProceedingJoinPoint`执行目标方法
- 环绕通知需要返回方法的返回值
- 环绕通知的异常一定要往外抛,否则普通的异常通知不执行
- 环绕通知比普通通知优先执行

日志打印:
```
环绕前置...方法名:add,参数:[1, 1]
前置通知...方法名:add,参数:[1, 1]
环绕后置...方法名:add,参数:[1, 1]
环绕返回...方法名:add,参数:[1, 1],返回值:2
后置通知...方法名:add,参数:[1, 1]
返回通知...方法名:add,参数:[1, 1],返回值:2
```
多个切面的执行顺序
- 在切面类上使用注解`@Order`,值越小,越先执行
### 7.3 纯xml配置spring aop
> 模块:`spring-aop-xml`
```xml
```
### 7.4 纯注解配置spring aop
```java
@Configuration @ComponentScan(basePackages = {"zoran.wang"})
/*** proxyTargetClass = true:cglib * false:根据有无接口判断 */
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
}
```
## 八、事务
略,spring中的事务在`mybatis-demo`项目中演示
## 九、Spring整合Web工程
略