文章目录
Spring框架两大核心机制:
- IoC(控制反转)/DI(依赖注入)
- AOP(面向切面编程)
一. Spring概述
Spring 是一个企业级开发框架,是软件设计层面的框架,优势在于可以将应用程序进行分层,开发者可以自主选择组件。
MVC:Struts2, Spring MVC
ORMapping: Hibernate, MyBatis, Spring Data
为什么使用Spring(企业级项目特点)
- 大规模:用户数量多,数据规模大,功能模块众多
- 性能和安全要求高
- 业务复杂
- 灵活多变
Spring体系框架
Spring优点
- 低侵入式设计;
- 独立于各种应用服务器;
- 依赖注入特性组件关系透明化,降低了耦合度;
- 面向切面编程特性允许将通用任务进行集中式处理;
- 与第三方框架的良好整合
二. Spring IoC
1. 什么是控制反转
在传统的程序开发中,需要调用对象时, 通常由调用者来创建被调用者的实例,即对象是由调用者主动new出来的。但是在Spring 框架中创建对象的工作不在由调用者来完成,而是交给IoC容器来创建, 再推送给调用者,整个流程完成反转,所以是控制反转。(举例:传统的购物需要你去超市自己购买商品,而Ioc相当于你将一个购物袋放到门口,Spring会将你需要的商品主动放到你的购物袋中,你只需要打开门拿购物袋即可)。
2. 如何使用IoC
创建Maven 工程,在pom.xml中添加依赖
关于配置Maven的教程,这里有一个比较详细的博主写的,给大家推荐一下:Maven的安装与配置及IDEA配置(超详细图文讲解)
<?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</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.12</version>
</dependency>
<!-- 简化实体类的开发 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
注意!!!(特别重要!!!):
在这里你的jdk版本如果是8的话,Spring版本一定要在6以下,否则他会报下面的错误:
所以如果你使用的是jdk11(本人的jdk11版本也不相容6.0.12,最后换为jdk20可以了)以下版本,建议配置Spring6以下版本,或者提高自己jdk的版本。
创建实体类Student
package com.spring.entity;
import lombok.Data;
@Data
public class Student {
private String name;
private long id;
private int age;
}
传统的开发方式,手动new Student
Student student = new Student();
student.setId(1);
student.setName("张三");
student.setAge(22);
System.out.println(student);
通过IoC创建对象, 在配置文件中添加需要管理的对象,XML格式的配置文件,文件名可自定义
<?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
">
<bean id="student" class="com.spring.entity.Student">
<property name="id" value="1"></property>
<property name="name" value="zhangsan"></property>
<property name="age" value="22"></property>
</bean>
</beans>
从IoC中获取对象, 通过id获取
// 加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
3. 配置文件
通过配置bean
标签来完成对象的管理
id
: 对象名。class
: 对象的模板类(所有交给IoC容器来的管理的类必须有无参构造函数,因为 Spring 底层是通过反射机制来创建对象,调用的是无参构造)
对象的成员变量通过property
标签完成赋值
name
: 成员变量名。value
: 成员变量值(基本数据类型,String 可以直接赋值, 如果是其他的引用类型,不能通过 value 赋值)ref
: 将IoC中的另外一个bean赋给当前的成员变量(DI)
<bean id="student" class="com.spring.entity.Student">
<property name="id" value="1"></property>
<property name="name" value="zhangsan"></property>
<property name="age" value="22"></property>
<property name="address" ref="address"></property>
</bean>
<bean id="address" class="com.spring.entity.Address">
<property name="id" value="12"></property>
<property name="name" value="甘肃"></property>
</bean>
4. IoC底层原理
- 这里自己实现IoC的底层,首先定义在pom.xml中配置:
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>你自己的dom4j版本</version>
</dependency>
- 创建以下目录:
- 首先实现
ApplicationContext
接口:
public interface ApplicationContext {
public Object getBean(String id);
}
- 接着实现
ClassPathXmlApplicationContext
类:
package com.spring.ioc;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class ClassPathXmlApplicationContext implements ApplicationContext{
private Map<String, Object> ioc = new HashMap<>();
public ClassPathXmlApplicationContext(String path) throws Exception {
SAXReader reader = new SAXReader();
// 将xml文件转为document对象然后进行读取
Document document = reader.read("./src/main/resources/" + path);
Element root = document.getRootElement(); // 这里拿到beans
// 迭代器读取
Iterator<Element> iterator = root.elementIterator();
while(iterator.hasNext()){
Element element = iterator.next(); // 拿到的是bean
String id = element.attributeValue("id");
String className = element.attributeValue("class");
// 通过反射机制创建对象
Class clazz = Class.forName(className);
// 获取无参构造函数
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
ioc.put(id, object);
}
}
@Override
public Object getBean(String id) {
return ioc.get(id);
}
}
- 最后实现
Test
类:
import com.spring.entity.Student;
public class Test {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
}
}
注意:这里Test中的 ApplicationContext 接口以及 ClassPathXmlApplicationContext 类一定是自己刚才写的,而不是 org.springframework.context 包中的。
- 运行结果比较:
我们主要实现的是无参构造的结构,对于数据处理并没有进行实现,所以是没有数据的。
读取配置文件
通过反射机制实例化配置文件中所配置所有的bean。
- 以下是有参构造的底层实现
package com.spring.ioc;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class ClassPathXmlApplicationContext implements ApplicationContext{
private Map<String, Object> ioc = new HashMap<>();
public ClassPathXmlApplicationContext(String path) throws Exception {
SAXReader reader = new SAXReader();
// 将xml文件转为document对象然后进行读取
Document document = reader.read("./src/main/resources/" + path);
Element root = document.getRootElement(); // 这里拿到beans
// 迭代器读取
Iterator<Element> iterator = root.elementIterator();
while(iterator.hasNext()){
Element element = iterator.next(); // 拿到的是bean
String id = element.attributeValue("id");
String className = element.attributeValue("class");
// 通过反射机制创建对象
Class clazz = Class.forName(className);
// 获取无参构造函数, 创建目标函数
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
// 给目标函数赋值
Iterator<Element> beanIter = element.elementIterator();
while(beanIter.hasNext()){
Element property = beanIter.next();
String name = property.attributeValue("name");
String valueStr = property.attributeValue("value");
String ref = property.attributeValue("ref");
if(ref == null){
String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
Field field = clazz.getDeclaredField(name);
Method method = clazz.getDeclaredMethod(methodName, field.getType());
System.out.println(field.getType().getName());
//根据成员变量的数据类型将value进行转换
Object value = null;
if(field.getType().getName() == "long"){
value = Long.parseLong(valueStr);
}
if(field.getType().getName() == "java.lang.String"){
value = valueStr;
}
if(field.getType().getName() == "int"){
value = Integer.parseInt(valueStr);
}
method.invoke(object, value);
}
ioc.put(id, object);
}
}
}
@Override
public Object getBean(String id) {
return ioc.get(id);
}
}
这里只需要改变`ClassPathXmlApplicationContext`类即可,其他写法与上面的无参构造相同。
- 运行结果
对于address是另一个bean,获取其中的信息与上述相同,只需要拿到信息之后添加到student之中即可,不做演示。
通过用时类来获取bean
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean(Student.class);
System.out.println(student);
这种方式存在一个问题, 配置文件中一个数据类型的对象只能由一个实例,即在bean中只能有一个class为Student:
<bean id="student" class="com.spring.entity.Student">
如果定义多个:
程序在运行的时候就会发现有两个Student类的bean,就会出现错误:
通过有参构造bean
- 在实体类中创建对应的有参构造函数
package com.spring.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor // 生成有参构造
@NoArgsConstructor // 生成无参构造
public class Student {
private long id;
private String name;
private int age;
private Address address;
}
- 配置文件(通过name)
<bean id="student3" class="com.spring.entity.Student">
<constructor-arg name="id" value="3"/>
<constructor-arg name="name" value="王五"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="address" ref="address"/>
</bean>
这里的name可以省略,但是前提是在赋值的顺序必须和实体类中的命名顺序相同,不然会出选错误
- 通过下标进行赋值(index)
<bean id="student3" class="com.spring.entity.Student">
<constructor-arg index="0" value="3"/>
<constructor-arg index="2" value="18"/>
<constructor-arg index="1" value="王五"/>
<constructor-arg index="3" ref="address"/>
</bean>
给bean注入集合
<bean id="student" class="com.spring.entity.Student">
<property name="id" value="1"/>
<property name="name" value="zhangsan"/>
<property name="age" value="22"/>
<property name="addresses">
<list>
<ref bean="address"/>
<ref bean="address2"/>
</list>
</property>
</bean>
<bean id="address" class="com.spring.entity.Address">
<property name="id" value="12"/>
<property name="name" value="甘肃"/>
</bean>
<bean id="address2" class="com.spring.entity.Address">
<property name="id" value="24"/>
<property name="name" value="浙江"/>
</bean>
5. scope 作用域
Spring 管理的备案时根据scope来生成的, 表示bean的作用域,共有四种,默认是singleton模式:
1. singleton: 单例, 表示通过 IoC 容器获取的 bean 是唯一的;
以下是演示:
将scope设置为singleton模式(不设置也可以,默认就是singleton):
以下是运行结果:
singleton模式就相当于getBean
方法在获取student2的时候,IoC中只有一个student2类,每一次IoC都会复制一份交给Bean方法,所以每个student2的地址是一样的,因此比较结果也是相同的。
2. prototype: 原型,表示通过 IoC 容器获取的 bean 是不同的;
以下是演示:
运行结果:
prototype模式就相当于
getBean
方法在获取student2的时候,IoC中只有一个student2类,IoC会将这个类交给Bean方法,然后再获取的时候IoC会重新生成一个student2类,所以每个student2的地址是不一样的,因此结果也不相同。
3. request: 请求,表示再一次 HTTP 请求内有效;
4. session: 会话,表示在一个用户会话内有效。
request和session值适用于Web项目,大多数情况下,使用单例和原型较多。
prototype 模式当业务代码获取 IoC 容器中的 bean 时, Spring 才去调用无参构造创建对应的 bean 。
singleton 模式无论业务代码是否获取 IoC 容器中的 bean,Spring 在加载 spring.xml 时都会创建 bean。
6. Spring 的继承
与Java 的继承不同 ,Java 是类层面的继承, 子类可以继承父类的内部结构信息; Spring 是对象层面的继承, 子对象可以继承父对象的属性值。
<bean id="student2" class="com.spring.entity.Student" scope="prototype">
<property name="id" value="2"/>
<property name="name" value="李四"/>
<property name="addresses">
<list>
<ref bean="address"/>
<ref bean="address2"/>
</list>
</property>
</bean>
<bean id="address" class="com.spring.entity.Address">
<property name="id" value="12"/>
<property name="name" value="甘肃"/>
</bean>
<bean id="address2" class="com.spring.entity.Address">
<property name="id" value="24"/>
<property name="name" value="浙江"/>
</bean>
<!--stu继承student2, 将student2中的所有值都继承, 修改子对象的属性值可以完成继承-->
<bean id="stu" class="com.spring.entity.Student" parent="student2">
<property name="id" value="5"/>
<property name="name" value="王五"/>
</bean>
<bean id="user" class="com.spring.entity.User" parent="address"></bean>
Spring的继承关注点在于具体的对象,而不在于类,即不同的两个类的实例化对象可以完成继承,前提是子对象必须包含父对象的所有属性,同时可以在此基础上添加其他属性(属性的类型必须也相同)。
7. Spring的依赖
与继承相似, 依赖也是描述 bean 和 bean 之间的一种关系, 配置依赖之后,被依赖的 bean 一定先创建,在创建依赖的 bean ,A 依赖于 B , 先创建 B ,再创建 A 。
- 先在Student和User类中创建无参构造:
public User(){
System.out.println("创建了User");
}
- 创建Spring-depend.xml文件:
<?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">
<bean id="student" class="com.spring.entity.Student" depends-on="user"></bean>
<bean id="user" class="com.spring.entity.User"></bean>
</beans>
- 创建 Test2.java 文件,获取Spring-depend.xml 文件:
import org.springframework.context.ApplicationContext; // 一定要是spring自带的包,而不是前面自己写的包!!!
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test2 {
public static void main(String[] args) throws Exception{
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Spring-depend.xml");
}
}
如果不加depend-on属性,IoC默认是从上到下创建:
加上depend-on属性之后,student 依赖于 user 类,创建的时候先创建 user ,再创建 student :
因为前面教大家手动实现过IoC的底层原理,但是并没有实现依赖,在这里的 Test2.java 中使用的时候一定要使用 Spring 包自带的,不能导自己写的,别问我怎么知道的,问就是自己导错了,导成自己的包了,查了半天!!!
8. Spring 的 P 命名空间
p 命名空间是对 IoC / DI 的简化操作,使用 p 命名空间可以更加方便地完成 bean 的配置以及 bean 的依赖注入。
<?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:p="http://www.springframework.org/schema/p"
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">
<bean id="student" class="com.spring.entity.Student" p:id="1" p:name="张三" p:age="18" p:address-ref="address"></bean>
<!--p:name 就相当于 <property name="id" value="1"/>-->
<bean id="address" class="com.spring.entity.Address" p:id="2" p:name="和谐路"></bean>
</beans>
9. Spring 的工厂方法
IoC通过工厂模式创建bean的方式有两种:
- 静态工厂方法:
- 实例工厂方法:
静态工厂方法:
- 创建实体类:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
private long id;
private String name;
}
- 创建静态工厂方法:
import com.spring.entity.Car;
import java.util.HashMap;
import java.util.Map;
public class StaticCarFactory {
private static Map<Long, Car> carMap;
static{
carMap = new HashMap<Long, Car>();
carMap.put(1L, new Car(1L, "宝马"));
carMap.put(2L, new Car(2L, "奔驰"));
}
public static Car getCar(long id){
return carMap.get(id);
}
}
- 配置文件:
<?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:p="http://www.springframework.org/schema/p"
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">
<!-- 配置静态工厂创建 Car -->
<bean id="car" class="com.spring.factory.StaticCarFactory" factory-method="getCar">
<constructor-arg value="2"/>
</bean>
</beans>
- 调用:
public class Test4 {
public static void main(String[] args){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-factory.xml");
Car car = (Car)applicationContext.getBean("car");
System.out.println(car);
}
}
实例工厂方法:
- 创建实体类(同上):
- 创建实例工厂方法:
import com.spring.entity.Car;
import java.util.HashMap;
import java.util.Map;
public class InstanceCarFactory {
private static Map<Long, Car> carMap;
// 在无参构造创建map集合
public InstanceCarFactory(){
carMap = new HashMap<Long, Car>();
carMap.put(1L, new Car(1L,"宝马"));
carMap.put(2L, new Car(2L,"奔驰"));
}
public Car getCar(Long id){
return carMap.get(id);
}
}
- 配置文件:
<!-- 配置实例工厂 bean -->
<bean id="carFactory" class="com.spring.factory.InstanceCarFactory"></bean>
<!-- 配置实例工厂创建 Car -->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="2"/>
</bean>
- 调用(同上):
10. IoC 自动装载(Autowire)
IoC 负责创建对象, DI 负责完成对象的依赖注入, 通过配置 property 标签的 ref 属性来完成, 同时 Spring 提供了另外一种更加简便的依赖注入方式:自动装载(不需要手动配置 property ,IoC 容器会自动选择 bean 完成注入。)
自动装在有两种方式:
- byName: 通过属性名自动装载;
- byType: 通过属性的数据类型自动装载。
byName:
- 创建实体类:
@Data
public class Person {
private int id;
private String name;
private Car car;
}
- 配置文件:
原理: 在配置 person 文件中使用了 autowire = “byName” 的方法,因为 person 具有 id ,name ,和 car 三个属性,其中 id 和 name 属性我们通过 property 实现了手动配置,剩余一个 car ,所以 IoC 会在自己的容器中去寻找一个 id 为 car 的 bean 标签,找到之后会将其自动装载在 person 中,如果未找到,则显示 null ;
byType:
- 创建实体类(同上):
- 配置文件:
原理: 在配置 person 文件中使用了 autowire = “byName” 的方法,因为 person 具有 id ,name ,和 car 三个属性,其中 id 和 name 属性我们通过 property 实现了手动配置,剩余一个 car ,所以 IoC 会在自己的容器中去寻找一个 class为 Car 的 bean 标签(但是只能有一个 Car 类型,不然会抛出异常),找到之后会将其自动装载在 person 中,如果未找到,则显示 null ;