@[TOC](回望Spring旅程(持续更新))
# 第一章节 Spring之旅
## 1.1 为什么会创建Spring
Spring在诞生之初,主要目的就是为了替代更加重量级的企业级Java技术,提供更加轻量级和简单的编程模型。增强老式Java对象的功能,让他具备了之前只有EJB和其他企业级Java规范才有的功能。
## 1.2 简化Java开发
为了降低Java开发的复杂性,Spring采取了以下4种关键性的策略:
- 基于POJO的轻量级和最小侵入式编程
- 通过依赖注入和面向接口实现了松耦合
- 基于切面和惯例实现声明式编程
- 通过切面和模板减少样板式代码
### 1.2.1 为什么说是基于POJO的轻量级和最小侵入式编程呢?
如何理解这句话?
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602141929292.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01yX0NfcHl0aG9u,size_16,color_FFFFFF,t_70)
如上,即便这个Student对象使用了Spring,但是如果放到一个非Spring的应用中,依旧是可以当作一个POJO对象(但是可能注解会报错,这也是最坏的情况,但依旧不影响他是一个POJO)。
我们都知道Spring的DI是有两种方法,待会会讲到,使用注解后放到另一个没有Spring的程序中,Spring的注解会报错没有导包,但如果是xml注入,完全没有影响,因此这里也说是最坏的情况。
### 1.2.2 通过依赖注入和面向接口实现松耦合是什么意思?
#### 1.2.2.1 依赖注入的两种实现方式
##### 基于xml的依赖注入:
1. Student学生对象:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602141958474.png)
2. 进行依赖注入:applicationContext.xml文件(Spring IOC上下文文件,也叫做IOC容器)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602142012608.png)
3. 测试程序:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602142026358.png)
4. 结果:(sayHello()是Student的一个方法)
> Hello
##### 基于注解的依赖注入:
1. Student学生对象
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602142204563.png)
3. 进行依赖注入:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602142217139.png)
4. 测试:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602142228491.png)
#### 1.2.2.2 什么是松耦合?
##### 传统的对象实例化
```java
Student student = new Student();
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200604210812951.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01yX0NfcHl0aG9u,size_16,color_FFFFFF,t_70)
根据图解,可以知道,在我们创建一个类之后,相应的会在堆内存中的某一个地址,存放这个对象。当我们实例化这个对象时,会从堆内存中把指向这个对象的地址压入栈内存之中。
如果按照传统的```new```实例化对象,如果只是一两个实例那还好,如果某一个大型的项目,岂不是要```new```它个上万次?不占内存吗?不会影响性能吗?有杠精就要说了,怕啥,内存我有的是。
但是你每次都去```new```,项目模块之间的耦合性会不会因此受到影响?为了解决松耦合的问题,就出现了```单例设计模式```
##### 单例设计模式
所谓单例模式,就是保证类在内存中只有一个对象,分为饿汉式、懒汉式
- [x] 饿汉式单例设计模式
```java
public class Fruit {
private String name;
private double price;
//本类实例化自己(private)
private static Fruit fruit = new Fruit();
//私有的构造,为了不让外部实例化
private Fruit(String name, double price) {
super();
this.name = name;
this.price = price;
}
//私有的构造,为了不让外部实例化
private Fruit() {
super();
}
//但是我们要设置一个方法让外部可以调用获取对象
public static Fruit getFruit() {
return fruit;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Fruit [name=" + name + ", price=" + price + "]";
}
}
```
```java
public static void main(String[] args) {
//获取实例化对象
Fruit fruit = Fruit.getFruit();
}
```
- [x] 懒汉式单例设计模式
```java
public class Fruit {
private String name;
private double price;
//本类实例化自己(private)
private static Fruit fruit;
//私有的构造,为了不让外部实例化
private Fruit(String name, double price) {
super();
this.name = name;
this.price = price;
}
//私有的构造,为了不让外部实例化
private Fruit() {
super();
}
//但是我们要设置一个方法让外部可以调用获取对象
public static Fruit getFruit() {
if(fruit==null) {
fruit = new Fruit();
}
return fruit;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Fruit [name=" + name + ", price=" + price + "]";
}
}
```
```java
public static void main(String[] args) {
Fruit fruit1 = Fruit.getFruit();
Fruit fruit2 = Fruit.getFruit();
System.out.println(fruit1==fruit2); //输出结果:true
}
```
> 饿汉式:简单来说就是空间换时间,因为上来就实例化一个对象,占用了内存,(也不管你用还是不用)
懒汉式:简单的来说就是时间换空间,与饿汉式正好相反
##### 工厂设计模式
再后来,设计了工厂设计模式。工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑使用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。
```java
public class Banana {
private String name;
public Banana(String name) {
super();
this.name = name;
}
public Banana() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Apple [name=" + name + "]";
}
}
```
```java
public class Apple {
private String name;
public Apple(String name) {
super();
this.name = name;
}
public Apple() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Apple [name=" + name + "]";
}
}
```
```java
public class Orange {
private String name;
public Orange(String name) {
super();
this.name = name;
}
public Orange() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Apple [name=" + name + "]";
}
}
```
```java
public class FruitFactory {
//工厂方法
public Object getObject(String name) {
if(name=="apple") {
return new Apple();
}
if(name=="banana") {
return new Banana();
}
if(name=="orange") {
return new Orange();
}
return null;
}
}
```
```java
public static void main(String[] args) {
//从工厂获取Apple对象实例
Apple apple = (Apple) new FruitFactory().getObject("apple");
}
```
##### Spring控制反转
我们可以看见单例设计模式下,对象只会被实例化一次,但是我们会发现我们的对象就变得有些许不像是POJO对象了。为了更好的解决松耦合问题,我们引入了```控制反转(依赖注入&依赖查找)```
1.什么是控制反转?
Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。
采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。
可以把IoC模式看作工厂模式的升华,把IoC容器看作是一个大工厂,只不过这个大工厂里要生成的对象都是在XML文件中给出定义的。利用Java 的“反射”编程,根据XML中给出的类定义生成相应的对象。从实现来看,以前在工厂模式里写死了的对象,IoC模式改为配置XML文件,这就把工厂和要生成的对象两者隔离,极大提高了灵活性和可维护性。
IoC中最基本的Java技术就是“反射”编程。通俗的说,反射就是根据给出的类名(字符串)来生成对象。这种编程方式可以让应用在运行时才动态决定生成哪一种对象。反射的应用是很广泛的,像Hibernate、Spring中都是用“反射”做为最基本的技术手段。
在过去,反射编程方式相对于正常的对象生成方式要慢10几倍,这也许也是当时为什么反射技术没有普遍应用开来的原因。但经SUN改良优化后,反射方式生成对象和通常对象生成方式,速度已经相差不大了(但依然有一倍以上的差距)。
2、SpringIOC容器形式
IoC是一个很大的概念,可以用不同的方式实现。其主要形式有两种:
**依赖查找:** 容器提供回调接口和上下文条件给组件。EJB和Apache Avalon 都使用这种方式。这样一来,组件就必须使用容器提供的API来查找资源和协作对象,仅有的控制反转只体现在那些回调方法上(也就是上面所说的 类型1):容器将调用这些回调方法,从而让应用代码获得相关资源。
**依赖注入:** 组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。容器全权负责的组件的装配,它会把符合依赖关系的对象通过JavaBean属性或者构造函数传递给需要的对象。通过JavaBean属性注射依赖关系的做法称为设值方法注入(Setter Injection);将依赖关系作为构造函数参数传入的做法称为构造器注入(Constructor Injection)
### 1.2.3 依赖注入
==Student类==
```java
package day0610.pojo;
public class Student {
private String sname,sno;
public Student() {
super();
}
public Student(String sname, String sno) {
super();
this.sname = sname;
this.sno = sno;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public String getSno() {
return sno;
}
public void setSno(String sno) {
this.sno = sno;
}
@Override
public String toString() {
return "Student [sname=" + sname + ", sno=" + sno + "]";
}
}
```
#### 1.2.3.1 通过Setter进行依赖注入
```xml
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
```
```java
package day0610.main;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import day0610.pojo.Student;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student)context.getBean("student");
System.out.println(student);
}
}
```
输出结果:
```
Student [sname=tom, sno=110001]
```
现在我们将我们POJO类的```setter()```方法拿掉,再输出
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200610180749416.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01yX0NfcHl0aG9u,size_16,color_FFFFFF,t_70)
得到报错结果:
```
六月 10, 2020 6:07:56 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'student' defined in class path resource [applicationContext.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'sname' of bean class [day0610.pojo.Student]: Bean property 'sname' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'student' defined in class path resource [applicationContext.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'sname' of bean class [day0610.pojo.Student]: Bean property 'sname' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1743)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1451)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
at org.springframework.context.support.ClassPathXmlApplicationContext.
at org.springframework.context.support.ClassPathXmlApplicationContext.
at day0610.main.Test.main(Test.java:7)
Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'sname' of bean class [day0610.pojo.Student]: Bean property 'sname' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
at org.springframework.beans.BeanWrapperImpl.createNotWritablePropertyException(BeanWrapperImpl.java:243)
at org.springframework.beans.AbstractNestablePropertyAccessor.processLocalProperty(AbstractNestablePropertyAccessor.java:426)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:278)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:266)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:97)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:77)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1739)
... 13 more
```
抓住关键报错信息:
```
Bean property 'sname' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
```
说的就是没有```setter()```方法导致bean无法创建。因为我们的依赖注入通过setter方法底层进行Java```映射```创建的,具体的感兴趣的查询相关的资料。
#### 1.2.3.2 通过构造器进行依赖注入
```xml
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
```
```java
package day0610.main;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import day0610.pojo.Student;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student1 = (Student)context.getBean("student1");
System.out.println(student1);
Student student2 = (Student)context.getBean("student2");
System.out.println(student2);
}
}
```
输出结果:
```
Student [sname=tom, sno=110001]
Student [sname=pitter, sno=110002]
```
我们可以看见通过构造器实现依赖注入,就必须有构造方法。
#### 1.2.3.3 通过p命名空间进行依赖注入
```xml
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
```
```java
package day0610.main;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import day0610.pojo.Student;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student1 = (Student)context.getBean("student1");
System.out.println(student1);
Student student2 = (Student)context.getBean("student2");
System.out.println(student2);
Student student3 = (Student)context.getBean("student3");
System.out.println(student3);
}
}
```
输出结果:
```
Student [sname=tom, sno=110001]
Student [sname=pitter, sno=110002]
Student [sname=lucy, sno=110003]
```
#### 1.2.3.4 其他常见的注入操作(特殊值、引用类型等等)
我们再加一个POJO类,并修改学生POJO对象
```java
package day0610.pojo;
public class Teacher {
private String tname,tno;
public Teacher(String tname, String tno) {
super();
this.tname = tname;
this.tno = tno;
}
public Teacher() {
super();
}
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
public String getTno() {
return tno;
}
public void setTno(String tno) {
this.tno = tno;
}
@Override
public String toString() {
return "Teacher [tname=" + tname + ", tno=" + tno + "]";
}
}
```
```java
package day0610.pojo;
public class Student {
private String sname,sno;
private Teacher teacher;
public Student() {
super();
}
public Student(String sname, String sno, Teacher teacher) {
super();
this.sname = sname;
this.sno = sno;
this.teacher = teacher;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public String getSno() {
return sno;
}
public void setSno(String sno) {
this.sno = sno;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student [sname=" + sname + ", sno=" + sno + ", teacher=" + teacher + "]";
}
}
```
==引用对象类型==
```xml
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
```
输出结果:
```
Student [sname=lucy, sno=110021, teacher=Teacher [tname=jack, tno=11000001]]
```
==特殊值处理==
我们在进行依赖注入的时候为了解决特殊值的注入一般采用特殊值的实体引用和``````标记,如下表所示为部分特殊值的实体引用。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200610202912752.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01yX0NfcHl0aG9u,size_16,color_FFFFFF,t_70)
1)带有```<>```符号
```xml
```
2)空值
```xml
```
```xml
```
3)使用``````包含特殊值
```xml
Java
```
==map类型注入==
```xml
```
### 1.2.4 自动装配