1. Spring的概述
1.1 构成图
2.IOC容器和bean的配置
2.1 IOC和DI
- IOC (Inversion of Control):反转控制,以前需要对象,直接new,现在对象的创建交给框架,解耦
- DI(Dependency Injection):依赖注入,组件以一些预先定义好的方式接受来自于容器的资源注入
2.2 IOC容器的实现
- BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的
- ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory
2.3 ApplicationContext的实现类
- ClassPathXmlApplicationContext:对应类路径下的XML格式的配置文件 (常用)
- AnnotationConfigApplicationContext:基于注解的配置类(Java类) (常用)
- FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件,不一定在类路径下
2.4 BeanFactory和ApplicationContext的区别
- BeanFactory: 在容器创建完成时, 没有创建对象,只有等到获取对象的时候才创建,默认单例
- ApplicationContext: 在容器创建完成前,就已经创建好了对象, 并且默认是单例的,如果需要多实例对象,则是在获取对象的时候才创建
3. 获取Bean的方式
//1.通过id获取bean(常用)
Object getBean(String var1) throws BeansException;
//2.通过id和对应类型获取bean(常用)
T getBean(String var1, Class var2) throws BeansException;
//3.通过类型获取bean(常用)
T getBean(Class var1) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
T getBean(Class var1, Object... var2) throws BeansException;
注意:
- 默认获取的对象都是单例的
- 通过类型获取bean的时候,如果配置文件中同一类型下有多个对象,则会抛出异常
- 注意各自返回值
4. 给Bean赋值的几种方式
4.1 通过setter方法赋值
注意:
- 必须要有对应属性的setter方法
- name的属性值不是该类的属性名称,而是将setXxx() 的方法名去掉set,然后首字母小写后,作为name的属性值
- 基于第二点, 我们最好使用idea自动生成setter方法
4.2 通过构造器赋值
注意:
- 必须要有对应参数的构造方法
- name的属性值是该类的属性名称
4.3 通过索引值赋值
注意:
- 必须要有对应参数的构造方法
- 索引的顺序就是构造函数中参数的顺序, 如果构造函数中参数顺序发生变化, 索引对应的赋值也会发生变化
4.4 通过构造器的默认顺序赋值
注意:
- 必须要有对应参数的构造器
- 赋值的顺序必须和构造函数的传参顺序一致
4.5 通过类型不同区分重载的构造器
注意:
- 必须要含有对应的构造函数
- 上面是含有两个参数的构造函数, 也叫构造函数的重载
- 通过类型指定了使用哪一个构造函数,通过索引给对应参数赋值
5. 级联属性的赋值
上述的属性都是基本数据类型的, 如果属性也是一个对象或者集合或者数组这种复杂类型的话, 就需要用到级联属性的赋值了
5.1 外部赋值
public class Person {
private String name;
private String gender;
private Integer age;
//此属性是一个对象,不是基本数据类型
private Car car;
}
public class Car {
private String name;
private Double price;
}
//ref属性代表Person中的car属性的属性值引用car01的
注意:
- 由于是对象的引用, 那么Person中的car属性和car01这个对象其实是同一个对象,因为引用的地址都是相同的
- 无论是更改Person.car的值还是更改car01的属性值, 另一个都会跟着发生变化
- 这里的car01其他地方如果需要用到, 可以随时引用, 相当于ioc.getBean("car01"),即已经添加到容器中了
5.2 内部赋值
注意:
- 内部赋值中, 赋值的car对象其他地方无法引用, 只能在person07中使用
- 内部赋值,相当于 new car()
6. 正确赋值null
错误方法:
正确方法:
注意: 错误的方法中,只是给gender属性赋值了一个"null"的字符串
7. 给List属性赋值
- list 标签中的bean即为一个对象, 相当于new Car()
- ref是引用外部的一个对象
- list中的两个对象都没有加 id 必要, 因为外部无法引用
8. 给Map属性赋值
9. 给Properties属性赋值
hello
注意: properties本身也是键值对, 但是和Map不同, properties的键值对都是字符串, 所以其值写在标签体内
10. 使用util实现bean的复用
- 使用util必须添加名称空间 (容意忘记)
- list-class: 表示list的实例由哪个类创建, 默认的是ArrayList
- value-type: 相当于泛型, 规定了list集合里面的数据类型
- Map, Set,Properties是类似的用法
11. 使用parent属性实现bean的复用
- parent属性指定的对象, 就是被继承的对象
- 在person02中只需要写与person01不相同的属性, 其余的属性及其属性值都直接继承自person01
- 这里的继承不是Java中的继承关系, 没有父子类一说, 只是内容上的一种关系而已
12. 使用abstract属性实现bean的复用
- 这里person01完全被当作了一个模板, 作用就是让别人来引用他的
- 与前面一个不同的是, 如果加了abstract属性, 则无法获取到person01这个对象, 和 Java中抽象类有点类似
13. bean的生命周期
13.1 生命周期的分类
生命周期一般由scope属性来控制, 属性值有四个
- prototype 多实例
- singleton 单实例 (默认)
- request 在web环境下, 一次请求创建一个实例 (一般不用)
- session 在web环境下, 一次会话创建一个实例 (一般不用)
13.2 初始化方法和销毁方法
//初始化方法
public void initStudent() {
System.out.println("init methods............");
}
//销毁方法
public void destroyStudent() {
System.out.println("destroy methods..........");
}
- init-method : 指定对象的初始化方法
- destroy-method : 指定对象的销毁方法
- 初始化和销毁方法都定义在Student实体类中
13.3 后置处理器
public interface BeanPostProcessor {
//在bean初始化前执行
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
//在bean初始化完成后执行
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
- postProcessBeforeInitialization : 在对象初始化前调用
- postProcessAfterInitialization : 在对象初始化后调用
- bean后置处理器对IOC容器里的所有bean实例逐一处理,而非单一实例
- 其典型应用是:检查bean属性的正确性或根据特定的标准更改bean的属性
- 不管对象有没有初始化方法, 如果定义了后置处理器, 就一定会工作
- 要使用后置处理器的步骤:
- 实现BeanPostProcessor接口
- 重写两个方法
- 在配置文件配置该实现类的bean实例
13.4 整个bean的生命流程
- 通过构造器或者工厂方法创建bean实例
- 为bean的属性赋值或者引用
- 将bean实例传给postProcessBeforeInitialization () 方法
- 调用bean的初始化方法
- 将bean实例传给postProcessAfterInitialization () 方法
- bean可以使用了
- 当容器关闭时,调用销毁方法
注意: 多实例在容器关闭时, 不调用销毁方法, 这是与单实例不同的地方
14. 静态工厂与实例工厂
往往我们想创建一个对象的时候,我们不想知道具体的创建过程,只想拿来就用,那么就可以用到工厂类方法进行创建
14.1 静态工厂
调用静态工厂方法创建bean是将对象创建的过程封装到静态方法中, 这样只需要类名.方法名调用,就可创建bean了
14.2 静态工厂的实现
//静态工厂类
public class StaticFactory {
//定义了一个静态方法, 用来创建Student实例的
public static Student getStudent(String name) {
Student st = new Student();
st.setName(name);
st.setGender("male");
st.setClassNum(11);
st.setChScore(98);
st.setEnScore(50);
return st;
}
}
- 在配置文件中创建了一个bean, 但是注意, 这里不是创建的工厂实例, 返回类型和getStudent方法的返回值一致
- factory-method: 指定工厂方法
- 利用constructor-arg标签传递工厂方法需要的参数
14.3 实例工厂
- 创建实例对象
- 实例对象. 方法名来创建指定对象
14.4 实例工厂的实现
//实例工厂类
public class InstanceFactory {
//定义了一个普通方法, 用来创建Student实例的
public Student getStudent(String name) {
Student st = new Student();
st.setName(name);
st.setGender("male");
st.setClassNum(11);
st.setChScore(98);
st.setEnScore(50);
return st;
}
}
//创建的实例工厂类对象
//待创建的Student对象
- 创建实例工厂类对象
- factory-bean: 指定工厂类实例
- factory-method: 指定工厂方法
- 利用constructor-arg标签传递工厂方法需要的参数
15. Spring中的工厂
Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean
工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象
//FactoryBean是一个接口, 凡是实现了他的类,都被Spring视为一个工厂bean
public interface FactoryBean {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
//工厂方法: 返回需要创建的对象实例,Spring自行调用, 不用我们管
@Nullable
T getObject() throws Exception;
//返回创建的实例的类型,Spring会自动调用来确认创建的对象的类型
@Nullable
Class getObjectType();
//控制创建的实例是单例还是多例
default boolean isSingleton() {
return true;
}
}
如下是一个实现了FactoryBean接口的实现类
public class MyFactoryBean implements FactoryBean {
//工厂方法, Spring自行调用, 不用我们管
@Override
public Object getObject() throws Exception {
System.out.println("创建对象中..........");
Student st = new Student();
st.setName("lisi");
st.setGender("female");
return st;
}
//返回创建的对象的类型,Spring会自动调用来确认创建的对象的类型
@Override
public Class getObjectType() {
return Student.class;
}
//控制创建的对象是否是单实例的
@Override
public boolean isSingleton() {
return false;
}
}
配置文件:
- 从配置文件可以看出, 好像是创建了一个MyFactoryBean对象, 其实不是的, 其返回的是其工厂方法所创建的对象,也就是Student类的实例
- 继承FactoryBean的类被Spring视为工厂类, 并且无论创建的对象是单实例还是多实例的, 都是在获取bean的时候创建
16. Spring中配置数据库
由于Spring框架默认的是产生单实例对象, 这个特点使得其对于创建数据库连接非常适合
- 导入依赖 (Pom.xml)
mysql
mysql-connector-java
5.1.6
c3p0
c3p0
0.9.1.2
- 编写外部属性文件 (jdbc.properties)
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.user=root
jdbc.password=root
- 编写配置文件 (applicationContext.xml)
17. 基于XML的自动装配
17.1 概述
手动装配:以value或ref的方式明确指定属性值都是手动装配。
自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中。
17.2 装配模式
自动装配模式主要是通过autowire属性来决定是否开启自动装配, 或者以什么模式自动装配,主要针对的是自定义类型, 不包含基本数据类型
对应取值:
- byName: 根据名称进行自动装配
- byType: 根据类型进行自动装配
- constructor: 根据构造器进行自动装配
- no: 不进行自动装配
- default: 默认是no
Person.java
public class Person {
private String name;
private String gender;
private Integer age;
//属性car本身又是一个对象
private Car car;
}
- 根据类型自动装配
注意:
- 根据类型装配, 在配置文件中必须保证只有唯一的该类型实例, 如果有多个该类型实例,会抛出异常
- 如果没有找到该类型, 则装配为null
- 根据名称自动装配
注意:
- 根据名称自动装配的前提是实体类中的属性名与配置文件中的id名一致, 就会直接将car当作一个属性, 赋值给对应的car属性
- 如果没有对应名称, 则装配为null
- 根据构造器自动装配
- 根据类型自动装配的前提是实体类中必须有对应属性的单独的构造器,否则装配null
- 查看构造器中的参数类型进行装配(成功就赋值), 没有就直接赋值null
- 如果类型有多个实例, 再按照参数的名称作为id去找, 找到了就赋值, 没有就赋值null
- 不会报错
注意: 一般来说, 在xml配置文件中使用自动装配意义不大, 并且略显笨拙, 主要用于注解开发中
18. SpEL表达式
18.1 为基本类型赋值
//为整数赋值
//为小数赋值
//科学计数法
//为字符串赋值
//为boolean类型赋值
18.2 为复杂类型赋值
//为car属性引用其他的bean(car01)
//用其他bean的属性值作为自己属性的属性值
18.3 调用非静态方法
18.4 调用静态方法
调用方法: #{T(全类名).静态方法名(参数)}
注意: 这里要注意, 静态方法要加T,标记了才说明要调用的是静态方法
18.5 其他方法
还可以调用运算符
①算术运算符:+、-、*、/、%、^
②字符串连接:+
③比较运算符:<、>、==、<=、>=、lt、gt、eq、le、ge
④逻辑运算符:and, or, not, |
⑤三目运算符:判断条件?判断结果为true时的取值:判断结果为false时的取值
⑥正则表达式:matches
19. 组件扫描
19.1 常见用法
四个将对象注入容器的注解
- @Controller 应用于控制层
- @Service 应用于业务层
- @Repository 应用于Dao层
- @Component 应用于任何组件
注意:
- 对于上面的四个注解, 在底层没有区分该组件到底是哪一层的, 所以理论上说可以混用的
- 标注上对应注解, 是为了让程序员更加清楚得知道该组件在整个项目中得大致作用是什么
开启注解得方式:
实体类:
//将PersonDao下的一个bean实例注入到容器中, 由容器同一管理
@Repository("personDao1111")
//改变实例的生命周期
@Scope("prototype")
public class PersonDao {
}
注意:
- 正常情况下, 注入容器的组件的id为类名首字母小写
- 如果想自定义名称, 可以在注解后面添加想要的名称
- 默认是单实例bean
- 使用 @scope 来改变实例的生命周期
19.2 指定要包含和排除的类
context: exclude-filter 排除相关类
context: include-filter 只包含相关类
expression: 表示对应过滤方式下的类的全类名
type: 表示要过滤的类是哪些类型的
类别 | 说明 |
---|---|
annotation | 过滤所有标注了指定注解的类 |
assignable | 过滤指定类 |
aspectj | 根据AspectJ表达式进行过滤。 |
regex | 根据正则表达式匹配到的类名进行过滤 |
custom | 自定义规则,该类必须实现org.springframework.core.type.filter.TypeFilter接口 |
注意:
- 扫描包的默认操作是将基类包下的所有类都扫描进来
- 排除方法只需要利用对应标签将对应规则下的类排除掉就行了,剩下的类都可以正常扫描到容器中
- 只包含的话,需要通过use-default-filters="false"属性, 首先禁用默认扫描规则, 之后再设置只想扫描进来的类
20. 自动装配 ( DI )
20.1 概述
自动装配一般依赖三个注解, @Autowired @Resource @Inject
- @Autowired 最强大,功能最多,是Spring框架提供的
- @Resource 功能一般,没有required属性, 是Java的标准
- @Inject 一般在EJB中使用, 可以不管,不常用
@Autowired | @Resource |
---|---|
Spring提供, 只能在Spring框架下使用 | Java的标准,扩展性强,换一个容器,同样可以用 |
20.2 @Autowired 用法
- 配置文件开启注解,并扫描 (见上一章)
- 标注@Component @Controller @Service @Repository中之一注解, 将组件注入容器
- 在需要使用对应组件的地方标注@Autowired, 将对应组件注入
- 使用对应组件
//2. 标注@Service注解, 将PersonService组件注入容器中
@Service
public class PersonService {
public void save() {}
}
public class PersonController {
//3.在需要使用的地方, 将对应组件注入进来
@Autowired
private PersonService personService;
public void save() {
//4. 使用对应组件,调用其方法
personService.save();
}
}
20.3 自动装配原理
根据类型进行装配, 如果对应类型只有一个实例, 则正常装配; 如果没有, 报错
如果对应类型有多个, 则根据id进行装配, 默认的id是类名首字母小写, 也可以利用@Qualifier改变id名称
如果容器中没有对应id名称的组件, 会报错
如果给@Autowired添加required=false的属性, 如果找不到对应组件, 会直接赋值null, 则不会报错
@Autowired可以标注在属性字段, 方法, 构造器,方法参数以及注解上
-
如果标注在方法上,该方法在bean创建的时候就会自动调用,并且会一次性把方法需要的参数都装配到容器中
@Controller public class PersonController { //标注在方法上,当PersonController实例创建的时候,会自动调用save方法,并把PersonDao实例也装配进来 @Autowired public void save(PersonDao personDao) { System.out.println("运行了"+personDao); } } //测试方法, 创建了personController实例,正常单实例的话,容器启动过程中就在创建实例,所以哪怕不用下面手动得到personController实例,启动容器的时候,就可以输出最下面的结果了 @Test public void test01() throws SQLException { PersonController personController = ioc.getBean(PersonController.class); } //运行结果,已经得到了PersonDao实例 运行了com.oneway.dao.PersonDao@49d904ec
上面对于参数的装配, 和正常的一样, 也是先按类型,之后按照id名称, 也可以使用@Qualifier指定id名称, 也可以用@Autowired指定找不到是返回null
21. Spring的测试
//使用spring自带的测试驱动替代junit
@RunWith(SpringJUnit4ClassRunner.class)
//指定配置文件路径创建容器
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class MyTest {
//不用手动创建ioc容器
//ApplicationContext ioc = null;
//自动注入该属性
@Autowired
private PersonDao personDao;
@Test
public void test01() {
System.out.println(personDao);
}
}