你真的了解Spring吗(全网最全Spring笔记上)
一、工厂设计模式
1.1、传统的容器——EJB的缺点
EJB(Enterprise Java Beans),被称为企业Java Beans。他是上一代使用的容器。我们来看看传统的J2EE的体系。
EJB具有的缺点是很致命的:
- 运行环境苛刻。
- 代码移植性很差。
- EJB是重量级框架。
1.2、什么是Spring
Spring是一个轻量级的JavaEE解决方案,整合众多优秀的设计模式,其中最重要的设计模式是——工厂设计模式。他还包含其他的设计模式,比如说:代理设计模式、模板设计模式、策略设计模式等等。
1.3、什么是工厂设计模式
在传统的创建对象的时候,我们都是调用无参构造函数来创建对象的即new的方式来创建,这样创建对象的方式的耦合程度(指定是代码间的强关联关系,一方的改变会影响到另一方)就十分高。、
一旦我们需要修改类型,就需要代码中修改,并且重新编译和部署。
1.4、工厂设计模式的实现
package com.factory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
private static Properties env = new Properties();
static{
try {
//第一步 获得IO输入流
InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
//第二步 文件内容 封装 Properties集合中 key = userService ,value = com.service.impl.UserServiceImpl
env.load(inputStream);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/*
对象的创建的两种方式:
1. 直接调用构造方法创建对象 UserService userService = new UserServiceImpl();
2. 通过反射的形式创建对象可以解耦合
Class clazz = Class.forName("com.service.impl.UserServiceImpl");
UserService userService = (UserService)clazz.newInstance();
*/
public static UserService getUserService() {
UserService userService = null;
try {
Class clazz = Class.forName(env.getProperty("userService"));
userService = (UserService) clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return userService;
}
public static UserDAO getUserDAO(){
UserDAO userDAO = null;
try {
Class clazz = Class.forName(env.getProperty("userDAO"));
userDAO = (UserDAO) clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return userDAO;
}
}
userDAO = com.dao.userDAO
userService = com.service.userService
1.5、简单工厂的代码修改
我们可以发现,上面一个工厂设计模式的代码是又臭又长,我们每创建一个新的对象,都需要重新写一个工厂类,并且代码很大一部分都是相同的,仅仅只是创建的对象不同,于是我们可以抽取出共同的代码,组成一个通用的工厂类。
public class BeanFactory{
public static Object getBean(String key){
Object ret = null;
try {
Class clazz = Class.forName(env.getProperty(key));
ret = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
}
1.6、总结
Spring本质上就是一个工厂,只不过我们在日常开发的时候使用的不是自己写的工厂,因为这个工厂的功能很少,性能很低下,Spring帮我们写好了一个大型工厂(ApplicationContext),我们只需要在一个固定的配置文件(applicationContext.xml)中进行配置即可。
二、Spring入门
2.1、Spring简介
Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。Spring 是可以在 Java SE/EE 中使用的轻量级开源框架。
Spring 的主要作用就是为代码“解耦”,降低代码间的耦合度。就是让对象和对象(模块和模块)之间关系不是使用代码关联,而是通过配置来说明。即在 Spring 中说明对象(模块)的关系。
Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。IoC 使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring容器统一管理,自动“注入”,注入即赋值。 而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由 Spring 容器统一完成“织入”。
2.2、Spring的优点
Spring 是一个框架,是一个半成品的软件。由 20 个模块组成。它是一个容器管理对象,容器是装东西的,Spring 容器不装文本,数字。装的是对象。Spring 是存储对象的容器。他的优点主要是以下几个方面。
- 轻量。
- 面向接口编程。
- 面向切面编程
- 可以轻易集成其他优秀的框架。
2.2.1、轻量
Spring 框架使用的 jar 都比较小,一般在 1M 以下或者几百 kb。Spring 核心功能的所需的 jar 总共在 3M 左右。 Spring 框架运行占用的资源少,运行效率高。不依赖其他 jar。
2.2.2、面向接口编程
Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合。
2.2.3、面向切面编程(AOP)
通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。 在 Spring 中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
2.2.4、集成其他优秀的框架
Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如 Shiro、MyBatis)等的直接支持。简化框架的使用。 Spring 像插线板一样,其他框架是插头,可以容易的组合到一起。需要使用哪个框架,就把这个插头放入插线板。不需要可以轻易的移除。
2.3、Spring的体系结构
Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、Web、面向切面编程(AOP, Aspects)、提供 JVM的代理(Instrumentation)、消息发送(Messaging)、核心容器(Core Container)和测试(Test)。
2.4、Spring的核心API
Spring的核心就是一个大工厂:ApplicationContext,他的作用是用于对象的创建,且可以解除耦合,他是一个接口,但是ApplicationContext是一个重量级的工厂对象占用大量的内存,所以我们不会频繁得去创建对象,一般一个应用只会创建一个工厂对象。ApplicationContext是线程安全的,可以被多线程并发访问。
他有两种实现方式:
- 适用于非WEB环境的:ClassPathXmlApplication
- 适用于WEB环境的:XmlApplicationContext
2.5、Spring的案例
2.5.1、引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
2.5.3、创建类型
package com.domain;
/**
* @author Xiao_Lin
* @date 2021/2/4 15:57
*/
public class Person {
}
2.5.4、修改配置文件
在applicationContext.xml的配置文件中更改配置
<!-- id属性:名字-->
<!-- class属性:需要创建对象的全限定名-->
<bean id="person" class="com.domain.Person"/>
2.5.5、创建对象
/**
* 用于测试Spring的第一个程序
*/
@Test
public void testSpring(){
// 1. 获得Spring的工厂
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2. 通过工厂类来获得对象
Person person = (Person)applicationContext.getBean("person");
System.out.println(person);
}
2.6、细节分析
2.6.1、名词解释
Spring工厂创建的对象,叫做bean或者组件(componet)
2.6.2、相关方法
//通过这种方式获得对象,就不需要强制类型转换
Person person = ctx.getBean("person", Person.class);
System.out.println("person = " + person);
//当前Spring的配置文件中 只能有一个<bean>标签的class是Person类型
Person person = ctx.getBean(Person.class);
System.out.println("person = " + person);
//获取的是 Spring工厂配置文件中所有bean标签的id值 person person1
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
}
//根据类型获得Spring配置文件中对应的id值
String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);
for (String id : beanNamesForType) {
System.out.println("id = " + id);
}
//用于判断是否存在指定id值的bean
if (ctx.containsBeanDefinition("a")) {
System.out.println("true = " + true);
}else{
System.out.println("false = " + false);
}
//用于判断是否存在指定id(name)值的bean
if (ctx.containsBean("person")) {
System.out.println("true = " + true);
}else{
System.out.println("false = " + false);
}
2.7、Spring创建对象的简易原理图
注意:反射底层调用的是无参构造函数来进行实例化对象的,即使构造方法私有了,依然可以调用进行实例化对象。
2.8、注意
在未来开发的过程中,理论上所有的对象都是交给Spring工厂来创建,但是有一类特殊的对象——实体对象是不会交给Spring来创建的,它是交给持久层来创建的。
三、注入
3.1、什么是注入
注入是指 Spring 创建对象的过程中,将对象依赖属性通过配置设值给该对象。
3.2、为什么需要注入
通过编码的方式(setXxx),为成员变量进行赋值,存在耦合。
3.3、注入的方式
- set注入:其类必须提供对应 setter 方法。
- 构造器注入:利用构造器进行注入。
3.4、set注入
package com.domain;
/**
* @author Xiao_Lin
* @date 2021/2/4 15:57
*/
public class Person {
private String username;
private Integer password;
@Override
public String toString() {
return "Person{" +
"username='" + username + '\'' +
", password=" + password +
'}';
}
public Person(String username, Integer password) {
this.username = username;
this.password = password;
}
public Person() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getPassword() {
return password;
}
public void setPassword(Integer password) {
this.password = password;
}
}
<bean id="person" class="com.domain.Person">
<property name="username">
<value>Xiao_Lin</value>
</property>
<property name="password">
<value>123456</value>
</property>
</bean>
/**
* 用于测试注入
*/
@Test
public void testDI(){
ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = application.getBean("person", Person.class);
System.out.println(person);
}
3.4.1、set注入的原理图
Spring通过底层调用对象属性对应的set方法完成对成员变量的赋值操作。
3.4.2、set注入详解
针对不同的不同类型的成员变量,我们不可能一直是使用value
标签,我们需要嵌套其他的标签,我们将成员变量可能的类型分类两大类:
- JDK内置类型。
- 用户自定义类型。
3.4.2.1、JDK内置类型
3.4.2.1.1、String+8种基本数据类型
都直接使用value
标签即可
<property name="password">
<value>123456</value>
</property>
3.4.2.1.2、数组类型
对于数组类型,我们需要在配置文件中,使用list
标签,表明是数组类型,嵌套value
标签来进行赋值。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String[] emails;
}
<property name="emails">
<list>
<value>124@qq.com</value>
<value>456@163.com</value>
</list>
</property>
3.4.2.1.3、Set集合
对于set集合类型,我们需要在配置文件中,使用set
标签,表明是set
集合类型,嵌套Set泛型中对应的标签来进行赋值。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private Set<String> tels;
}
<property name="tels">
<set>
<value>123456</value>
<value>456789</value>
<value>13579</value>
</set>
</property>
对于set集合由于我们规范了泛型为String,她是8种基本数据类型,所以在set标签中才嵌套value标签。如果没有规定泛型或者说是规定了其他的泛型,set嵌套的标签需要根据具体的情况来具体分析。
3.4.2.1.4、List集合
对于List集合类型,我们需要在配置文件中,使用list
标签,表明是List集合类型,嵌套List泛型中对应的标签来进行赋值。
list便签中嵌套什么标签,取决于List集合中的泛型。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private List<String> address;
}
<property name="address">
<list>
<value>sz</value>
<value>sz</value>
<value>gz</value>
</list>
</property>
3.4.2.1.5、Map集合
对于Map集合,有一个内部类——Entry,所以我们在配置文件中需要使用的标签是用map
标签来嵌套entry
标签,里面是封装了一对键值对。我们使用key
标签来表示键,里面嵌套键对应的标签,值要根据对应的类型来选择对应的标签。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private Map<String,String> qq;
}
<property name="qq">
<map>
<entry>
<key><value>zs</value></key>
<value>123456</value>
</entry>
<entry>
<key><value>lisi</value></key>
<value>456789</value>
</entry>
</map>
</property>
3.4.2.1.6、Properties集合
Properties类似是特殊的Map,他的key
和value
都必须是String
类型。
在配置文件中,我们使用props
标签,里面嵌套prop
标签,一个prop
就是一个键值对,键写在key
属性中,值写在标签内部。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private Properties properties;
}
<property name="properties">
<props>
<prop key="username">admin</prop>
<prop key="password">123456</prop>
</props>
</property>
3.4.2.2、自定义类型
3.4.2.2.1、第一种注入方式
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hobby {
private String name;
}
<property name="hobby">
<bean class="com.domain.Hobby"/>
</property>
我们可以发现第一种注入方式其实就是在property
标签里面写一个bean
标签,他的劣势也很明显:
- 配置文件代码冗余,我有一万个类需要引用同一个对象的时候,我同一段代码需要写一万次。
- 被注入的对象,被多次创建,浪费(JVM)内存资源,因为我每写一个
bean
标签意味着就创建一个新的对象。
3.4.2.2.2、第二种注入方式
鉴于第一种注入方式的缺点很明显,我们就需要改进,于是就有了第二种注入方式,这种方式是将我们需要注入的对象提前先创建一份出来,谁需要谁去引用即可。
<bean>
<property name="hobby">
<ref bean="hobby"/>
</property>
</bean>
<bean id="hobby" class="com.domain.Hobby">
<property name="name">
<value>admin</value>
</property>
</bean>
3.4.3、set注入的简化写法
3.4.3.1、基于属性的简化
JDK类型注入
我们可以使用value
属性来简化value
标签的值,但是只可以简化8种基本数据类型➕Stirng类型的值。
<!--以前的方式-->
<property name="name">
<value>Xiao_Lin</value>
</property>
<!--简化后的方式-->
<property name="name" value="Xiao_Lin"/>
用户自定义类型的注入
我们可以使用ref属性来简化ref
标签的值.
<!--以前的方式-->
<property name="hobby">
<ref bean="hobby"/>
</property>
<!--简化后的方式-->
<property name="hobby" ref="hobby"/>
3.4.3.2、基于p命名空间的简化
我们可以发现,bean
标签的很多值都是重复且冗余的,于是可以使用p
命名空间来进行简化。
<!--内置数据类型-->
<bean id="person" class="com.domain.Person" p:username="zs" p:password="123456" />
<!--用户自定义类型-->
<bean id="hobbyBean" class="com.domain.Hobby"></bean>
<bean id="hobby" class="com.domain.Person" p:hobby-ref="hobbyBean"
3.5、构造注入
Spring调用构造方法,通过配置文件为成员变量赋值。如果要使用构造注入,必须提供有参的构造方法。
构造注入使用的标签是constructor-arg
标签,一个构造参数就是一对constructor-arg
标签。顺序和个数都必须和构造参数一样。
当出现构造方法重载的时候,我们可以通过控制constructor-arg
的个数来进行控制。如果出现构造参数个数相同的重载的时候(如第一个构造方法是给name赋值,第二个构造方法给type赋值),我们需要用type
属性来指定类型。
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Hobby {
private String name;
private String type;
}
<bean id="hobbyBean" class="com.domain.Hobby">
<constructor-arg>
<value>running</value>
</constructor-arg>
<constructor-arg>
<value>dayily</value>
</constructor-arg>
</bean>
/**
* 用于测试构造注入
*/
@Test
public void testDI2(){
ClassPathXmlApplicationContext cxt =
new ClassPathXmlApplicationContext("/applicationContext.xml");
Hobby hobbyBean = cxt.getBean("hobbyBean", Hobby.class);
System.out.println(hobbyBean);
}
3.6、注入总结
四、控制反转(IOC)和依赖注入(DI)
4.1、控制反转(IOC)
控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理。
简单来说控制反转就是把对于成员变量赋值的控制权,从代码中反转(转移)到Spring工厂和配置文件中完成。
IoC 是一个概念,是一种思想,其实现方式多种多样。Spring 框架使用依赖注入(DI)实现 IoC。
4.2、依赖注入(DI)
依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,即 classA对 classB 有依赖。
依赖注入(Dependency Injection):当一个类需要另一个类时,就可以把另一个类作为本类的成员变量,最终通过Spring的配置文件进行注入(赋值)。简单来说就是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持对象之间依赖关系的管理。
4.3、总结
Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。
转载自:https://juejin.cn/post/6991238154460069919