知识点
将要讲解如下
1 IOC底层原理
2 IOC接口卡BeanFactory
3 IOC操作Bean管理(基于xml)
4 IOC操作Bean管理(基于注解)
IOC原理和概念
1什么是IOC?
控制反转,用于降低你代码的耦合度。说的通俗点,我们原始创建对象是new对象,而IOC是解耦处理,将对象创建和调用过程交给spring管理,我们入门的案例就是IOC实现
2 IOC底层原理
底层主要用到3个,xml解析、工厂模式、反射
还是拿类似入门案例,我们比如有2个类如上图,一个类里调用另一个类,以前是new一个对象,然后调用方法,这种方法耦合度太高了,如果后者类变化,我们就影响很多(比如类路径变化)
为了降低耦合,我们衍生出了工厂模式,当然这个解耦并不是最终方案,最终是IOC
如上图,工厂模式是额外创建一个工厂类,这个工厂类负责返回对象,然后我们直接在调用类里使用其静态方法即可,当然这种耦合度并不是完全解耦,其实只是2个类之间通过工厂类连接
现在IOC过程如下图
第一步,配置xml文件,创建对象,bean标签,我们写过,id自己起一般相近,class使用类全路径
第二步,使用工厂类,这里就不能直接返回new对象了,而是使用xml获得class值赋给字符串,就是类名,通过反射获得字节码对象,然后使用newInstance方法,这里结果做了强转,默认的应该是Object
比如我们的路径有变化,那我们只需要修改xml配置文件,其他的都不用变,仍然可以创建实例,调用方法
IOC接口
IOC容器基于IOC思想(工厂模式)
spring给IOC提供了2个实现方式(接口):
1.BeanFactory:IOC容器的最基本实现,一般是其内部使用接口,一般不提供给开发人员,前者加载xml时不创建对象,而是到获得实例时创建
2.ApplicationContext:BeanFactory子接口,比其提供了更多更强大的功能,面向开发人员,加载xml配置时已经创建对象(web开发项目启动时就已经准备好)
我们之前的代码是使用的后者,换成BeanFactory也能运行,但是一般推荐ApplicationContext
我们在idea里可以CTRL+H查看继承关系,我们来查看其接口和实现类,可以看到很多,我们重点学习选中的2个,FileSystemXmlApplicationContext,ClassPathXmlApplicationContext
我们之前的案例使用的是后者,我们可以换成前者,但是前者需要传入系统盘符绝对路径
最后BeanFactory也看下层级关系,除了能看到ApplicationContext外,我们还能看到子接口ConfigurableListableBeanFactory这里简单了解下,其是用于功能扩展的接口
IOC操作Bean管理
什么是bean管理?
通俗就是2个操作,1 spring创建对象2spring注入属性
关于创建对象,之前讲过了,这里讲注入属性(我们以前定义类,比如设置个private的属性,然后要设置getter和setter,这里spring帮我们做了)
spring对bean的操作,有2种实现,一种是基于xml解析实现,另一种是通过注解实现
基于xml配置实现
基于xml创建对象
创建对象演示过了,如上,就是xml文件使用bean标签
这里同时介绍bean标签的常用几个属性
id属性 起个别名,我们使用能得到标识
class属性 创建的类所在的全路径
name属性 和id比较相近,其可以用一些特殊字符,在structs里使用过,版本比较早,现在很少用
另外spring创建对象默认是执行无参的构造方法
如果我们把构造方法改成有参的,而没写无参的,运行之前的程序就会报错
基于xml注入属性
spring里对于注入属性有DI缩写,也叫依赖注入(是IOC的一种具体实现)
我们以前给属性设置值,就是设置个setter,调用方法就实现属性的设置,或者通过有参的构造方法初始化设置值,然后我们spring也是这么做的
通过setter设置
为了加深印象,我们先创建个Book类,然后定义2个属性和setter,为了测试,定义show方法,
对于xml文件,我们当然还是写标签,但是赋值的部分写在xml配置文件里即可,如上,是在Bean标签里再加上Property标签,里面2个属性name和value,当然不用我说了,一个是我们定义的成员变量名,一个是我们设置的值,这里我们给2个类成员变量都赋值
这里还是修改测试类文件,我们这里注意了换了新类,对应位置要替换,运行show方法,可以看到属性赋值进去了
通过有参构造设置
如上,我们给之前的Book类加上有参构造方法
这次我们把之前的property标签删掉,是在bean标签内部使用constructor-arg标签,里面还是使用name,value设置值
运行结果当然还是如我们所想
其中,constructor-arg标签还可以使用index标签,0,1表示其有参构造的参数顺序,不过一般还是使用name多些,防止写乱顺序
p名称注入
也是通过setter注入,其不需要bean内再使用额外标签,但是需要配置p名称空间
如上,最开始xml的错误果然就出现了,我们定义p名称空间,需要xml约束里添加,我们复制beans那行网址,然后粘贴一行,这里因为不能和之前的重复,左侧我们使用xmlns:p,网址里p替换beans,然而网页还是标红,视频里并没有报错
我们在file-settings-languages&frameworks->schemas and DTDs 右下的加号,添加忽略的网址,点Ok确定
确定后就不标红了,这里定义了p名称空间,只要bean标签里给p:属性名赋值即可,如上,
需要注意的是,这是通过setter方法设置,我们需要把之前定义的有参构造注释了,这个我们了解下就好,一般还是使用内部添加标签多一些
xml注入其他属性及特殊符号
字面量,什么是字面量,给属性赋的值就是字面量,我们之前都是给属性赋值,这里专门讲2个特殊情况
1赋值null
xml配置文件里,使用property标签,比如我们想给bauthor赋空,如上,需要指定name属性,value属性不写,然后内部嵌套一个null标签
运行结果如上,可以看到实现了赋null
null标签除了
2赋值特殊符号
比如我们想给书使用<<>>英文的大小于符号,ide就会报错,为什么,是因为将其识别成了标签,而标签格式不正确,如果就是要使用呢,就需要其他办法
办法1 转义< >来替代小于,大于
如上,记得不要少了分号
办法2,使用xml的原始内容展示
代码如上,我们可以把value单独作为property内部标签,里面使用的单标签,这个内容不用加引号,效果是一样的
注入外部Bean
什么情况下使用呢,比如我们想实例的不是book了,而是User,里面的属性有Book类属性,我们定义方法等待测试调用
我们知道创建book对象的写法,如上下面的bean标签,创建user也是,这里设置属性,使用property,但是属性value就不用了,因为我们创建了Book对象,这个对象有id,我们只需要把id传给ref属性即可,如上,即完成了给User的book属性赋值,
最后测试调用方法,如上,这里的关键其实是设置ref属性,然后将id传入,其中记得给User设置setter方法
注入属性内部Bean和级联赋值
我们数据库表之间的关系,有时有1对多,如部门和员工,就是就考虑定义部门类Dep,员工类Emp
如上,因为员工只有一个部门,所以我们给其设置部门属性,并定义测试函数
当然我们要实例的是emp对象,我们可以像之前的使用外部bean,当然,这里是使用内部bean,即property设置value的标签部分我们使用新的bean标签替代,
最后运行结果如上,很简单
级联赋值
级联赋值分2种,第一种就是我们之前使用的外部Bean,不重复说了
第二种如下
如上,xml里bean部门写在外边,其属性设置值了,但是我们在员工bean里使用外部引入后,可以在对其属性的name进行设置,这里需要注意的是,我们的Emp类需要给dep属性设置getter,否则会报错,
如上,设置getter及运行结果,此项相当于给初始的dep.name又进行了赋值
xml注入集合属性
比如类有数组、list,set,map集合等,多个元素,也是可以通过xml注入的
我们定义Stu类,定义属性为以上几种,并定义setter,和待测试的方法,层级关系如上
然后就是给这些属性赋值了,创建bean标签不用说了,property还是用来赋值的标签,这里多元素数组在property内使用array标签,然后用value标签包裹元素,List,Set分别使用list,set包裹,Map是使用map包裹,但是里面使用的是entry标签,里面的属性使用key,value给键值对赋值,(视频里说list和array是都可以的对于数组,List。。没去试)
最后就是运行测试了,可以看到集合都被成功赋值了
集合元素为引用对象注入
比如我们创建Course类,将其给Stu
2个类修改如上,之前定义的一大堆被精简了
在xml里,我们还是list内嵌property,但是元素不是使用value包裹,而是使用ref包裹,ref有属性bean,这个值就是我们需要创建bean标签对象的id,于是我们下面写了2个bean标签
运行结果如上
xml集合抽取注入
我们之前使用xml实现了注入集合属性,有时候需要把集合抽取出来,可能供别的bean多次调用,而不是仅类内部调用
我们对Stu修改,不用Course类,这里仅设置List即可,
重点是xml进行名称空间设置
第一处设置,跟我们之前说的p名称空间一样,我们设置util名称空间,
第二个设置,对xsi:shemaLocation属性值的网址添加网址,就是把上面一行复制过来,带beans,都换成util即可,注意还是在双引号内
定义了名称空间,我们就能使用util标签,里面有:list可选,设置id属性为了别人调用,里面使用value设置集合,然后还是创建bean标签了,这里property就不用使用value了,使用ref属性,值为前面的id
运行结果如上
IOC操作Bean管理
IOC管理对象一种是通过Bean,我们之前讲过,一种是FactoryBean,注意和BeanFactory不一样,也叫工厂Bean。
我们的普通Bean,在我们xml里定义了什么,context返回Bean使用什么字节码和id就返回什么类型的对象,而工厂Bean,返回可以不是同一个类型
使用步骤:创建类实现FactoryBean接口,实现对应方法,返回指定类型
比如上面我们定义MyBean类,其要求复写3个方法,其实只修改第一个就行(我们暂时只用这个,用来获取对象的),还有获取对象类型和是否为单例,暂时也不关心这2个,第一个方法默认返回null,我们只要让其返回指定类型即可
我们给其返回Stu实例,为了测试,我们给其设置booklist,
在xml里,我们需要注入就是MyBean,这没有变,
但是测试里,我们不能用MyBean来接收了,因为返回类型是Stu,运行结果如上
IOC管理----Bean的作用域
spring创建bean默认是单例的,我们可以设置为多实例
先看单例效果
我们不使用工厂类,就使用普通Bean,输出可以看到我们就得到一个对象
我们可以在Bean标签里的scope属性设置值,常用的有1默认值singleton(单实例),2 prototype多实例
我们设置其为多实例时,可以看到内存地址不一样
scope的2个属性值还有差别,当默认或者设置singleton时,我们读xml文件得到context对象时已经创建了实例(单例),但是prototype时,其到获得Bean才生成实例
scope属性还有request和session,是请求和对话,设置后就会把实例放在request和session中,当然很少使用这两个值
IOC管理----Bean的生命周期
什么是周期,对象从创建到销毁的过程
bean的生命周期:1通过构造器创建对象(无参或者constructor-arg有参)2设置属性值和其他bean的引用(使用setter),3 调用Bean的初始化方法(需要配置) 4使用Bean对象 5销毁Bean(也需要配置)
为了看得更清楚,我们给无参构造写出来,并setter打印,同时定义init,和end方法,如果只是这么写,并看不出会在什么时候调用,
在xml配置里,我们需要使用init-method属性,值为我们初始化函数名,destroy-method属性值为销毁函数名,
在测试类里,我们调用当然是第四步了,这里销毁需要我们执行context的close方法,注意这里我们context对象并没有使用多态写法,因为默认接口没有close方法,只有实现类有,所以直接2侧类一样,效果如上
7步生命周期
当然以上只是5步演示,其实spring对bean的生命周期控制一个有7步,就是在第3步初始化前后分别有2个后置处理器的配置方法
我们创建类MyBeanPost,让其继承BeanPostProcessor后置处理器接口,我们复写2个方法(只是加上打印)
在xml配置里,导入后置处理器到bean标签,然后不用写别的,因为我们继承了后置处理器后就会默认在所有bean的初始化方法前后执行对应方法(不管你写接Bean,每个Bean有初始化都会前后执行)
运行结果如上,可以看到后置处理器的方法
XML自动装配
我们之前给对象注入属性,是使用xml像property标签,使用name,value设置,这是手动装配,而自动装配我们就不需要使用property标签了,而是根据名称或类型自动装配
我们先展示一个手动装配案例
如上,我们定义学生类,里面属性Book类,然后打印对象测试,我们之前使用外部Bean,ref属性如上实现,而现在我们就要使用自动装配了,涉及到Bean的autowire属性
autowire属性值可以是byName,byType,我们使用byName,就会按照名称自动查找,这里下面的bean的id必须和Stu成员变量名一样,否则Spring找不到,如上,运行结果是一样的,可以注入属性
byType是按类型自动装配,但是这会有问题,当然你会想到多个类型一样的Bean
比如多个一样类型的Bean,Spring就不知道装哪个了,所以还是通过id,byName准确些
视频里也说了xml自动装配还是比较少
XML管理Bean(外部属性文件)
我们已经用了多次xml配置Bean了,如果Bean的设置property比较多,我们如果改动可能就要回xml里更改比较麻烦,一般把不变的属性放到一个外部的文件中,像数据库账户密码等
配置Druid连接池
Druid是数据库连接池,然而我此时还没用过,需要安装jar包,包在它资源里可以找到我们还是复制到lib下,然后file->project-structure里导入包,
我们直接xml配置,可能要如上图所写,4个属性分别为驱动,数据库地址,用户和密码如上
现在我们进行外部文件的配置来实现功能,首先创建.properties文件
里面使用的是=连接的内容,我们把Key=value写上,这里加上prop.是为了防止冲突
下一步是创建context名称空间,相信util名称空间你已经学会了,这里照猫画虎
如上,我们给util复制,同时替换为context即可
接下来我们就动value值,因为我们配置了外部文件,value就可以使用spring引用格式,${}内部使用我们配置文件的key即可
基于注解方式创建对象
我们之前已经讲解了基于xml创建对象,现在我们讲解基于注解创建对象
注解:@注解名称(属性名=属性值,...属性名=属性值)
我们使用注解可以简化配置
spring对Bean管理提供了几个内置注解
@Component
@Service 一般用于业务逻辑层或者Service层上
@Controller 一般用于Web层上
@Repository 一般用于数据访问层
上面4个注解功能是一样的,都可以创建Bean实例,但是名称使对象创建更有逻辑性
使用步骤
1引入依赖
除了之前的安装jar包,我们需要新的,这个是他们教学资料里的,安装完idea导入下,不重复说了
2开启spring组件扫描
首先要确保xml文件里已经引入context名称空间,我们之前的xml里引入过,所以这里以直接用,
我们给spring5包下新建2个包,如果我们组件扫描在package1中,我们使用context:component-scan标签(使用此标签,spring就知道我们要使用注解来生成对象),属性base-package传入包路径,如上,如果我们想使用多个包呢,比如package1,2都用
一种方法是如上,使用逗号分割,写入2个包,
另一种写法是直接使用上级目录,spring会自动扫描
3 创建类,添加注解
比如我们在package1里创建类UserService,然后我们就可以类前面加上我们之前说的内置注解,这里给value赋值等同于之前bean标签的id值,这里value赋值是可以省略的,如果不写,自动设置value为类名称首字母小写
最后就是测试了,可以看到通过注解可以实现创建对象
4组件扫描细节配置
我们上节使用组件扫描context:component-scan标签规定扫描文件夹,则此文件夹里所有的类都会被扫描,我们可以细节配置哪些类被扫描到,哪些不被扫描
如上,我们要看懂如上的xml配置,这里我们还是使用组件扫描标签,里面使用了context:include-filter是包含的过滤器标签,类型是annotation注解,表达式,表示我们只扫描带Controller注解的类
上图和之前的又不一样,我们使用的是context:exclude-filter,没错,就是扫描不包含Controller注解的类
5基于注解属性注入
我们刚才只是对象创建,没有注入属性,这里我们学习使用注解注入属性,需要关系3个注解
@AutoWired 根据属性类型自动注入
我们定义Book类,给其加上注解用于创建对象
创建Stu类,还是使用注解创建对象,这里属性Book我们使用@AutoWired,我们使用注解,就不用setter方法了,spring自动帮我们做
xml使用组件扫描,
测试结果如上,可以看到Stu实例的Book被自动创建了
@Qualifier 根据属性名称进行注入,要和@AutoWired一起使用
我们知道注解可以设置value,否则是默认类名首字母小写,我们起名book111,
如上,我们可以使用@Qualifier注解,使用我们刚才设置的名称,有人也许会问,这么做有什么用?比如我们的属性是使用多态写法,那属性名就是接口名,而接口可以有多个实现类,每个实现类可能不一样,我们用哪个实现类就有讲究了,使用@Qualifier给实现类起id,就保证不会使用错
@Resource 可以根据类型或名称注入
老师视频里的Java版本是8而我使用9,所以出错了,因为9没有Javax包,其实效果和之前的一样,不使用value,则按类型注入,使用value则按名称注入,视频里也说了Resource不是Spring包里,是扩展包,不推荐使用,反正9也用不了- -
@Value 注入普通类型(如String)
比如我们修改Stu类,设置属性name,我们用Value标签设置其属性值,就不用像xml写bean->property标签了
测试结果如上
纯注解开发
我们之前已经演示了注解注入属性生成对象,注解目的是简化xml配置,而现在我们可以考虑使用纯注解(不使用xml配置文件)开发
使用步骤:1创建配置类
我们把之前的乱七八糟全删了,建包config,里面创建SpringConfig类,然而不是这么建立就完了,需要添加注解@ComponentConfiguration让Spring知道是配置类
我们知道原来的基于xml写注解需要写上组件扫描,这里我们使用@ComponentScan,属性使用文件路径
2 编写测试类
对于配置注解创建对象和属性设置不用说了,这里测试类会发生变化,因为之前我们使用的是xml文件生成context对象
我们的待生成对象的User类
测试类如上,我们之前使用的xml相关的Context,这次使用AnnotationConfigApplicationContext,参数是配置类的字节码,然后其他使用就一样了
实际使用中,一般是使用完全注解开发多一些