目录
前言
一、Spring的核心
二、Spring的特点
三、Spring5的主要内容
四、IOC底层
1、IOC底层的技术
2、IOC接口
五、IOC操作Bean管理(基于xml配置文件)
1、bean标签创建对象说明
2、基于xml配置文件进行属性注入的方式
3、基于xml配置文件进行属性注入
4、FactoryBean
5、bean的作用域
6、bean生命周期
7、xml自动装配
8、外部属性文件
六、IOC操作Bean管理(基于注解的方式)
前几天Spring框架曝出RCE 0day漏洞,可导致远程代码执行 (RCE),使用JDK9及以上版本都有可能受到影响,还好博主用的是稳定的JDK8(JDK8真的是yyds)。本篇文章主要介绍一下spring核心之一的IOC/DI以及他们是如何通过XML和注解的方式实现Bean管理。天不生我小码农,码道万古如长夜,码字不易,感谢支持,当然可能有一些理解不到位的地方,欢迎指正。
Spring是轻量级的开源JavaEE框架,Spring主要用到的设计模式有工厂模式和代理模式,一般而言,工厂模式分为三类:简单工厂模式(Simple Factory)、工厂方法模式(Factory Method)、抽象工厂模式(Abstract Factory)。所有的框架主要用于解耦和快速开发,可以理解为框架=注解+反射+设计模式。Spring 有两个核心部分:IOC和AOP(这也是Spring面试常问的两个东西)。
1、IOC:控制反转,把对象创建和对象之间的调用过程交给Spring进行管理(DI和IOC是同一个概念的不同角度描述,控制反转是一种编程思想,依赖注入是实现控制反转的典型方法或者说DI是IOC的具体实现,DI需要在创建对象的基础上实现,给对象的属性进行赋值)。控制:包括对象的创建,属性的赋值以及对象之间关系的管理。反转:把原来由开发人员创建、管理对象的权限交给第三方即代码之外的spring容器进行实现,在JavaSE中,当我们缺少对象时,我们可以用关键字new一个对象,这也可以称为正转。
2、AOP:面向切面,不修改源代码的情况下增强功能(原理:动态代理技术,设计模式就是代理加装饰器)。
1、方便解耦,简化开发
2、AOP编程支持
3、方便程序的测试(可以使用Junit测试单元)
4、方便和其他优秀的框架进行整合(比如博主以前所写的SSM集成框架,就是Spring集成了Mybatis等优秀框架,当然还有过时的SSH框架:Spring + Struts +Hibernate,以前老会开玩笑说学完春天学冬天,现在也用不上冬天了)
5、方便进行事务的操作
6、降低API的开发难度
博主用的是5.2.5版本的,但官网已经更新到了5.3.18版本了
1、IOC容器
2、AOP
3、jdbcTemplate
4、事务管理
5、Spring 5新特性:Webflux(响应式编程及函数式编程)、函数式注册对象、Nullable注解等。
IOC思想是基于IOC容器完成的,IOC容器的底层就是Object Factories(对象工厂,工厂顾名思义是用来生产产品的,利用对象工厂,我们创建对象时就可以得到解脱)。IOC底层技术包括xml解析、工厂模式、反射、注解。
Spring提供IOC容器的实现有两种方式(两个接口)
BeanFactory:IOC容器的基本实现,是Spring内部的使用接口,不提供开发人员进行使用。加载配置文件时不会创建对象,只有在获取或者使用对象时才去加载对象(懒汉式)。
ApplicationContext:继承了BeanFactory接口,是Beanactory接口的子接口,它提供了更多更强大的功能,一般由开发人员使用,在加载配置文件的时候就会对对象进行创建。(饿汉式)
ApplicationContext接口有实现类,其中有两种是加载配置文件的具体实现类
对比源码会发现两者在前面的代码基本一致,主要是体现在后面加载配置文件的方式上,FileSystemXmlApplicationContext加载绝对路径的配置文件,ClassPathXmlApplicationContext加载类路径的配置文件。
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
public ClassPathXmlApplicationContext(String path, Class> clazz) throws BeansException {
this(new String[]{path}, clazz);
}
public ClassPathXmlApplicationContext(String[] paths, Class> clazz) throws BeansException {
this(paths, clazz, (ApplicationContext)null);
}
public ClassPathXmlApplicationContext(String[] paths, Class> clazz, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
Assert.notNull(paths, "Path array must not be null");
Assert.notNull(clazz, "Class argument must not be null");
this.configResources = new Resource[paths.length];
for(int i = 0; i < paths.length; ++i) {
this.configResources[i] = new ClassPathResource(paths[i], clazz);
}
this.refresh();
}
@Nullable
protected Resource[] getConfigResources() {
return this.configResources;
}
Bean管理指的是两个操作,一为Spring创建对象,二为Spring注入属性(有构造器注入和set注入)。Bean管理操作有两种方式,一种是基于xml配置文件,另外一种是基于注解。
在Spring配置文件中,使用bean标签,标签里可以添加对应的属性,就可以实现对象的创建了。(1)bean标签的属性
id属性:唯一标识,自定义对象名称(可以没有,spring可以提供默认名称),后面获取配置的创建对象时,需要对应上
class属性:类的全限定名称,spring通过反射机制创建对象,不能是接口
spring根据id,class创建对象,并把对象放入到spring的一个map对象 中,map.put(id,对象)
(2)在创建对象的时候,默认执行的是无参构造方法,之所以会使用无参构造方法,是因为反射底层调用的是newInstance(),该方法中没有参数。如何进行验证呢?我们在Books类中写一个带参构造方法,学过java基础的都知道,java在开发者未指定构造方法时,会自动创建一个默认的无参构造方法,当写入一个带参构造方法时,默认的无参构造便会销毁,需要手动创建。
当不创建无参构造方法时,运行代码,会报 "No default constructor found"的错误,侧面验证了上述观点。
属性注入的方式有两种,一为set注入(也叫设值注入),二为构造注入(用的比较少)
(1)set注入
通过set方法对属性进行赋值,可以对简单数据类型进行set注入,也可以对引用数据类型进行set注入。像下面例子,是对引用类型进行注入,UserServiceImpl实现类中注入userDao对象,name属性:类里面的属性名称,ref属性:创建userDao对象bean标签的id值。
普通类型的set注入, 使用property标签完成属性的注入,name表示属性的名字,value表示要注入的值。
(2) 构造注入
这里主要指使用有参构造方法进行属性的注入,使用constructor-arg标签,使用该标签可以不写无参构造方法。
package com.pojo;
/*使用有参构造注入*/
public class Student {
//两个属性,name和id
private String name;
private int id;
/*public Student(){
}*/
//有参构造
public Student(int id,String name){
this.id=id;
this.name=name;
}
public void getStudent(){
System.out.println(id+","+name);
}
}
@Test
public void testAllAug() {
//加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//获取所配置的对象
Student student = context.getBean("student", Student.class);
System.out.println(student);
student.getStudent();
(1)注入属性--外部bean
创建两个类service类和dao类,并在service里调用dao里面的方法,可以注意到在创建对象的过程中,两个bean标签的位置是并排的,只是将要注入的对象bean标签的id引入被注入的对象中。
package com.dao;
public class UserDaoImpl implements UserDao {
@Override
public void update() {
System.out.println("dao执行更新操作");
}
}
package com.service;
import com.dao.UserDao;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("service执行add方法");
userDao.update();
}
//创建UserDao类型属性,生成set方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
(2)注入属性--内部bean
需要建立一对多的关系来说明,假设现在有员工和部门两个类,一个部门可以有多个员工,而一个员工只属于一个部门。可以看出一个类有多个属性,包括基本类型属性和引用对象引用,对于引用类型的属性使用内部bean进行属性注入,这个过程有点像Mybatis按结果嵌套查询。
package com.bean;
//员工类
public class Employees {
private String eName;
private String gender;
//员工属于某一个部门,使用对象形式表示
private Department department;
public void setEName(String eName) {
this.eName = eName;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setDepartment(Department department) {
this.department = department;
}
//生成department的get方法
public Department getDepartment() {
return department;
}
public void add(){
System.out.println(eName+","+gender+","+department);
}
}
(3)注入属性--级联赋值
何为级联操作?指的是多个对象之间的映射关系,级联操作在引入外部bean的同时,给外部bean设置了属性,下面用表达式的形式,直接引用外部bean的属性。该方法必须在Employees类中生成Department属性的get方法,上面实体类中已有说明,如没有会报下列错误 ,第一行的refresh方法,读过源码的都知道,这个方法出现的频率很高,是spring的核心启动方法。
(4)注入属性--集合属性
可以注入数组类型、List集合、Map集合、set类型的属性,在集合里面设置对象类型值
Java课程
Android开发
王富贵
老王
Mysql
Redis
(5)把集合注入部分提取出来
首先要在spring配置文件中引入名称空间util,再使用util标签完成list集合注入提取,实体类里用set注入
JavaSE如此简单
Java修仙路由
MySQL从入库到跑路
package com;
import java.util.List;
public class Book {
private List list;
public void setList(List list) {
this.list = list;
}
}
Spring有两种类型的bean,一种是普通的bean,另一种是工厂bean(FactoryBean),工厂模式的意义就是为了不暴露对象创建的过程。
普通bean:在配置文件中定义bean类型就是返回类型
工厂bean:在配置文件中定义的bean类型可以和返回类型不一样
需要注意的是,上面还提了一个BeanFactory,两者的区别还是要搞清楚的,BeanFactory是IOC容器的基本实现,是Spring内部的使用接口。
创建一个Mybean类,实现FactoryBean接口,重写FactoryBean中的三个方法,可以看出源码中的三个方法:
getObject():返回需要注册的对象 ,如果为单例,该实例会放到Spring容器中单实例缓存池中
getObjectType():返回对象的类型
isSingleton():判断是否是单例 ,非单例时每次创建都会返回一个新的bean
package org.springframework.beans.factory;
import org.springframework.lang.Nullable;
public interface FactoryBean {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class> getObjectType();
default boolean isSingleton() {
return true;
}
}
package com.factorybean;
import com.Course;
import org.springframework.beans.factory.FactoryBean;
public class MyBean implements FactoryBean {
//定义返回bean
@Override
public Course getObject() throws Exception {
Course course=new Course();
course.setCourseName("老滑头");
return course;
}
@Override
public Class> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
(1)在spring里面,默认的情况下,bean是单实例,当用getBean获取对象两次时,输出的引用对象地址一样
(2) 如何设置单实例还是多实例
在spring配置文件bean标签里面有属性(scope)用于设置单实例还是多实例。
scope属性值:
第一个值为默认值,singleton,表示单例对象,第二个值,prototype,表示多实例对象。
singleton和prototype的区别:
第一 singleton单实例,prototype多实例
第二 设置scope值是singleton的时候,在加载spring配置文件时就会创建单实例对象(饿汉式)。设置scope值是prototype的时候,不是在加载spring配置文件的时候创建对象,而是在调用getBean方法时才会创建多实例对象。可以看出当用getBean获取对象两次时,输出的引用对象地址不一样。
生命周期,即从对象的创建到对象销毁的过程
bean的生命周期(面试常问):
(1)通过构造器创建bean实例(无参构造)
(2)为bean的属性设置值和对其他bean的引用(调用set方法)
(3)把bean实例传递给bean后置处理,执行postProcessBeforeInitialization方法
(4)调用bean的初始化方法(需要进行配置)
(5)把bean实例传递给bean后置处理器,执行postProcessAfterInitialization方法
(6)bean可以使用了(对象获取到了)
(7)当容器关闭的时候,调用bean的销毁方法(需要自己配置销毁的方法)
package com.bean;
public class Orders {
private String oName;
//无参构造方法
public Orders() {
System.out.println("第一步:执行无参构造创建bean实例");
}
public void setoName(String oName) {
this.oName = oName;
System.out.println("第二步:调用set方法设置对象的属性值");
}
//创建执行的初始化方法
public void initMethod() {
System.out.println("第三步:执行初始化的方法");
}
//创建执行的销毁方法
public void destroyMethod() {
System.out.println("第五步:执行销毁的方法");
}
}
@Test
public void test4(){
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("bean4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步:获取创建bean实例对象");
System.out.println(orders);
//手动让bean实例销毁
context.close();
}
package com.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之前执行的方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之后执行的方法");
return bean;
}
}
根据指定的装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入。bean便签属性autowire,配置自动装配,autowire属性常用的两个值:byName根据属性名称注入,注入值bean的id和类属性名称一样,如果不一样,属性注入失败,得出的结果为null
byType根据属性类型注入(class的类型),如果是多个属性,采取就近原则,选第一个,后面的autowire注解也会谈到。
外部属性文件在Mybatis中经常会用到,Spring也可以通过引入外部属性文件配置数据库连接池。把外部properties属性文件引入到配置文件中,这里需要引入context名称空间:
使用注解进行spring配置,注解作用在类上面,方法上面,属性上面,使用注解的目的:简化xml配置,springboot就是简化spring的配置,可以实现完全注解开发(基于注解的方式在实际应用中使用的较多)。
1、Spring针对Bean管理创建对象提供注解
(1)@Component:相当于配置文件中的
(2)@Service:service层
(3)@Controller:web层
(4)@Repository:dao层
*上面四个注解功能是一样的,都可以创建bean实例
2、基于注解方式实现对象创建
第一步 开启组件扫描
如果扫描多个包,多个包使用","隔开 ,并且扫描包的上层目录。use-default-filters="false" 表示现在不使用默认的filter,自己配置filter。
context:include-filter,设置扫描哪些内容:
context:exclude-filter:设置哪些内容不进行扫描
3、基于注解方式进行属性的注入
(1)@AutoWired:根据属性类型进行自动装配
第一步:创建service和dao对象,在service和dao类上添加创建对象注解,在注解里面value属性值可以不写,默认是类名称,首字母小写,value值和bean的id等价。
package com.dao;
import org.springframework.stereotype.Repository;
@Repository(value ="userDaoImpl")
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("dao add..........");
}
}
package com.service;
import com.dao.UserDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Value(value = "老王")
private String name;
public void add() {
System.out.println("service add........"+name);
userDao.add();
}
}
第二步:在service里面注入dao对象,在service类里面添加dao类型的属性,在属性上使用注解。
(2)@Qualifier:根据属性名称进行注入。这个@Qualifier注解的使用要和上面的@AutoWired一起使用。
@Autowired //根据类型进行注入
@Qualifier(value = "userDaoImpl11")//根据名称进行注入
(3)@Resource:可以根据类型注入,也可以根据名称注入。根据导包来看,import javax.annotation.Resource;是 java自带的注解,不是框架里有的。
@Resource(name = "userDaoImpl11")//根据名称进行注入
(4)@Value:注入普通类型属性
@Value(value = "老王")
private String name;
4、完全注解开发
创建一个配置类,替代xml配置文件。@Configuration作为配置类注解,可以替代xml配置文件,
组件扫描也可以用注解@ComponentScan(basePackages = {"com"})实现。
package com.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration //作为配置类,替代xml配置文件
@ComponentScan(basePackages = {"com"})
public class SpringConfig {
}