学习内容:
- Spring概述
- Spring优点
- 如何使用IOC
- IOC底层原理
- IOC特性
- Spring的工程方法
- IOC的自动装载
学习笔记:
1. Spring概述
-
Spring是什么?
- Spring是一个企业级开发框架,是软件设计层面的框架,优势在于可以将应用程序进行分层,开发者可以自主选择组件。
- Spring已经成为Java领域的行业标准
- Spring全家桶:
- Web:Spring Web MVC/Spring MVC 、Spring Web Flux
- 持久层:Spring Data/Spring Data JPA 、Spring Data Redis、 Spring Data MongoDB
- 安全校验:Spring Security
- 建构工程脚手架:Spring Boot
- 微服务:Spring Cloud
-
Spring两大核心机制:
- IOC(控制反转)/DI(依赖注入)
IOC是Spring全家桶各个功能模块的基础,是创建对象的容器
- AOP(面向切面编程)
2. Spring的优点:
- 低侵入式设计
使得代码污染性极低,应用程序对Spring API的依赖可以减至最小限度
- 独立于各种应用服务器
- 依赖注入特性将组件关系透明化,降低了耦合度。
Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护工作都交给Spring容器的管理,大大的降低了组件之间的耦合性。
- 支持AOP
面向切面编程特性允许将通用任务如安全、事物、日志等进行集中式处理,从而提高了程序的复用性。
- 与第三方框架的良好整合
Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如Struts、Hibernate、MyBatis、Quartz等)的直接支持
3.如何使用IOC
在使用之前我们需要了解什么叫做控制反转:
在传统的程序开发中,需要调用对象时,通常由调用者来创建被调用者的实例,即对象是由调用者主动new出来的,而在Spring框架中创建对象的工作时不再由调用者完成,而是交由IOC容器来创建,再推送给调用者。
如何使用IOC:
- 创建Maven工程,pom.xml添加依赖
pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
- 创建实体类Student
Student.java
package com.hbb.entity;
import lombok.Data;
@Data
public class Student {
private long id;
private String name;
private int age;
}
- 传统的开发方式,手动new
StudentTest.java
package com.hbb.test;
import com.hbb.entity.Student;
public class StudentTest {
public static void main(String[] args) {
Student sdt = new Student();
sdt.setId(1L);
sdt.setAge(20);
sdt.setName("葛小蕾");
System.out.println(sdt);
}
}
此处需要导入lombok的jar包,用来简化实体类的开发
需要首先从网上下载lombok-1.18.6.jar版本,配置到pom.xml里
配置好后找到jar的本地位置,通过CMD命令执行安装。
命令:java -jar lombok-1.18.6.jar
配置好后,对于实体类,不需要再实装set/get方法和toString方法,即可直接调用。
添加方法:
pom.xml
<!-- 简化实体类的开发 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
- 通过IOC创建对象,在配置文件中添加需要管理的对象,XML格式的配置文件,文件名可自定义
关于配置文件:
- 通过配置bean标签来完成对象管理
- id ≈ 对象名
- class ≈ 对象的模板类(所有交给IOC容器来管理的类必须有无参构造函数,因为Spring底层是通过反射机制来创建对象。调用的是无参构造)
- 对象的成员变量通过property标签完成赋值
- name:成员变量名
- value:成员变量值(基本数据类型,String可以直接赋值,如果是其他引用类型,不能通过value赋值)
- ref: 将IOC中的另外一个bean赋给当前的成员变量(DI)
Spring.xml(resources目录下)
<!-- 通过ID来区分bean,名字可以自定义 -->
<bean id="student" class="com.hbb.entity.Student">
<property name="id" value="11"></property>
<property name="age" value="20"></property>
<property name="name" value="杨大宁"></property>
</bean>
(如果有其他引用类型:)
Address.java
public class Address {
private long id;
private String name;
}
Student.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private long id;
private String name;
private int age;
private Address address;
}
spring.xml
<bean id="student" class="com.hbb.entity.Student">
<property name="id" value="11"></property>
<property name="age" value="20"></property>
<property name="name" value="葛小蕾"></property>
<property name="address" ref="address"></property>
</bean>
<bean id="address" class="com.hbb.entity.Address">
<property name="id" value="2022"></property>
<property name="name" value="在淮安"></property>
</bean>
- 从IOC中获取对象
StudentTest.java
// 加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
如果没有无参构造方法,运行时会报以下错误:
Causedby:java.lang.NoSuchMethodException:com.hbb.entity.Student.init()
补充:lombok的@AllArgsConstructor标签可用来添加有参构造方法
结果:
Student(id=11, name=葛小蕾, age=20,address=Address(id=2022, name=在淮安))
4. IOC底层原理(极简略版)
- 读取配置文件,解析XMl
- 通过反射机制实例化配置文件中所有的bean
5. IOC特性
- 给bean注入集合
Student.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private long id;
private String name;
private int age;
private List<Address> addresses;
}
spring.xml
<bean id="student" class="com.hbb.entity.Student">
<property name="id" value="11"></property>
<property name="age" value="20"></property>
<property name="name" value="葛小蕾"></property>
<property name="addresses">
<list>
<ref bean="address"></ref>
<ref bean="address2"></ref>
</list>
</property>
</bean>
<bean id="address" class="com.hbb.entity.Address">
<property name="id" value="2022"></property>
<property name="name" value="在淮安"></property>
</bean>
<bean id="address2" class="com.hbb.entity.Address">
<property name="id" value="2012"></property>
<property name="name" value="世界末日之前"></property>
</bean>
- scope作用域
Spring管理的bean是根据scope来生成的,表示bean的作用域,共有四种:
- singleton:单例,表示通过Spring容器获取的bean是唯一的
- prototype:原型,表示通过Spring容器获取的bean是不同的
- request:请求,表示在一次http请求有效
- session:会话,表示在一个用户会话内有效
request和session只适用于web项目,大多数情况下,使用单例和原型较多
- prototype模式当业务代码获取IOC容器中的bean时,Spring才去调用无参构造创建对应的bean
- singleton模式无论业务代码是否获取IOC容器中的bean,Spring在Spring.xml时就会创建bean
- Spring的继承
和Java中的继承不同,Java是类层面的继承,子类可以继承父类的内部结构信息;Spring是对象层面的继承,子对象可以继承父对象的属性值
<bean id="student2" class="com.hbb.entity.Student">
<property name="id" value="11"></property>
<property name="age" value="20"></property>
<property name="name" value="葛小蕾"></property>
<property name="addresses">
<list>
<ref bean="address"></ref>
<ref bean="address2"></ref>
</list>
</property>
</bean>
<bean id="student3" class="com.hbb.entity.Student" parent="student2">
<property name="name" value="小葛"></property>
</bean>
输出结果:
运行结果:
Student(id=11, name=小葛, age=20, addresses=[Address(id=2022,name=在淮安), Address(id=2012, name=世界末日之前)])
Spring的继承关注点在于重点的对象,而不在于类,即不同的两个类的实例化对象可以完成继承,前提是子对象必须包含父对象的所有属性,同时可以在此基础上添加其他的属性
- Spring的依赖
和继承类似,依赖也是描述bean和bean之间的一种关系,配置依赖之后,被依赖的bean一定县创建,再创建依赖的bean,A依赖于B,先创建B,再创建A。
- Spring的p命名空间
p命名空间是对IoC/DI的简化操作,使用p命名空间可以更加方便的完成bean的配置以及bean之间的依赖注入关系。
头文件中包含:
xmlns:p=http://www.springframework.org/schema/p
通过p命名空间配置属性:
spring.xml
<bean id="student" class="com.hbb.entity.Student" p:id="101" p:age="30" p:name="创造" p:addresses-ref="address"></bean>
<bean id="address" class="com.hbb.entity.Address" p:id="1314" p:name="再爱你"></bean>
输出结果:
Student(id=101, name=创造, age=30, addresses=[Address(id=1314, name=再爱你)])
6.Spring的工厂方法:
IOC通过工厂模式创建bean的方法有两种:
- 静态工厂方法
创建实体类Cat.java
package com.hbb.Ioc;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Cat {
private int age;
private String name;
}
创建静态工厂类StaticCatFactory .java
package com.hbb.factory;
import java.util.HashMap;
import java.util.Map;
import com.hbb.Ioc.Cat;
public class StaticCatFactory {
private static Map<Long, Cat> catMap;
static {
catMap = new HashMap<Long, Cat>();
catMap.put(1L, new Cat(11, "小葛"));
catMap.put(2L, new Cat(22, "小杨"));
}
public static Cat getCat(int age) {
return catMap.get(age);
}
}
在static静态代码块中进行catMap的实例化。在类被加载时即可被调用。
在spring.xml里配置静态工厂
<!-- 配置静态工厂,创建cat -->
<bean id="cat" class="com.hbb.factory.StaticCatFactory" factory-method="getCat">
<constructor-arg value="2"></constructor-arg>
</bean>
测试类StaticCatFactoryTest.java
package com.hbb.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.hbb.Ioc.Cat;
public class StaticCatFactoryTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Cat cat = (Cat) applicationContext.getBean("cat");
System.out.println(cat);
}
}
- 实例工厂方法
实例工厂类InstanceCatFactory .java
package com.hbb.factory;
import java.util.HashMap;
import java.util.Map;
import com.hbb.Ioc.Cat;
public class InstanceCatFactory {
private static Map<Long, Cat> catMap;
public InstanceCarFactory() {
catMap = new HashMap<Long, Cat>();
catMap.put(1L, new Cat(11, "小葛"));
catMap.put(2L, new Cat(22, "小杨"));
}
public Cat getCat(int age) {
return catMap.get(age);
}
}
在无参构造方法中进行catMap的实例化。当实例工厂类实例化时即可被调用
在spring.xml里配置实例工厂
<!-- 配置实例工厂bean -->
<bean id="catFactory" class="com.hbb.factory.InstanceCatFactory">
</bean>
<!-- 配置实例工厂,创建cat -->
<bean id="cat2" factory-bean="catFactory" factory-method="getCat">
<constructor-arg value="1"></constructor-arg>
</bean>
实例工厂的方式时,需要先配置实例工厂bean,和配置普通bean的方式是一样的。然后再配置实例工厂,创建car。通过factory-bean将cat2依赖于实例工厂catFactory
测试类CatFactoryTest.java
public class CatFactoryTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Cat cat = (Cat) applicationContext.getBean("cat2");
System.out.println(cat);
}
7. IOC的自动装载
IoC负责创建对象,DI负责完成对象的依赖注入,通过配置property标签的ref属性来完成,同时Spring提供了另外一种更加简便的依赖注入方式:
自动装载——不需要手动配置property,IoC容器会自动选择bean完成注入。
自动装载有两种方式:
- byname:通过属性名自动装载
- byType:通过属性的数据类型自动装载
创建实体类Car.java
package com.hbb.entity;
import lombok.Data;
@Data
public class Car {
private long id;
private String name;
}
创建实体类Person.java
package com.hbb.entity;
import com.hbb.entity.Car;
import lombok.Data;
@Data
public class Person {
private long id;
private String name;
private Car car;
}
测试类PersonTest,java
package com.hbb.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.hbb.entity.Person;
public class PersonTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
}
}
byName的方式如下(autowire=“byName”):
在spring.xml中配置bean
<bean id="car" class="com.hbb.entity.Car">
<property name="id" value="1"></property>
<property name="name" value="玛莎拉蒂"></property>
</bean>
<bean id="person" class="com.hbb.entity.Person" autowire="byName">
<property name="id" value="11"></property>
<property name="name" value="小葛"></property>
</bean>
结果:
Person(id=11, name=小红, car=Car(id=1, name=玛莎拉蒂))
通过名字进行装载。将注入的事情交给IoC容器来完成。
IoC容器寻找名字是car的bean,找到后,自行装载给Person。
如果找不到的话,car的值是null。(不进行装载)
byType的方式(autowire=“byType”):
在spring.xml中配置bean
<bean id="car2" class="com.hbb.entity.Car">
<property name="id" value="1"></property>
<property name="name" value="玛莎拉蒂"></property>
</bean>
<bean id="person" class="com.hbb.entity.Person" autowire="byType">
<property name="id" value="11"></property>
<property name="name" value="小葛"></property>
</bean>
结果与byName的结果相同
byType的方式,是通过类型来进行匹配的,只要bean的类型和实体类(Person.java)中成员变量的类型(Car)是一致的,就可以进行装载。
但是使用byType会存在一个问题:
如果配置文件spring.xml里同时有两个及以上的符合条件的bean时,自动装载会抛出异常(没有唯一的bean)。