Spring
1. Spring概述
1.1 什么是Spring?
spring是一个java框架,使用java语言开发的轻量级,开源框架。可以在j2se,j2ee项目中使用。
Spring核心技术:IOC,AOP
Spring作用:实现解耦,解决对象之间的耦合,模块之间的耦合。
1.2 Spring官网
1.3 Spring优点
(1) 轻量
Spring框架实用的jar都比较小,一般在1M以。Spring核心功能所需jar总共在3M左右。
(2) 针对接口编程,解耦
Spring提供了IOC控制反转,由容器管理对象,对象的依赖关系。
对象之间依赖解耦合
(3) AOP编程支持
通过SpringAOP功能,方便进行面向切面编程,许多传统OOP实现功能,都可以通过AOP轻松解决,提高了开发效率。
(4) 方便集成各种框架
Spring不排斥各种优秀的开源框架,相反Spring还兼容各种优秀的框架。
2. IOC控制反转
2.1 概念
IOC,Inversion of Control
:控制反转,是一个理论,一种指导思想。指导开发人员如何使用对象,管理对象.把对象的创建,属性赋值,对象生的命周期交给代码之外的容器管理。
- IOC 分为
控制
和反转
控制:对象创建,属性赋值,对象生命周期管理
反转: 把开发人员管理对象的权限,转移给代码之外的容器实现。由容器完成对象的管理,通过容器使用容器中的对象。
-
DI
Dependency Injection
依赖注入是IOC的一种技术实现。程序猿只需要提供要使用的对象的名称就可以了,对象如何创建,如何从容器中查找,获取都由容器内部自己实现。
-
依赖
如果ClassA使用了ClassB的属性或方法,叫做ClassA依赖ClassB。
-
Spring通过DI实现IOC
通过Spring框架,只需要提供对象名称就可以了。从容器中获取名称对应的对象。
Spring底层是通过,反射机制,通过反射创建对象。
2.2 第一个Spring程序
2.2.1 创建maven项目
更改项目SDK版本号
更改模块SDK版本号
修改pom.xml中java版本
2.2.2 添加Spring依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring-01</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<!--单元测试-->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--Spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
</dependencies>
</project>
2.2.3 Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
- 根标签是 beans
- beans 后面是约束文件说明
- beans 里面是bean声明
- 什么是bean?
- bean就是java对象,Spring容器管理java对象
2.3 Spring容器创建对象特点
- 容器对象ApplicationContext:接口
- 通过ApplicationContext对象,获取要实用的其他java对象,执行
getBean(id)
- 通过ApplicationContext对象,获取要实用的其他java对象,执行
- Spring默认调用类的无参构造方法,创建对象
- Spring读取配置文件,一次创建好所有java对象,放到map中
2.4 DI:给属性赋值
Spring调用类的无参构造方法,创建对象,对象创建后给属性赋值.
给属性赋值有两种方式
- 使用xml标签和属性赋值
- 使用注解
DI分类
- set注入
设值注入
- 构造注入
2.4.1 基于XML的DI
在xml配置文件中使用标签和属性,完成对象的创建,属性赋值。
set注入
概念: Spring调用类中的set方法,在set方法中可以完成属性赋值。推荐使用
简单类型的set注入 使用 value
<bean id="myStudent" class="com.lu.pack01.Student">
<property name="name" value="luckyFang"/> <!-- setName() -->
<property name="age" value="22"/> <!-- setAge() -->
</bean>
引用类型的set注入 使用 ref
<!-- Spring bean 声明顺序无关,但是区分大小写-->
<!-- 首先创建school bean-->
<bean id="mySchool" class="com.lu.pack02.School">
<property name="name" value="山西晋中理工学院"/>
<property name="address" value="晋中市榆次区张庆乡工业园区"/>
</bean>
<!-- 其次使用 ref 引用 school bean-->
<bean id="myStudent" class="com.lu.pack02.Student">
<property name="name" value="luckyFang"/>
<property name="age" value="22"/>
<property name="school" ref="mySchool"/> <!-- setSchool() -->
</bean>
构造注入
构造注入: Spring调用类中的有参数构造方法,在对象创建时给属性赋值
<bean id="mySchool" class="com.lu.pack03.School">
<constructor-arg index="0" value="山西晋中理工学院"/>
<constructor-arg index="1" value="山西省晋中市榆次区"/>
</bean>
<!-- 可以省略index 属性 默认顺序 0 1 2 3 -->
<bean id="mySchool02" class="com.lu.pack03.School">
<constructor-arg value="山西晋中理工学院"/>
<constructor-arg value="山西省晋中市榆次区"/>
</bean>
<bean id="myStudent" class="com.lu.pack03.Student">
<constructor-arg name="name" value="luckyFang"/>
<constructor-arg name="age" value="22"/>
<constructor-arg name="school" ref="mySchool02"/>
</bean>
<bean id="myFile" class="java.io.File">
<constructor-arg name="parent" value="/Users/luckyfang/Desktop"/>
<constructor-arg name="child" value="第一章梳理逻辑.pdf"/>
</bean>
自动注入
概念: Spring可以根据某些规则给引用类型完成赋值。只对引用类型有效,规则byName
和byType
-
byName
- 按名称注入: java类中引用类型属性名称和spring容器中bean的id名称一样的,并且数据类型也一致,这些bean能够赋值给引用类型。
<!--byName注入 1. 按名称注入 java类中引用类型的属性名称和 spring容器中的 bean id一样,数据类型一致 语法: <bean id = "xxx" class ="xxx" autowire = "byName"> 简单属性赋值 </bean> --> <!--这里的id 要和 属性中的 名称一致--> <bean id="school" class="com.lu.pack04.School"> <property name="name" value="山西晋中理工学院"/> <property name="address" value="山西省晋中市榆次区"/> </bean> <!--所有属性规则 按照 byName 赋值--> <bean id="myStudent" class="com.lu.pack04.Student" autowire="byName"> <property name="name" value="张三"/> <property name="age" value="22"/> </bean>
-
byType
- 按类型注入: java类中引用类型输入类型和Spring容器中bean的class值是同源关系,这样的bean就会赋值给引用类型。
<!-- byType注入 按类型注入,java中引用类型数据类型和ban的class 是同源的 这些bean能够赋值给引用类型 同源: 1. java引用数据类型和bean的calss值是一样的 2. java引用数据类型和bean的class值是父子关系 3. java引用数据类型和bean的class值是接口实现关系 注意: 在整个xml文件中,符合条件的对象只能有一个 不然会引发二义性问题 --> <!-- <bean id="mySchool" class="com.lu.pack05.School">--> <!-- <property name="name" value="山西晋中理工学院"/>--> <!-- <property name="address" value="山西省晋中市榆次区"/>--> <!-- </bean>--> <!--所有属性规则 按照 byType 赋值--> <bean id="myStudent" class="com.lu.pack05.Student" autowire="byType"> <property name="name" value="张三"/> <property name="age" value="22"/> </bean> <!-- 声明子类 子类被注入--> <bean id="primerySchool" class="com.lu.pack05.PrimarySchool"> <property name="name" value="山西应用科技学院"/> <property name="address" value="山西省太原市小店区"/> </bean>
在整个xml文件中,符合条件的对象只能有一个 不然会引发二义性问题
多文件配置
目的:减少单文件大小,提高代码可读性
按功能模块划分
- 一个模块一个配置文件
按类的功能划分
- 配置文件 事务 等一个文件
包含管理配置文件
- ApplicationContext.xml
- aaa.xml
- bbb.xml
- ccc.xml
<import resource ="filepath1"/>
<import resource ="filepath2"/>
classpath: 表示类路径,也就是类文件所在的目录。
什么时候使用classpath? 在一个文件中使用其他文件,就需要使用classpath
<!--
总配置文件,包含多个配置文件,一般不声明bean
语法:
<import resource = "classpath:"/>
-->
<import resource="classpath:pack06/spring-student.xml"/>-->
<import resource="spring-school.xml"/>-->
当然也可以使用通配符*
<!--包含关系配置文件可用通配符 * 表示任意字符
注意: 总文件不能包含在通配符范围内
-->
<import resource="classpath:pack06/spring-*.xml"/>
注意: 主文件不能包含在通配符范围内
死循环
2.4.2 基于注解的DI
使用Spring提供的注解来完成配置。
- @Component
- @Value
- @AutoWired
- @Qualifier
- @Resource
开启注解支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解扫描-->
<context:component-scan base-package="com.lu.pack01"/>
</beans>
- base-package: 注解所在的包,会扫描包及其子包
扫描多个包
- 多次使用扫描器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解扫描-->
<!-- 多次使用扫描器-->
<context:component-scan base-package="com.lu.pack01"/>
<context:component-scan base-package="com.lu.pack02"/>
<context:component-scan base-package="com.lu.pack03"/>
</beans>
- 使用分隔符
, or ;
<!--分隔符扫描-->
<context:component-scan base-package="com.lu.pack01;com.lu.pack02;com.lu.pack03"/>
- 指定父包
<!--指定父包-->
<context:component-scan base-package="com.lu"/>
@Componet
需要在类上使用注解
@Component
该注解的value属性用于指定该bean的id
package com.lu.pack01;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Component 表示创建对象放到容器中
* 相当于 <bean></bean>
* 属性: value
* 相当于 bean id
* 位置: 在类的上面,表示此类的对象
* <bean id "myStudent" class = "com.lu.pack01.Student"/>
*/
// @Component(value = "myStudent")
// value 也可以省略
// 如果不提供 value 则采用默认值,对象名(小写)
@Component("myStudent")
public class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
当然除了@Component注解用来创建对象,还有
- @Repository:
放在dao接口实现类上,表示创建一个doa对象 (持久层对象)
- @Service:
放在业务层接口实现类上,表示业务层对象 业务层有事务功能
- @Controller:
放在控制器类上面的对象,用于表示层
以上4个注解都能创建对象,表示对象是分层的。
@Value
值注入
Spring中值注入有两种
- 属性注入
- set注入
package com.lu.pack02;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "myStudent")
public class Student {
/**
* 简单类型属性赋值
*
* @value 简单类型属性赋值
* 属性: value
* 位置:
* 1. 属性定义上面,无需set方法
* 2. 在set方法上面
*/
@Value("jack")
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
@Value("22")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
这样注入值没意义,如果我们要动态修改值呢?
读取外部配置文件注入值
<!-- 读取配置文件到spring-->
<context:property-placeholder location="classpath:/config.properties"/>
myName=Fangfang
myAge=22
然后使用 @Value("${")来实现注入
package com.lu.pack02;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "myStudent")
public class Student {
/**
* 简单类型属性赋值
*
* @value 简单类型属性赋值
* 属性: value
* 位置:
* 1. 属性定义上面,无需set方法
* 2. 在set方法上面
*/
@Value("${myName}")
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
@Value("${myAge}")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
@Autowired
Spring框架提供的,给引用类型赋值,自动注入注解
- required(boolean)
- true: 在spring启动时,创建容器对象,会检查引用类型是否赋值成功,如果赋值失败程序终止,然后报错
- false: 在spring启动时,创建容器对象,会检查引用类型是否赋值成功,如果赋值失败 程序正常执行,不报错,引用类型值为null
支持
- byName
- byType
默认
使用位置
- 属性定义上面
- 在set方法上面
package com.lu.pack03;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("mySchool")
public class School {
private String name;
private String addr;
@Value("山西晋中理工学院")
public void setName(String name) {
this.name = name;
}
@Value("山西省晋中市榆次区张庆乡工业园区")
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", addr='" + addr + '\'' +
'}';
}
}
package com.lu.pack03;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "myStudent")
public class Student {
/**
* 简单类型属性赋值
*
* @value 简单类型属性赋值
* 属性: value
* 位置:
* 1. 属性定义上面,无需set方法
* 2. 在set方法上面
*/
@Value("${myName}")
private String name;
private int age;
private School school;
// 自动注入
@Autowired
public void setSchool(School school) {
this.school = school;
}
public void setName(String name) {
this.name = name;
}
@Value("${myAge}")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
Student{name='Fangfang', age=22, school=School{name='山西晋中理工学院', addr='山西省晋中市榆次区张庆乡工业园区'}}
byType注入
package com.lu.pack03;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "myStudent")
public class Student {
/**
* 简单类型属性赋值
*
* @value 简单类型属性赋值
* 属性: value
* 位置:
* 1. 属性定义上面,无需set方法
* 2. 在set方法上面
*/
// @Value("jack")
@Value("${myName}")
private String name;
private int age;
private School school;
// byType注入
@Autowired
public void setSchool(School school) {
this.school = school;
}
public void setName(String name) {
this.name = name;
}
// @Value("22")
@Value("${myAge}")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
@Qualifier
byName注入时指定要注入对象名称 结合 @AutoWired使用
byName注入
package com.lu.pack03;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "myStudent")
public class Student {
/**
* 简单类型属性赋值
*
* @value 简单类型属性赋值
* 属性: value
* 位置:
* 1. 属性定义上面,无需set方法
* 2. 在set方法上面
*/
// @Value("jack")
@Value("${myName}")
private String name;
private int age;
private School school;
// byName注入
@Autowired
@Qualifier("mySchool")
public void setSchool(School school) {
this.school = school;
}
public void setName(String name) {
this.name = name;
}
// @Value("22")
@Value("${myAge}")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
@Resource
是jdk自带的注解 默认按名称注入
作用
- 属性上
- set方法上
先使用 byName赋值,如果找不到则使用byType赋值
如果指定 name参数 则 强制采用 byName 注入
package com.lu.pack04;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component(value = "myStudent")
public class Student {
/**
* 简单类型属性赋值
*
* @value 简单类型属性赋值
* 属性: value
* 位置:
* 1. 属性定义上面,无需set方法
* 2. 在set方法上面
*/
// @Value("jack")
@Value("${myName}")
private String name;
private int age;
private School school;
// 这里是 byType注入成功
@Resource
public void setSchool(School school) {
this.school = school;
}
public void setName(String name) {
this.name = name;
}
// @Value("22")
@Value("${myAge}")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
2.5 IOC总结
IOC: 管理对象的,把对象放到容器中,创建赋值,管理依赖关系
IOC: 通过管理对象,实现解耦,IOC解决业务处理逻辑中对象之间的耦合关系。
Spring作为容器适合管理什么对象?
- service 对象 dao 对象
- 工具类对象
不适合什么对象?
- 实体类
- servlet listener filter 等 web对象 因为他们是 tomcat管理的
3. AOP面向切面编程
在源码中,不改变原有方法增加功能。
3.1 传统开发面临问题
- 源码改动可能比较多
- 重复代码比较多
- 代码难以维护
3.2 AOP概念
什么是AOP?
AOP(Aspect Orient Programing):面向切面编程
Aspect: 表示切面:给业务方法增加的功能,叫做切面一般都是非业务功能,并且切面是可以复用的,比如日志,事务,权限检查,参数检查,统计信息等。
Orient: 面向切面
Programming: 编程
3.3 AOP作用
- 提高切面复用率
- 让开发人员专注于业务逻辑,提高开发效率
- 实现业务功能和其他非业务功能解耦合
- 给存在的业务方法增加功能,不用修改原来代码
3.4 AOP中术语
- Aspect: 切面,给业务方法增加的功能。
- JoinPoint: 连接点,连接切面的业务方法。在这个业务方法执行时,会同时执行切面的功能
- PointCut: 切入点,是一个或多个连接点集合。表示这些方法执行时,都能增加切面的功能。
- target: 目标对象,给哪个对象添加切面功能,那么这个对象就是目标
- **Advice:**通知(增强),表示切面的执行时间。在目标方法执行前,还是目标方法执行后执行切面。
在 Advice时间,在PointCut位置,执行Aspect
AOP本质是动态代理
3.5 什么时候用AOP
要给某些方法增加相同的功能。源代码不方便修改。
给业务方法增加非业务功能也可以使用AOP
3.6 AOP
下面我们来用AOP实现日志功能。
导入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
标注一个切面
package com.lu.log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class Log {
@Before("execution(* com.lu.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("before ...");
}
@Before("execution(* com.lu.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("after ...");
}
@Around("execution(* com.lu.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint joinPoint){
System.out.println("around before...");
try {
joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("around after");
}
}
package com.lu.service;
public interface UserService {
void add();
void delete();
void modify();
void query();
}
package com.lu.service;
import org.springframework.stereotype.Component;
@Component("userService")
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("add method");
}
@Override
public void delete() {
System.out.println("delete method");
}
@Override
public void modify() {
System.out.println("modify method");
}
@Override
public void query() {
System.out.println("query method");
}
}
- @Before: 前置通知
执行方法前
- @After: 后置通知
执行方法后
- @Around: 环绕通知
执行方法时
关于execution表达式
- 返回值
*
- 要拦截的类
com.xxx
- 下所有方法
.*
- 任意参数
(..)