目录
Spring
1 总体概述
2 IoC控制反转
2.1 对IoC及Spring的概念解释和实现原理
2.2 Spring的第一个程序
2.3 基于XML(配置文件)的DI
2.4 基于注解的DI
2.5 XML和注解该使用哪种方式
2.6 一个小技巧
出现在2002左右,解决企业开发难度,减轻对项目模块之间的管理,类和类之间的管理,帮助开发人员创建对象,管理对象之间的关系;能实现模块之间,类之间的解耦合。
spring核心技术:ioc,aop。
依赖:classA中使用classB的属性和方法,叫做classA依赖classB
优点:
轻量:spring框架使用jar比较少,核心功能所需jar总共3m左右;运行占用资源少,运行效率高。不依赖其他jar
针对接口编程,解耦合:IOC
AOP编程的支持:AOP
方便集成各种优秀框架
Spring体系结构:
IoC(Inversion of Control):控制反转,是一个理论,概念,思想。
作用是:把对象的创建,赋值,管理工作交给代码之外的容器实现,也就是对象的创建是由其他外部资源完成
对几个关键词的描述:
控制:创建对象,对象的属性赋值,对象之间的关系管理。
反转:把原来开发人员管理,创建对象的权限转移给代码之外的容器实现,由容器代替开发人员管理对象。创建对象,给属性赋值。
正转:由开发人员在代码中,使用new构造方法创建对象,开发人员主动管理对象
容器:是一个服务器软件,一个框架(Spring)
为什么使用IoC:目的就是减少代码的改动,也能实现不同的功能,实现解耦合
IoC的体现:
Servlet:
创建类继承HttpServlet。
在web.xml注册servlet。
没有自己创建Servlet对象。
Servlet对象是Tomcat服务器为你创建并管理的。
Tomcat也称为容器,其中存放着Servlet对象,Listener,Filter对象。
DI(Dependency Injection):
DI是IoC的技术实现,即依赖注入。
只需要在程序中提供需要使用的对象名称就可以了,致于对象如何在容器中创建,赋值,茶轴都由容器内部实现
Spring使用DI实现了IoC的功能,spring底层创建对象,使用的反射机制。
2.2.1 创建maven项目
在学习过程中,我们只需要最基础的部分,因此选择quickstart模板作为maven项目来创建
2.2.2 加入maven的依赖
加入Spring的依赖和junit依赖。
junit
junit
4.11
test
org.springframework
spring-context
5.2.5.RELEASE
2.2.3 创建类(接口和实现类)
此处我创建了一个名为OneService的接口及其实现类OneServiceImpl。
package com.pjh.service;
public interface OneService {
void doSome();
}
//以上接口,分割线,以下实现类
package com.pjh.service.impl;
import com.pjh.service.OneService;
public class OneServiceImpl implements OneService {
@Override
public void doSome() {
System.out.println("doSome已执行...");
}
}
2.2.4 创建spring需要使用的配置文件
在配置文件中声明类的信息,这些类由spring创建和管理。
spring配置文件应当创建在resource目录下
这里提供一个标准spring配置文件模板(注解内容非必须)(XML)
然后我们需要在配置文件中声明java对象
2.2.5 测试spring创建对象
在你的测试类中:
@Test
public void testDoSomeBySpring() {
//使用spring容器创建的对象
//1. 指定spring配置文件的名称
String config = "beans.xml";
//2. 创建表示spring容器的对象,ApplicationContext
//ClassPathXmlApplicationContext: 表示从类路径中加载spring的配置文件
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//3. 从容器中获取某个对象,需要调用getBean方法
//传入配置文件中的id
OneService one = (OneService) ac.getBean("oneService");
//4. 使用创建好的对象
one.doSome();
}
2.2.6 一些思考
对象是在什么时机创建的?
在执行加载spring的配置文件时创建的,见2.2.5 第8行代码。且会创建配置文件中声明的所有对象。
如何获取容器中对象的信息?
获取对象数量信息:
调用getBeanDefinitionCount()方法,返回对象的数量。
获取所有对象的名称(key):
调用getBeanDefinitionNames()方法,返回一个字符串数组。
spring能创建非自定义的对象吗?
可以,只需要见类的全限定名称书写正确,就可以创建任意指定的类对象。
即通过配置文件对对象赋值。
DI:依赖注入,表示创建对象,给属性赋值。
其实现语法有两种:
在spring配置文件中,通过标签和属性完成,叫做基于XML的DI实现(见本章2.3)
使用spring中的注解,完成属性赋值,叫做基于注解的DI实现(见2.4)
2.3.1 DI的语法分类
set注入:spring调用类的set方法,在set方法可以实现属性的赋值,比较常用
构造注入:spring调用类的有参数构造方法,创建对象。在构造方法中完成赋值
注意:
set方法必须符合命名规范
即使没有属性,只要存在setXXX的方法,就可以通过set注入调用
必须存在set方法,否则无法执行
set注入实际上只是调用了set方法,即使不进行赋值
Spring会先执行无参构造方法,再执行set注入
在set注入时,所有的值(value)不管是数字还是其他都必须放在引号中,这是XML规范所限制的
在spring里创建对象无需在意顺序,原理似乎是二次扫描可以保证每个对象都被正确的创建
为对象赋值时也无需在意赋值顺序
2.3.2 引用类型属性自动注入
byName属性:
java类中引用类型的属性名和spring容器中(配置文件)
byType:java类中引用类型的数据类型和spring容器中(配置文件)
java类中引用类型的数据类型和class类型的值是一样的
java类中引用类型的数据类型和class类型的值是父子关系
java类中引用类型的数据类型和class的值接口和实现类关系
语法:
注意:
符合条件的对象仅能存在一个,出现多个符合的bean会报错
2.3.3 为什么要使用多个配置文件
便于管理:
当项目较大时,往往存在成百上千个对象,如果全部存在一个配置文件里,不仅难以查找修改,文件的大小也会变得十分庞大,打开配置文件和保存配置文件时都会变慢。
便于多人协同开发:
避免在多人合作开发时,出现错误覆盖、模块功能冲突等情况。
因此,在开发大型项目时,可以采用一个模块一个配置文件的做法,不仅可以使配置文件小很多、提高效率,还可以让负责该模块的开发人员不与其他人员产生冲突。
2.3.4 配置文件的包含关系(套娃)
在上一小节中我们讨论了为什么要使用多个配置文件,这一节我们将学习如何管理多个配置文件,于是这里涉及到了关于配置文件的包含关系。
注意:以下代码都仅保留了关键代码作为示例
当前存在两个模块Student模块和School模块,且分别拥有spring_student和spring_school配置文件,两个配置文件中分别声明了一个bean。
我们可以创建一个total的主配置文件来管理这些模块配置文件。
通过注解完成Java对象的创建和属性赋值。
使用注解的步骤:
加入maven的依赖spring-context,在你加入spring-context的同时,会间接加入spring-aop的依赖 使用注解必须使用spring-aop的依赖
在类中加入spring的注解(多个不同功能的注解)
在spring的配置文件中,加入一个组件扫描器的标签,说明注解在你的项目中的位置
需要学习的注解:
@Component
@Respotory
@Service
@Controller
@Value
@Autowired
@Resource
2.4.1 @Component的使用
首先创建一个类,并在其中使用注解@Component
package com.pjh.ba01;
import org.springframework.stereotype.Component;
/**
* @Component 创建对象的,等同于的功能
* @value 就是对象的名称,也就是bean的id,其值是唯一的,创建的对象在整个spring容器中就一个
*
* 该注解使用在类的上面
*
* @Component(value = "myStudent") 等同于
*
*/
@Component(value = "myStudent")
//当我们只填入value的值,其实是可以省略的,并且在平时更加常见
//OR @Component("myStudent")
//我们也可以不指定对象的名称,由spring指定默认名称,其默认值为类名的lowcase格式,即student
//OR @Component
public class Student {
private String name;
private String age;
public void setName(String name) {
this.name = name;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
然后创建一个配置文件,并在其中使用组件扫描器component-scan。
此时已经完成了在spring容器中创建java对象的目的,我们可以调用这个对象。
package com.pjh;
import com.pjh.ba01.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author yueyinghaibao
* @date 2021/9/25
*/
public class MyTest {
@Test
public void test01() {
String config = "applicationContext.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(config);
Student myStudent = (Student) context.getBean("myStudent");
//OR Student myStudent = (Student) context.getBean("student");
System.out.println("myStudent = " + myStudent);
}
}
可以得到myStudent = Student{name='null', age='null'}的结果 顺带一提,此时spring是调用的student类的无参构造方法
2.4.2 与Component具有相同功能的注解
@Repository(放在持久层):放在dao的实现类上,表示创建dao对象,dao对象是能访问数据库的。
@Service(放在业务层):放在service的实现类上,表示创建service对象,service对象是做业务处理, 可以有事务功能。
@Controller(放在控制器):放在controller类上的,表示创建controller对象,controller对象能够接受用户提交的参数,显示请求的处理结果。
以上三个注解的使用语法和Component一样。都能创建对象,但这三个注解有额外功能。
这三个注解是给项目的对象分层的。
非这三类的对象应该使用Component。
2.4.3 component-scan扫描多个包的方式
多次使用扫描器,指定不同的包
使用分隔符( ; OR , )分割多个包名
指定一个父包,可以包含父包中的所有子包
2.4.4 @Value的使用
此标签用于给简单类型的属性赋值。
package com.pjh.ba02;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("myStudent")
public class Student {
/**
* @Value 给简单类型赋值
* 属性:value 是String类型,表示简单类型的属性值
* 位置:1.在属性定义的上方,无需set方法,推荐使用
* 2.在set方法上面
*/
@Value("张飞")
private String name;
@Value("20")
private String age;
// public void setName(String name) {
// this.name = name;
// }
//
// public void setAge(String age) {
// this.age = age;
// }
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
2.4.5 @Autowired的使用
自动注入实现引用类型的属性赋值。
package com.pjh.ba03;
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("myStudent")
public class Student {
@Value("张飞")
private String name;
@Value("20")
private int age;
/**
* 引用类型
* @Autowired spring框架提供的注解,实现引用类型的赋值
* spring中通过注解给引用类型赋值,使用的是自动注入原理,支持byName和byType
* 默认使用byType
* 位置:1.在属性定义上,无需set方法,推荐使用
* 2.在set方法上面
*
* 属性:required 是一个boolean类型的,默认true
* required = true:表示引用类型赋值失败,程序报错,并终止执行。
* required = false:表示引用类型赋值失败,程序正常执行,并且赋值为null。
*
* 如果要使用byName方式,需要做的是:
* 1.在属性上面加入@Autowired
* 2.在属性上面加入@Qualifier(value = "bean的id"):表示使用指定名称的bean完成赋值
*/
// @Autowired
// private School school1;
@Autowired
@Qualifier("mySchool")
private School school2;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school2 +
'}';
}
}
注意:
关于required的true OR false问题,更加推荐使用true。原因在于如果你的程序出现问题,会更早暴露你的问题,方便你尽快修改程序问题。
2.4.6 @Resource的使用
该注解是由jdk提供的自动注入注解,作用与autowire一样
package com.pjh.ba06;
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("myStudent")
public class Student {
@Value("张飞")
private String name;
@Value("20")
private int age;
/**
* 引用类型
* @Resource 来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值
* 使用的也是自动注入的原理,支持byName,byType,默认是byName
* 位置:1.在属性定义的上面,无需set方法,推荐使用
* 2,在set方法上面
* 默认使用byName:先使用byName自动注入,如果buName失败,在使用byType
* 如果想指定只使用byName,需要增加一个属性 name
* name是bean的id
*/
@Resource(name = "mySchool")
private School school;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
XML的优势:
使用xml配置文件可以方便的修改属性的值,不需要改动类
XML的劣势:
需要写的代码太多,开发比较繁琐
由于赋值和类完全分离,所以在浏览类代码时,完全不清楚其属性值的内容
注解的优势:
使用方便,不需要写很多的代码
注解的劣势:
注解和源代码不宜分开,在需要改动时非常麻烦
注解对源代码的结构是破坏性的(O~O!)
总的来说:如果需要经常变动属性值,推荐用XML;如果属性值相对固定,推荐使用注解
并且当前的趋势是,以注解为主,XML配置文件为辅。
在注解中使用 ${},通过properties配置文件实现解耦合。
需要在配置文件中用