一、Spring概念与IOC
JavaWeb发展史:
1、Spring概述
简单概述:Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。
2、IoC概念介绍
- IoC: Inversion of Control,控制反转、依赖注入
【1】控制什么?
控制对象的创建及销毁(生命周期)
【2】反转什么?
将对象的控制权交给IoC容器
【3】为什么要使用IOC ?
模拟场景解读:回家
①、②之间的区别在于,我自己走路回家,从出发到到达目的地都是自己在执行;而②中是将自己要回家的任务交由车辆执行,自己只需要依赖于车辆(坐在车上)就可以,等车辆到达目的地,下车就可以。
public class ZhangSan extends HumenWithCar{
public void goHome(){
Audi audi = new Audi();
audi.start();
audi.turnLeft();
audi.turnRight();
audi.stop();
}
public void goShop(){
Audi audi = new Audi();
audi.start();
audi.turnRight();
audi.turnLeft();
audi.stop();
}
}
由此引申出,默认情况下,我们调用一个实例时,大部分情况由自己手动创建,然后才能去达到目的;使用IOC后,只需要掌握主要的方向,将实际的操作交由容器去处理,在需要的时候调用容器返回的结果,如@Autowire注解的实例(这是基于多态的实现),便可以达到调用bean实例的目的;
不过,IOC真正的意义在于可以降低代码的耦合度;当目标人物只有一辆车时,使用IOC进行依赖注入效果并不明显,但是,如果目标人物有多辆车呢?
按照传统的做法,每一辆车都创建一个实例,奥迪 是 new Audi(),别克是 new Buick();......
这就显得很繁琐,代码也会很冗余,每一次替换车辆都会造成代码大幅度的改动,增加了工作量。
而IOC则能很好的解决这个问题,由前文中所描述的,IOC的底层依赖注入使用了类似 多态的结构,这也就意味着可以通过接口的实现或类的继承的方式来通过进行动态调用 车辆的实例,而不需要修改整行的实例对象代码。
例如:奥迪和别克都实现了车的接口,那么就可以通过多态的形式,将车类的共性方法或属性放在父类中,独有的各自声明,当需要调用时,由容器返回父类的实例,再通过泛型的方式声明当前调用的是哪一种车?然后返回实例。
注意:
Car audi = new Audi();
多态中,返回的 "audi" 虽然是父类变量的引用,但是指向的是子类对象。
可以用它来调子类对象的方法或属性
泛型方式:
Car<Audi> audi = new Car<Audi>();这里做了进一步的解释,有利于理解。实际上容器不会这么做;而是
Car audi = new Audi();
Car buick = new Buick();
这便是底层的一部分操作的解读
【4】手动创建实例的 问题——高耦合性
- ①张三所有的行为都需要自己主动创建并销毁一辆车
- ②更换车辆的代价是巨大的
【5】改进原始的 购车计划:
将方法域中的 Audi 实例提到 成员属性所在的域中,使用 Buick 替代 Audi,这样就不需要修改 方法内的代码
进一步分析:
【1】张三需要的是一辆奥迪?一辆别克?或者就是一辆车?
张三只是需要一辆车
【2】张三会智造(创建)车么?
车辆不应该由张三来创建
那么,重点来了:Car 的 创建者是谁呢?
//改进代码
private Car car;
public ZhangSan(Car car){
this.car = car;
}
public void goHome(){
car.start();
car.turnLeft();
car.turnRight();
car.stop();
}
public void goShop(){
car.start();
car.turnRight();
car.turnLeft();
car.stop();
}
在Audi和 Buick 实现Car 的接口后,在 ZhangSan中声明 Car 的实例,在这里,Car 的实例不是有张三所创建的,将其放到构造方法中,
public class Audi implements Car{
.........
}
public class Buick implements Car{
.........
}
注意: 从代码中可知,车辆的创建和销毁已经不是 ZhangSan 所 执行的,那么也就意味着ZhangSan 失去了对对象的控制权;
那么,对象的可控制权是交给谁呢?
答案就是 IOC 容器;而这也是 IOC 中的 “控制反转”中“反转” 的真正体现
3、实现一个自己的IoC
【1】实现一个自己的 IOC , 场景设计
【2】简化 IOC 业务逻辑的 三个约定
- 所有Bean的生命周期交由IoC容器管理
- 所有被依赖的Bean通过构造方法执行注入
- 被依赖的Bean需要优先创建
第一步:声明一个 Humen 人类接口
public interface Humen {
public void hoHome();
}
第二步 : 创建一个 HumenWithCar 类 ,关联人与车;将该类声明为 抽象类,类中的 Car 实例通过构造方法获取
public abstract class HumenWithCar implements Humen{
private Car car;
public HumenWithCar(Car car){
this.car = car;
}
public abstract void hoHome();
}
第三步:创建 ZhangSan ,继承 HumenWithCar 类,并重写 hoHome 方法
public class ZhangSan extends HumenWithCar{
public ZhangSan(Car car){
super(car);
}
@Override
public void hoHome() {
car.start();
car.turnLeft();
car.turnRight();
car.stop();
}
}
第四步:创建LiSi,继承HumenWithCar类,并重写 hoHome 方法
public class LiSi extends HumenWithCar {
public LiSi(Car car){
super(car);
}
@Override
public void hoHome() {
car.start();
car.stop();
}
}
第五步:创建 IOC 容器 IoCContainer 类,声明一个Map 类型的 beans
并创建 GET/SET 两个方法用于对Bean实例进行赋值与获取值
public class IoCContainer {
private Map<String,Object> beans = new ConcurrentHashMap<String,Object>();
public Object getBean(String beanId){
return beans.get(beanId);
}
public void setBeans(Class<?> clazz,String beanId,String... paramBeanIds){
}
}
getBean() 用于根据beanId 获取实例Bean 并 返回 Bean:
/*
* 根据beanId 获取一个Bean
* @param beanId beandId
* @return 返回bean
* */
public Object getBean(String beanId){
return beans.get(beanId);
}
setBean()用于委托ioc 容器创建一个bean,具体的创建Bean的过程是通过反射实现的
/*
* 委托ioc 容器创建一个bean
* @param clazz 要创建的bean的class
* @param beanId beanId
* @param paramBeanIds 要创建的bean的构造方法所需要的参数的beanId们
* */
public void setBeans(Class<?> clazz,String beanId,String... paramBeanIds){
//1、组装构造方法所需要的参数值
Object[] paramValues = new Object[paramBeanIds.length];
for(int i = 0; i< paramBeanIds.length;i++){
paramValues[i] = beans.get(paramBeanIds[i]);
}
//2、调用构造方法实例化bean
Object bean = null;
for (Constructor<?> constructor : clazz.getConstructors()){
try {
bean = constructor.newInstance(paramValues);
}catch (InstantiationException e){
}catch (IllegalAccessException e){
}catch (InvocationTargetException e){
}
}
if (bean == null){
throw new RuntimeException("找不到合适的构造方法去实例化bean");
}
//3.将实例化的bean 放入beans
beans.put(beanId,bean);
}
第六步:测试类:
在测试类中,创建 自定义 IOC 容器 的实例,
使用@Before 管理依赖关系, 使用 自定义IOC 的 实例对象 调用setBean ()传入 类的class 和 要调用的构造器的 beanId
public class ClassTest {
private IoCContainer ioCContainer = new IoCContainer();
@Before
public void before(){
ioCContainer.setBeans(Audi.class,"audi");
ioCContainer.setBeans(Buick.class,"buick");
ioCContainer.setBeans(ZhangSan.class,"zhangSan","audi");
ioCContainer.setBeans(LiSi.class,"liSi","buick");
}
@Test
public void test(){
Humen zhangsan = (Humen)ioCContainer.getBean("zhangSan");
zhangsan.hoHome();
Humen liSi = (Humen)ioCContainer.getBean("liSi");
liSi.hoHome();
}
}
总结:
4、Spring IoC入门
使用Spring提供的IOC 容器:
【1】创建Bean类
package com.webcode;
public class Bean {
public Bean(){
System.out.println("Bean.Bean");
}
}
【2】引入Spring 的依赖, 包括 spring-core、spring-context
<!-- 加入Web开发所需要的场景启动器 -->
<dependency>
<!-- 指定groupId和artifactId即可,版本已在父工程中定义 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--<version>5.1.41</version> -->
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
【3】创建 spring.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">
<bean id="bean" class="com.webcode.Bean"/>
</beans>
【4】创建test测试类,通过读取配置文件,从服务上下午获取 bean 对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Bean1 bean = context.getBean("bean",Bean.class);
System.out.println("bean" + bean);
总结:
二、Bean的实例化
使用Spring 实例化 Bean的4中方式:
1、通过构造方法实例化Bean;
【1】创建Bean1
public class Bean1 {
public Bean1(){
System.out.println("Bean.Bean");
}
}
【2】创建对应的Bean1配置:
<bean id="bean1" class="com.webcode.Bean1"/>
【3】测试:
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Bean1 bean = context.getBean("bean1",Bean1.class);
System.out.println("bean" + bean);
2、通过静态方法示例化Bean
【1】创建Bean2
public class Bean2 {
public Bean2(){
System.out.println("Bean2.Bean2");
}
}
【2】创建Bean2的静态工厂类,并创建对应的getBean2()方法去实例化一个Bean2
public class Bean2Factory {
public static Bean2 getBean2(){
return new Bean2();
}
}
创建与Bean2 对应的 <bean> 配置
<bean id="bean2" class="com.webcode.Bean2Factory" factory-method="getBean2" />
test测试创建 Bean2 的实例,由静态工厂方法创建实例并返回
Bean2 bean2 = context.getBean("bean2", Bean2.class);
System.out.println("bean2" + bean2);
总结: 由静态工厂方法创建 bean 实例的方法有两种,
3、通过实例方法实例化Bean;
【1】创建Bean3和Bean3Factory:
public class Bean3 {
public Bean3(){
System.out.println("Bean3.Bean3");
}
}
public class Bean3Factory {
public Bean3 getBean3(){
return new Bean3();
}
}
【2】Bean3的配置注入管理:
<bean class="com.webcode.Bean3" factory-bean="bean3Factory" factory-method="getBean3" id="bean3"/>
【3】使用Bean3Factory的实例化对象,调用方法来创建Bean3的实例化:
Bean3Factory bean3Factory = new Bean3Factory();
Bean3 bean3 = bean3Factory.getBean3();
System.out.println(bean3);
4、Bean的别名
通过Bean的别名创建对象实例有多种方法:
第一种:实例化Bean 对象时 , 别名的使用:
配置<bean>文件 ,name = "bean1_1,bean1_2", id = "bean1"
<bean id="bean11" class="com.webcode.Bean1" name="bean1_1,bean1_2" />
<alias name="bean11" alias="bean1_3"/>
Bean1 bean1_1 = context.getBean("bean1_1",Bean1.class);
System.out.println(bean1_1);
Bean1 bean1_2 = context.getBean("bean1_2",Bean1.class);
System.out.println(bean1_2);
Bean1 bean1_3 = context.getBean("bean1_3",Bean1.class);
System.out.println(bean1_3);
由测试结果可知,使用别名创建 Bean 实例的时候,其实创建的是同一个实例对象的Bean ,指向同一个地址值:
三、注入Bean的方式:
1、通过构造方法注入Bean
【1】创建两个 类 ,分别为 Bean 和 AnotherBean 类,在Bean 类 中声明两个变量 , 分别为 AnotherBean 和 string . 并创建相应的构造方法
public class Bean {
private AnotherBean anotherBean;
private String string;
public AnotherBean getAnotherBean() {
return anotherBean;
}
public void setAnotherBean(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
@Override
public String toString() {
return "Bean{" +
"anotherBean=" + anotherBean +
", string='" + string +
'}';
}
public class AnotherBean {
}
【2】配置 Bean 类 和 AnotherBean 的实例化配置文件,
在Bean 中声明 <constructor-arg> 用于映射Bean实例中的构造方法,
<bean class="com.webcode.Bean" id="bean">
<!--通过构造方法注入Bean:-->
<constructor-arg index="0" name="anotherBean"
type="com.webcode.AnotherBean"
ref="anotherBean"/>
<constructor-arg index="1" name="string" type="java.lang.String"
value="aaaaa"/>
</bean>
【3】@Test测试类,获取Bean对象实例
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Bean bean = context.getBean("bean", Bean.class);
System.out.println(bean);
2、通过set方法注入Bean
【1】在Bean类中创建 类型为 AnotherBean 的变量 anotherBean ;以及 类型为String 的 变量string1;并创建对应的SET/GET 方法
private AnotherBean anotherBean1;
private String string1;
<!--注入时创建内部Bean :-->
<property name="anotherBean1">
<bean class="com.webcode.AnotherBean"/>
</property>
<property name="string1" value="bbbb"/>
【2】启动 Test测试类,通过打印的结果可以看到,当Bean被实例化时,所配置的 bean1 和anthorBean1 的值也被打印出来 了
3、集合类Bean的注入
【1】分别创建包括: List 、Map、Set、Properties 的四中属性的变量或对象,并声明相应的 SET/GET 方法
private List<String> stringList;
private List<AnotherBean> anotherBeanList;
private Map<String,String> stringMap;
private Map<AnotherBean,AnotherBean> anotherBeanMap;
private Set<String> stringSet;
private Set<AnotherBean> anotherBeanSet;
private Properties properties;
【2】配置spring.xml 配置文件配置,使用 <property/> 来配置 List、Map、Set、 Properties 的bean实例
<property name="stringList">
<list>
<value>aaaaa</value>
<value>bbbbb</value>
</list>
</property>
<property name="anotherBeanList">
<list>
<ref bean="anotherBean"/>
<ref bean="anotherBean"/>
</list>
</property>
<property name="stringSet">
<set>
<value>aaaaa</value>
<value>bbbbb</value>
</set>
</property>
<property name="anotherBeanSet">
<set>
<ref bean="anotherBean"/>
<ref bean="anotherBean"/>
</set>
</property>
<property name="stringMap">
<map>
<entry key="ccccc" value="ddddd"/>
<entry key="eeeee" value="fffff"/>
</map>
</property>
<property name="anotherBeanMap">
<map>
<entry key-ref="anotherBean" value-ref="anotherBean"/>
</map>
</property>
<property name="properties">
<props>
<prop key="aaaaa" >bbbbbbbbb</prop>
</props>
</property>
【3】@Test中测试执行 Bean 的实例化
System.out.println("bean.getStringList() = " + bean.getStringList() );
System.out.println("bean.getStringMap() = " + bean.getStringMap() );
System.out.println("bean.getStringSet() = " + bean.getStringSet() );
System.out.println("bean.getAnotherBeanList() = " + bean.getAnotherBeanList() );
System.out.println("bean.getAnotherBeanMap() = " + bean.getAnotherBeanMap() );
System.out.println("bean.getAnotherBeanSet() = " + bean.getAnotherBeanSet() );
System.out.println("bean.getProperties() = " + bean.getProperties() );
4、null值注入
【1】创建 AnotherBean 类型的变量 anotherBena2 ,并声明 GET/SET 方法
private AnotherBean anotherBean2;
public AnotherBean getAnotherBean2() {
return anotherBean2;
}
【2】配置 spring.xml 的配置数据
<!--null值的注入-->
<property name="anotherBean2">
<null/>
</property>
【3】Test测试
System.out.println("bean.getAnotherBean2() = " + bean.getAnotherBean2());
5、注入时创建内部Bean
使用 <property>中包含<bean> 标签,由<bean>标签返回 bean实例的方式实现内部包含 bean 对象
<!--注入时创建内部Bean :-->
<property name="anotherBean1">
<bean class="com.webcode.AnotherBean"/>
</property>
四、Bean作用域
1、singleton、prototype
Singleton 作用域:当 Bean 实例被使用 singleton 进行单例设置后,生成的Bean 实例无论被调用多少次,都是同一个Bean实例,指向同一个内存地址
Prototype 作用域:每次向Spring上下文请求Bean 都会 new 一个 新的实例
【1】创建 Bean1 和 Bean2 ,在Bean1 中创建Bean2 类型的对象变量,并声明SET/GET 方法
public class Bean1 {
private Bean2 bean2;
public Bean2 getBean2() {
return bean2;
}
set\get省略
}
public class Bean2 {
}
【2】配置spring.xml 配置文件,声明Bean2的Scope 为 singleton ,并引入到 Bean1 中
<bean class="com.webcode.Bean2" id="bean2" scope="singleton"/>
<bean class="com.webcode.Bean1" id="bean1" >
<property name="bean2" ref="bean2"/>
</bean>
【3】@Test中进行测试,由结果可知,当Bean2被Singleton 模式化后,生成的 Bean2实例指向的是同一个地址;又由于Bean1 默认情况下是 singleton 的模式,所以Bean1 的多个实例对象指向的是同一个地址值
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Bean2 bean2_1 = context.getBean("bean2", Bean2.class);
Bean2 bean2_2 = context.getBean("bean2", Bean2.class);
注意:当Bean2 被声明为 singleton ,而 Bean1被声明为 Prototype 时, Bean2 在Spring 上下文中只实例化一次,而Bean1 属于多例,此模式下默认返回的是不同的Bean2 ,返回的Bean2 的实例地址是不同的;而由于Bean1 是 Prototype 模式的,Spring进行了对此的实例化,所以 bean1_1 == bean1_2 的比较结果是false,意味着每一次的Bean1 都进行了新的实例化
【4】当Bean2 为 Prototype 时,而Bean1为singleton时,Spring只对Bean1做了一次实例化,故只有一个地址,而由于Bean1 需要一个Bean2 的实例,所以Spring需要对Bean2 进行实例化,但是由于Bean1只实例化一次,所以Propotype模式下的Bean2 实际上只被Spring 进行了一次实例化
<bean class="com.webcode.Bean2" id="bean2" scope="prototype"/>
<bean class="com.webcode.Bean1" id="bean1" scope="singleton">
<property name="bean2" ref="bean2"/>
</bean>
2、web相关作用域
【1】创建一个 WebServlet 工程,使用Maven管理
【2】配置webServlet 的web.xml 前端控制器
<servlet>
<servlet-name>SpringServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>SpringServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
【3】创建 ApplicationController 、RequestController、 SessionController三个类
@Controller
public class SessionController {
@RequestMapping("testSession")
@ResponseBody
public String test(){
return this.toString();
}
}
@Controller
public class RequestController {
@RequestMapping("testRequest")
@ResponseBody
public String test(){
return this.toString();
}
}
@Controller
public class ApplicationController {
@RequestMapping("testApplication")
@ResponseBody
public String test(){
return this.toString();
}
}
【4】将每个Controller的 bean 实例交由 spring容器来管理,在spring.xml 中添加相应配置
<bean class="com.webcode.ApplicationController" scope="application"/>
<bean class="com.webcode.RequestController" scope="request"/>
<bean class="com.webcode.SessionController" scope="session"/>
Request作用域实例:每次刷新都会 生成新的实例
Session作用域实例:每次重新打开浏览器才会生成新的实例,同一个浏览器如果没有关闭页面,每次刷新都是相同的实例。
Application作用域实例:只要浏览器的页面不关闭,serveltContext上下文就不会变更,实例不变
【6】使用tomcat 作为容器来配置 Controller请求,可知:
3、自定义作用域
自定义双例作用域:
【1】创建MyScope.java类,并实现Scope.java,并重写方法
【2】创建两个变量:
【3】完善get\remove方法:
【4】配置Bean注入到spring中:
【5】测试:
结果:
五、Bean的懒加载与生命周期
1、Bean的懒加载
Bean的懒加载只有在标注为 Singleton 作用域时才有效
【1】关于 Bean 的配置实现方法
对单个bean配置懒加载时,只需要将lazy-init属性配置为true,
在beans标签加上default-lazy-init属性配置为true,则该文件下的所有bean都是懒加载
【2】Bean的懒加载的 适用场景
如果某个Bean在程序整个运行周期都可能不会被使用,那么可以考虑设定该Bean为懒加载。
优点:尽可能的节省资源
缺点:可能会导致某个操作响应时间增加
可以设置单个Bean的加载方式为lazy,也可以在配置文件头部的Beans标签中,统一设置default-lazy-init,这样它包含的所有Bean都是懒加载方式
2、Bean初始化及销毁逻辑处理
【1】bean初始化的方法
【2】Bean 的声明周期 之 Bean 的销毁:
Bean的销毁,如果需要在Bean销毁之前进行一些逻辑,有两种方法:
【3】实现一次Bean的声明周期:
①创建一个Bean类并创建 onInit 和 onDestroy 两个方法,分别表示 Bean的初始化和销毁,并将 Bean 的实例通过spring.xml注入到容器中
public class Bean implements InitializingBean,DisposableBean{
public void onInit(){
System.out.println("Bean.onInit");
}
public void onDestroy(){
System.out.println("Bean.onDestroy");
}
@Override
public void destroy() throws Exception {
}
@Override
public void afterPropertiesSet() throws Exception {
}
}
<bean class="com.webcode.Bean" id="bean" init-method="onInit" destroy-method="onDestroy"/>
②执行 Bean的生命周期 测试,
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Bean bean = context.getBean("bean", Bean.class);
System.out.println(bean);
context.destroy();
}
③使用 AbstractApplicationContext 可以调用 Spring 上下文的销毁方法
@Test
public void test() {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Bean bean = context.getBean("bean", Bean.class);
System.out.println(bean);
context.destroy();
}
④使用 init-method 定义实例化后的逻辑
⑤使用 destory-method 定义实例销毁前的逻辑
⑥为所有的Bean 设定默认的初始化方法和销毁方法
3、Bean属性继承
【1】一个父bean的属性,被两个子bean继承
【2】场景2,也是继承,但是父bean中的属性不能被继承,在子bean中需要分开单独写属性值
【3】Bean 属性 继承 所需要 的 Spring 配置
【4】Bean 属性 继承 实例:
第一步:创建 ParentClass 类,并创建 attribute1、attribute2、attribute3 三个变量
public class ParentClass {
private String attribute1;
private String attribute2;
private String attribute3;
set/get省略
第二步:创建 Class1 类,并创建 attribute4、attribute5 两个变量
public class Class1 extends ParentClass{
private String attribute4;
private String attribute5;
第三步:创建 Class2 类,并创建 attribute7、attribute8 两个变量
public class Class2 extends ParentClass {
private String attribute7;
private String attribute8;
第四步: 在 spring.xml 配置 Bean 到容器中,并将 ParentClass 和 Class1、Class2 关联起来
<bean class="com.webcode.ParentClass" id="parentClass" abstract="true">
<property name="attribute1" value="attribute1"/>
<property name="attribute2" value="attribute2"/>
<property name="attribute3" value="attribute3"/>
</bean>
<bean class="com.webcode.Class1" id="class1" parent="parentClass">
<property name="attribute4" value="attribute4"/>
<property name="attribute5" value="attribute5"/>
</bean>
<bean class="com.webcode.Class2" id="class2" parent="parentClass">
<property name="attribute7" value="attribute7"/>
<property name="attribute8" value="attribute8"/>
</bean>
第五步: @Test 单元测试结果
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Class1 class1 = context.getBean("class1", Class1.class);
System.out.println("class1 :"+class1);
Class2 class2 = context.getBean("class2", Class2.class);
System.out.println("class2 :"+class2);
}
代码分析:
继承机制下的的Bean属性继承场景,适用 abstract =“true" 告诉 spring 这个 bean 是不需要实例化的,再在 Class1 、Class2 中进行改造,让它们继承指向parentClass 的bean
六、注解的使用
1、注解的基本使用介绍
SpringIOC 注解的使用,要比使用配置便捷的多。
【1】对比一下通过配置文件来配置的方式:
上图是配置 spring.xml 装配一个 bean 所需要的配置,相对比较繁琐。
为了简化代码和流程,加快开发的效率,使用约定大于配置的方式。
【2】@Bean 和 @Conguration 注解的使用:
第一步:创建一个Bean1类和一个MyConfiguration 类,在MyConfiguration 类中创建 bean1 方法,返回值为 Bean1 的匿名对象;并在MyConfiguration 上添加 @Configuration 注解,和在bean1 方法上添加 @Bean 注解;
public class Bean1 {
}
@Configuration
public class MyConfiguration {
@Bean
public Bean1 bean1(){
return new Bean1();
}
}
第二步:使用 AnnotationConfigurationApplicationContext(MyConfiguration.class)创建一个Spring 上下文的对象,并使用context.getBean(“bean1”,Bean1.class);返回 Bean1 的实例
@Test
public void test() {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
Bean1 bean1 = context.getBean("bean1", Bean1.class);
System.out.println("bean1:" + bean1);
ApplicationContext context2 = new AnnotationConfigApplicationContext(MyConfiguration2.class);
Bean2 bean2 = context2.getBean("bean2", Bean2.class);
System.out.println("bean2:" + bean2);
}
说明:由以上 Spring 上下文的生成可以知道, 被添加 @Configuration 的类可以被AnnotationConfigurationApplicationContext()所扫描到并添加到Spring中从而创建一个Spring的上下文;
而 @Bean 则类似于 spring.xml 中的 <bean/> 的作用,可以将Bean1 装配到容器中,从而创建一个 Bean1的实例,即 bean1。 而被添加@Bean 注解的方法名就是 类似于<bean/> 中的 id=“bean1”了。
【3】简化springioc的注解操作逻辑
第一步:使用注解: @ComponentScan(value="类的上一级包路径名")
@Configuration
@ComponentScan(value = "com.webcode")
public class MyConfiguration {
}
第二步:使用注解: @Component,标注到要被实例化的类上,如Bean1
@Component
public class Bean1 {
}
另一种简化注解的操作:
在spring.xml配置文件添加如下:
<!--开启包扫描-->
<context:component-scan base-package="....."/>
【4】Bean别名
小结:
2、通过注解注入Bean
通过注解注入Bean 的 技术体系
Spring通过注解注入Bean
【1】通过方法注入Bean
①通过有参构造方法注入
步骤1:创建扫描配置类,并添加注解@Configuration、@ComponentScan(value=“路径”)
@Configuration
@ComponentScan(value="springzhuru")
public class MyConfiguration {
}
步骤2:创建要实例化的Bean,并提供有参的构造方法,并在构造方法上添加注解@Autowired在类上添加@Component。
@Component
public class MyBean {
private AnotherBean anotherBean;
@Autowired
public MyBean(AnotherBean anotherBean) {
super();
this.anotherBean = anotherBean;
}
测试代码:
@Test
public void test(){
ApplicationContext ac=new AnnotationConfigApplicationContext(MyConfiguration.class);
MyBean bean1=ac.getBean("myBean",MyBean.class);
System.out.println(bean1);
}
结果:
②通过set方法注入
步骤1:创建扫描配置类,并添加注解@Configuration、@ComponentScan(value=“路径”)
@Configuration
@ComponentScan(value="springzhuru")
public class MyConfiguration {
}
步骤2:创建要实例化的Bean,并提供set方法,并在set方法上添加注解@Autowired在类上添加@Component。
@Component(value="myBean")
public class MyBean {
private AnotherBean anotherBean;
private AnotherBean anotherBean1;
@Autowired
public MyBean(AnotherBean anotherBean) {
super();
System.out.println("MyBean被创建了");
this.anotherBean = anotherBean;
}
@Autowired
public void setAnotherBean1(AnotherBean anotherBean1) {
this.anotherBean1 = anotherBean1;
}
@Override
public String toString() {
return "MyBean [anotherBean=" + anotherBean + ", anotherBean1=" + anotherBean1 + "]";
}
}
结果:anotherBean和anotherBean1相等,@Component是默认单例模式,同一spring上下文中只会创建一个AnotherBean的对象。
【2】集合类型Bean的注入
①List集合类型Bean的注入
步骤1:创建注入Bean的类(包括创建集合类型的属性,基本类型的作为Bean的属性),并提供set方法,添加@Resource注解。
public class MyBean {
private List<String> stringList;
public List<String> getStringList() {
return stringList;
}
@Resource //@Resource注解首先会根据属性名称注入,其次会根据类型进行注入。
public void setStringList(List<String> stringList) {
this.stringList = stringList;
}
}
步骤2:扫描配置类,提供List<String>类型的实例的方法,并添加@Bean注解,告知spring由spring管理的方法。
@Bean
public List<String> stringList(){
List<String> list=new ArrayList<String>();
list.add("哈哈");
list.add("嘿嘿");
list.add("呵呵");
return list;
}
测试:
@Test
public void test(){
ApplicationContext ac=new AnnotationConfigApplicationContext(MyConfiguration.class);
MyBean myBean=ac.getBean("myBean",MyBean.class);
System.out.println(myBean);
for (String s:myBean.getStringList()) {
System.out.println(s);
}
}
通过注解注入List的第二种方式:在扫描配置类中添加几个返回类型为字符串类型的方法,返回的字符串都会被注入到Bean的集合属性中。
@Configuration
@ComponentScan(value="springzhuru")
public class MyConfiguration {
@Bean
public String string1(){
return "111";
}
@Bean
public String string2(){
return "222";
}
}
测试:
List注入方式:如果一个Bean有一个List类型的属性需要注入,spring会到上下文中(扫描注解类)查找所有该List中定义泛型的所有实例(带有@Bean),然后将所有实例注入到List里面。
@Qualifier("stringList")指定Id,而且在集合属性的set方法上的@Qualifier(“stringList”)指定Id。
拓展:@Order(数值),来控制实例化Bean的顺序,小的先注入。前提:Spring4.2版本以后该注解才起作用,可以通过它实现注入集合中数据的顺序。
②Map注入
步骤1:创建Map类型的集合,并提供set方法
public class MyBean {
private Map<String,Integer> getIntegerMap;
@Resource("map")
public void setGetIntegerMap(Map<String, Integer> getIntegerMap) {
this.getIntegerMap = getIntegerMap;
}
public Map<String, Integer> getGetIntegerMap() {
return getIntegerMap;
}
}
步骤2:扫描配置文件中提供返回map集合的方法。
@Bean("map")
public Map<String,Integer> integerMap(){
Map<String,Integer> map=new HashMap<String,Integer>();
map.put("aaa", 111);
map.put("bbb", 222);
map.put("ccc", 333);
return map;
}
测试:
@Test
public void test(){
ApplicationContext ac=new AnnotationConfigApplicationContext(MyConfiguration.class);
MyBean myBean=ac.getBean("myBean",MyBean.class);
System.out.println(myBean);
for(Entry<String,Integer> entry:myBean.getGetIntegerMap().entrySet()){
System.out.println(entry);
}
}
Map注入的第二种方式:同List相同,创建多个方法返回Integer类型参数。
扫描配置类代码:
@Bean //该情况下key的值为Bean的名
public Integer integerMap1(){
return 444;
}
@Bean
public Integer integerMap2(){
return 555;
}
结果:还可以通过给@Bean(name="名称")来给实例取名。
【4】String、Integer等类型直接赋值:
步骤1:创建简单数据类型的变量,并提供set方法,并在set方法上添加@value(“值”)注解。
public class MyBean {
private String string;
public String getString() {
return string;
}
@Value("2222")
public void setString(String string) {
this.string = string;
}
}
测试:
@Test
public void test(){
ApplicationContext ac=new AnnotationConfigApplicationContext(MyConfiguration.class);
MyBean myBean=ac.getBean("myBean",MyBean.class);
System.out.println(myBean);
}
【5】SpringIoC容器内置接口实例注入:
private ApplicationContext context;
//这种情况下,可以直接使用ApplicationContext方法
public ApplicationContext getContext() {
return context;
}
@Autowired
public void setContext(ApplicationContext context) {
this.context = context;
}
- 可直接将ApplicationContext注入进来,也可以注入BeanFactory、Environment、ResourceLoader、ApplicationEventPublisher、MessageSource及其实现类!