文章目录
Spring Core(核心容器)
spring core提供了IOC,DI,Bean配置装载创建的核心实现
核心概念: Beans、BeanFactory、BeanDefinitions、ApplicationContext
- spring-core :IOC和DI的基本实现
- spring-beans:BeanFactory和Bean的装配管理(BeanFactory)
- spring-context:Spring context上下文,即IOC容器(AppliactionContext)
- spring-expression:spring表达式语言
一、IOC容器
1、控制反转(ioc)
- 控制反转是一种
思想
- 控制反转是为了降低程序耦合度,提高程序扩展力
- 控制反转,反转的是什么?- 将
对象的创建
权利交出去,交给第三方容器负责- 将对象和对象之间关系
的维护权交出去,交给第三方容器负责 - 控制反转这种思想如何实现呢?- DI(Dependency Injection):依赖注入
2、依赖注入
DI(Dependency Injection):依赖注入,依赖注入实现了控制反转的思想
- 指Spring创建对象的过程中,将对象
依赖属性
通过配置进行注入
- 依赖注入常见的实现方式包括两种:- set注入- 构造注入
- Bean管理说的是:Bean对象的
创建
,以及Bean对象中属性的赋值
(或者叫做Bean对象之间关系的维护)
所以结论是:IOC 就是一种控制反转的思想, 而 DI 是对IoC的一种具体实现
3、IoC容器在Spring的实现
- Spring 的 IoC 容器就是 IoC思想的一个落地的产品实现
- IoC容器中管理的组件也叫做 bean
- 在创建 bean 之前,首先需要创建IoC 容器
- Spring 提供了IoC 容器的两种实现方式:- BeanFactory- 这是 IoC 容器的
基本实现
- 是 Spring 内部使用的接口- 面向 Spring 本身,不提供给开发人员使用- ApplicationContext-BeanFactory 的子接口,提供了更多高级特性
- 面向 Spring 的使用者- 几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory
ApplicationContext的主要实现类
类型名简介ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象FileSystemXmlApplicationContext通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象ConfigurableApplicationContextApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力WebApplicationContext专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中
二、基于XML管理Bean
搭建项目
- 添加依赖
<!--spring context依赖--><!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.3</version></dependency>
- 引入java类
publicclassUser{}
- beans.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"><!--1 获取bean演示,user对象创建--><bean id="user"class="com.xc.spring6.iocxml.bean.User"></bean></beans>
1、获取bean
方式一、根据id获取
publicclassTestUser{publicstaticvoidmain(String[] args){ApplicationContext context =newClassPathXmlApplicationContext("bean.xml");// 根据id获取beanUser user =(User) context.getBean("user");}}
方式二、根据类型获取
publicclassTestUser{publicstaticvoidmain(String[] args){ApplicationContext context =newClassPathXmlApplicationContext("bean.xml");User user = context.getBean(User.class);}}
方式三、根据id和类型获取bean
publicclassTestUser{publicstaticvoidmain(String[] args){ApplicationContext context =newClassPathXmlApplicationContext("bean.xml");User user = context.getBean("user",User.class);}}
注意
- 当根据类型获取bean时,要求IOC容器中
指定类型
的bean有且只能有一个 - 当IOC容器中一共配置了两个
<bean id="user"class="com.xc.spring6.iocxml.bean.User"></bean><bean id="user1"class="com.xc.spring6.iocxml.bean.User"></bean>
根据类型获取时会抛出异常:NoUniqueBeanDefinitionException(没有唯一Bean定义异常)
扩展
- 如果组件类实现了接口,
根据接口类型
可以获取 bean 吗?(可以
,前提是bean唯一) - 如果一个接口有
多个实现类
,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗?(不行
,因为bean不唯一)
2、依赖注入之setter注入
- 创建学生类Student
@DatapublicclassStudent{privateInteger id;privateString name;privateInteger age;privateString sex;}
- 配置bean时为属性赋值
<beanid="studentOne"class="com.xc.spring6.bean.Student"><!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 --><!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) --><!-- value属性:指定属性值 --><propertyname="id"value="1001"></property><propertyname="name"value="张三"></property><propertyname="age"value="23"></property><propertyname="sex"value="男"></property></bean>
- 测试
@TestpublicvoidtestDIBySet(){ApplicationContext ac =newClassPathXmlApplicationContext("spring-di.xml");Student studentOne = ac.getBean("studentOne",Student.class);System.out.println(studentOne);}
3、依赖注入之构造器注入
- 在Student类中添加有参构造
publicStudent(Integer id,String name,Integer age,String sex){this.id = id;this.name = name;this.age = age;this.sex = sex;}
- 配置bean
<bean id="studentTwo"class="com.xc.spring6.bean.Student"><constructor-arg value="1002"></constructor-arg><constructor-arg value="李四"></constructor-arg><constructor-arg value="33"></constructor-arg><constructor-arg value="女"></constructor-arg></bean>
注意:
constructor-arg标签还有两个属性可以进一步描述构造器参数
- index属性:指定参数所在位置的索引(从0开始)
- name属性:指定参数名
- 测试
@TestpublicvoidtestDIByConstructor(){ApplicationContext ac =newClassPathXmlApplicationContext("spring-di.xml");Student studentOne = ac.getBean("studentTwo",Student.class);System.out.println(studentOne);}
4、特殊值处理
字面量赋值
<!-- 使用value属性给bean的属性赋值时,Spring会把value属性的值看做字面量 --><propertyname="name"value="张三"/>
null值
<propertyname="name"><null/></property>
xml实体
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 --><!-- 解决方案一:使用XML实体来代替 --><propertyname="expression"value="a < b"/>
CDATA节
<propertyname="expression"><!-- 解决方案二:使用CDATA节 --><!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 --><!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 --><!-- 所以CDATA节中写什么符号都随意 --><value><![CDATA[a < b]]></value></property>
5、为对象类型属性赋值
- 员工类和部门类
// 部门累@DatapublicclassDept{privateString dname;}// 员工类@DatapublicclassEmp{//对象类型属性:员工属于某个部门privateDept dept;//员工名称privateString ename;//员工年龄privateInteger age;}
方式一:引用外部bean
<beanid="dept"class="com.xc.spring6.iocxml.ditest.Dept"><propertyname="dname"value="安保部"></property></bean>
- 使用
ref属性
给Emp类的部门属性赋值对象
<beanid="emp"class="com.xc.spring6.iocxml.ditest.Emp"><!--注入对象类型属性
private Dept dept;
--><propertyname="dept"ref="dept"></property><!--普通属性注入--><propertyname="ename"value="lucy"></property><propertyname="age"value="50"></property></bean>
方式二:内部bean
<beanid="emp2"class="com.xc.spring6.iocxml.ditest.Emp"><!--普通属性注入--><propertyname="ename"value="mary"></property><propertyname="age"value="20"></property><!--内部bean--><propertyname="dept"><beanid="dept2"class="com.xc.spring6.iocxml.ditest.Dept"><propertyname="dname"value="财务部"></property></bean></property></bean>
方式三:级联属性赋值
- name属性 = 对象.属性名
<beanid="dept3"class="com.xc.spring6.iocxml.ditest.Dept"><propertyname="dname"value="技术研发部"></property></bean><beanid="emp3"class="com.xc.spring6.iocxml.ditest.Emp"><propertyname="ename"value="tom"></property><propertyname="age"value="30"></property><propertyname="dept"ref="dept3"></property><propertyname="dept.dname"value="测试部"></property></bean>
6、为数组类型属性赋值
- Emp员工类添加爱好属性数组
//爱好privateString[] loves;
<beanid="emp"class="com.xc.spring6.iocxml.ditest.Emp"><!--普通属性--><propertyname="ename"value="lucy"></property><propertyname="age"value="20"></property><!--对象类型属性--><propertyname="dept"ref="dept"></property><!--数组类型属性--><propertyname="loves"><array><value>吃饭</value><value>睡觉</value><value>敲代码</value></array></property></bean>
7、为集合类型属性赋值
- Dept部门类添加员工集合
//一个部门有很多员工privateList<Emp> empList;
<beanid="empone"class="com.xc.spring6.iocxml.ditest.Emp"><propertyname="ename"value="lucy"></property><propertyname="age"value="20"></property></bean><beanid="emptwo"class="com.xc.spring6.iocxml.ditest.Emp"><propertyname="ename"value="mary"></property><propertyname="age"value="30"></property></bean><beanid="dept"class="com.xc.spring6.iocxml.ditest.Dept"><propertyname="dname"value="技术部"></property><propertyname="empList"><list><refbean="empone"></ref><refbean="emptwo"></ref></list></property></bean>
若为Set集合类型属性赋值,只需要将其中的list标签改为
set标签
即可
8、基于xml自动装配
- 根据指定的策略,在IOC容器中匹配某一个bean
- 自动为指定的bean中所依赖的类类型或接口类型属性赋值
- 基于XML自动装配,底层使用set注入
bean类
publicclassUserServiceImplimplementsUserService{privateUserDao userDao;publicvoidsetUserDao(UserDao userDao){this.userDao = userDao;}@OverridepublicvoidaddUserService(){
userDao.addUserDao();}}
配置bean.xml
- 自动装配方式:byType
- byType:根据
类型匹配
IOC容器中的某个兼容类型的bean,为属性自动赋值- 若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null- 若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException
<bean id="userService"class="com.xc.spring6.autowire.service.impl.UserServiceImpl"
autowire="byType"></bean><bean id="userDao"class="com.xc.spring6.autowire.dao.impl.UserDaoImpl"></bean>
- 自动装配方式:byName
- byName:将自动装配的属性的
属性名
,作为bean的id在IOC容器中匹配相对应的bean进行赋值
三、基于注解管理Bean(☆)
1、配置类替换bean.xml
@Configuration//@ComponentScan({"com.xc.spring6.controller", "com.xc.spring6.service","com.xc.spring6.dao"})@ComponentScan("com.xc.spring6")publicclassSpring6Config{}
加载配置类:
ApplicationContext context1 =newAnnotationConfigApplicationContext(Spring6Config.class);
2、@Autowired注入
- 单独使用@Autowired注解,
默认根据类型装配
- 查看注解源码
@Target({ElementType.CONSTRUCTOR,ElementType.METHOD,ElementType.PARAMETER,ElementType.FIELD,ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceAutowired{booleanrequired()defaulttrue;}
- 注解可以标注的位置- 构造方法上- 方法上- 形参上- 属性上- 注解上
- 该注解有一个required属性- 默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错- 如果required属性设置为false,表示注入的Bean存在或者不存在都没关系
- 先根据
类型
去找Bean,如果找到多个再根据名称
确定一个(确定不了抛异常不唯一)
方式一:属性注入(
最常用
)
- 基于注解自动装配,底层使用反射注入,故不需要set方法
@ServicepublicclassUserServiceImplimplementsUserService{@AutowiredprivateUserDao userDao;}
方式二、set注入
@ServicepublicclassUserServiceImplimplementsUserService{privateUserDao userDao;@AutowiredpublicvoidsetUserDao(UserDao userDao){this.userDao = userDao;}}
方式三、构造方法注入
@ServicepublicclassUserServiceImplimplementsUserService{privateUserDao userDao;@AutowiredpublicUserServiceImpl(UserDao userDao){this.userDao = userDao;}}
方式四、形参注入
@ServicepublicclassUserServiceImplimplementsUserService{privateUserDao userDao;publicUserServiceImpl(@AutowiredUserDao userDao){this.userDao = userDao;}}
方式五、只有一个构造函数,通过构造器注入不需要注解
- 当有参数的构造方法只有一个时,@Autowired注解可以省略
@ServicepublicclassUserServiceImplimplementsUserService{privateUserDao userDao;publicUserServiceImpl(UserDao userDao){this.userDao = userDao;}}
方式六、@Autowired注解和@Qualifier注解联合
- 如果UserDao接口有
两个实现类
- 通过@Autowired注入会抛异常(expected single matching bean but found 2:xx1,xx2)
- 此时可以通过@Qualifier注解指定具体bean的名称
@ServicepublicclassUserServiceImplimplementsUserService{@Autowired@Qualifier("userDaoImpl")// 指定bean的名字privateUserDao userDao;}
3、@Resource注入
@Resource注解也可以完成属性注入。那它和@Autowired注解有什么区别?
- @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分- 所以该注解是标准注解,更加具有通用性- JSR-250标准中制定的注解类型。JSR是Java规范提案
- @Autowired注解是Spring框架自己的
- @Resource注解默认根据名称装配byName,通过name找不到的话会自动启动通过类型byType装配
- @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用
- @Resource注解用在属性上、setter方法上
- @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上
@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖
如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖
<dependency><groupId>jakarta.annotation</groupId><artifactId>jakarta.annotation-api</artifactId><version>2.1.1</version></dependency>
方式一、根据name注入
@ServicepublicclassUserServiceImplimplementsUserService{@Resource(name ="userDao")privateUserDao myUserDao;}
方式二、name未知注入
- 当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名
@ServicepublicclassUserServiceImplimplementsUserService{@ResourceprivateUserDao userDao;}
方式三、name找不到的情况
- 显然当通过name找不到的时候,自然会启动byType进行注入
@ServicepublicclassUserServiceImplimplementsUserService{@ResourceprivateUserDao userDaoNotFound ;}
四、原理-手写IoC
定义标记bean的@Bean注解和依赖注入的@Di注解
- @Bean相当于@Component
- @Di相当于@Autowired
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public@interfaceBean{}@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)public@interfaceDi{}
定义bean容器接口
publicinterfaceApplicationContext{ObjectgetBean(Class<?> clazz);}
注解bean容器接口和依赖注入的实现
publicclassAnnotationApplicationContextimplementsApplicationContext{//创建map集合,放bean对象privatefinalMap<Class<?>,Object> beanFactory =newHashMap<>();privatestaticString rootPath;//返回对象@OverridepublicObjectgetBean(Class<?> clazz){return beanFactory.get(clazz);}//创建有参数构造,传递包路径,设置包扫描规则//当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化publicAnnotationApplicationContext(String basePackage){// com.xctry{//1 把.替换成\String packagePath = basePackage.replaceAll("\\.","/");//2 获取包绝对路径Enumeration<URL> urls =Thread.currentThread().getContextClassLoader().getResources(packagePath);while(urls.hasMoreElements()){URL url = urls.nextElement();String filePath =URLDecoder.decode(url.getFile(),StandardCharsets.UTF_8);//获取包前面路径部分,字符串截取
rootPath = filePath.substring(0, filePath.length()- packagePath.length());//包扫描loadBean(newFile(filePath));}}catch(Exception e){thrownewRuntimeException(e);}//属性注入loadDi();}//包扫描过程,实例化privatevoidloadBean(File file)throwsException{//1 判断当前是否文件夹if(file.isDirectory()){//2 获取文件夹里面所有内容File[] childrenFiles = file.listFiles();//3 判断文件夹里面为空,直接返回if(childrenFiles ==null|| childrenFiles.length ==0){return;}//4 如果文件夹里面不为空,遍历文件夹所有内容for(File child : childrenFiles){//4.1 遍历得到每个File对象,继续判断,如果还是文件夹,递归if(child.isDirectory()){//递归loadBean(child);}else{//4.2 遍历得到File对象不是文件夹,是文件,//4.3 得到包路径+类名称部分-字符串截取String pathWithClass = child.getAbsolutePath().substring(rootPath.length());//4.4 判断当前文件类型是否.classif(pathWithClass.contains(".class")){//4.5 如果是.class类型,把路径\替换成. 把.class去掉// com.xc.service.UserServiceImplString allName = pathWithClass.replaceAll("/",".").replace(".class","");//4.6 判断类上面是否有注解 @Bean,如果有实例化过程//4.6.1 获取类的ClassClass<?> clazz =Class.forName(allName);//4.6.2 判断不是接口if(!clazz.isInterface()){//4.6.3 判断类上面是否有注解 @Beanif(clazz.isAnnotationPresent(Bean.class)){//4.6.4 实例化Object instance = clazz.getConstructor().newInstance();//4.7 把对象实例化之后,放到map集合beanFactory//4.7.1 判断当前类如果有接口,让接口class作为map的keyif(clazz.getInterfaces().length >0){
beanFactory.put(clazz.getInterfaces()[0], instance);}else{
beanFactory.put(clazz, instance);}}}}}}}}//属性注入privatevoidloadDi(){//实例化对象在beanFactory的map集合里面//1 遍历beanFactory的map集合Set<Map.Entry<Class<?>,Object>> entries = beanFactory.entrySet();for(Map.Entry<Class<?>,Object> entry : entries){//2 获取map集合每个对象(value),每个对象属性获取到Object obj = entry.getValue();//获取对象ClassClass<?> clazz = obj.getClass();//获取每个对象属性获取到Field[] declaredFields = clazz.getDeclaredFields();//3 遍历得到每个对象属性数组,得到每个属性for(Field field : declaredFields){//4 判断属性上面是否有@Di注解if(field.isAnnotationPresent(Di.class)){//如果私有属性,设置可以设置值
field.setAccessible(true);//5 如果有@Di注解,把对象进行设置(注入)try{
field.set(obj, beanFactory.get(field.getType()));}catch(IllegalAccessException e){thrownewRuntimeException(e);}}}}}}
测试类
@BeanpublicclassUserDaoImplimplementsUserDao{@Overridepublicvoidadd(){System.out.println("dao.......");}}
@BeanpublicclassUserServiceImplimplementsUserService{@DiprivateUserDao userDao;@Overridepublicvoidout(){
userDao.print();System.out.println("Service层执行结束");}}
publicclassSpringIocTest{@TestpublicvoidtestIoc(){ApplicationContext applicationContext =newAnnotationApplicationContext("com.xc");UserService userService =(UserService)applicationContext.getBean(UserService.class);
userService.add();System.out.println("run success");}}
输出结果
service.......
dao.......
run success
版权归原作者 冬天vs不冷 所有, 如有侵权,请联系我们删除。