IOC 实验 1、实验一[Debug 查看对象的属性是如何设置进去的] Debug 查看对象的属性是如何设置进去的
结论 1:创建 HappyFactory 对象并设置属性值是发生在 IOC 容器对象创建的过程中,而不是 getBean()的时候。 结论 2:Spring IOC 容器内部使用 BeanFactory 创建 bean 的对象,对外提供 ApplicationContext 给开发者使用。 结论 3:bean 的属性通过 setXxx()方法注入到 bean 对象中 。
2、实验二[重要,根据类型获取 bean] 根据类型获取 bean
1 2 3 4 5 6 7 8 9 @Test public void testGetBeanByType () { HappyFactory factory = iocContainer.getBean(HappyFactory.class); System.out.println("factory = " + factory); }
此时如果同样类型的 bean 在 IOC 容器中不只一个对象,那么会抛出下面异常:
1 org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.atguigu.spring.component.HappyFactory] is defined: expected single matching bean but found 2 : happyFactory,happyFactory2
3、实验三(属性赋值,能看懂就行) 使用构造器给属性赋值。(能看懂就行)。
必须按照顺序赋值,否则使用 index。
上面创建的顺序:
1 2 3 4 5 public class HappyFactory { private String factoryName; private Double registerMoney; private Integer factoryAge;
所以使用构造器给属性赋值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <bean id ="happyFactory3" class ="com.atguigu.spring.component.HappyFactory" > <constructor-arg value ="goodFactory" /> <constructor-arg value ="8877.66" /> <constructor-arg value ="99" /> </bean > <bean id ="happyFactory4" class ="com.atguigu.spring.component.HappyFactory" > <constructor-arg value ="66" index ="2" /> <constructor-arg value ="5544.33" index ="1" /> <constructor-arg value ="helloFactory" index ="0" /> </bean >
一般的赋值方式:
1 2 3 4 5 6 <bean id ="happyFactory" class ="com.atguigu.spring.component.HappyFactory" > <property name ="factoryName" value ="dreamFactory" /> <property name ="factoryAge" value ="20" /> <property name ="registerMoney" value ="500" /> </bean >
4、实验四(属性赋值) 使用 p 名称空间给属性赋值
1 2 3 4 5 6 7 8 <bean id="happyFactory5" class="com.atguigu.spring.component.HappyFactory" p:factoryName="appleFactory" p:factoryAge="33" p:registerMoney="666.66" />
5、实验五[null 值和级联属性] null 值和级联属性
级联属性使用ref 标签,声明级联属性的具体对象的位置。
1 2 public class HappyFactory { private Machine machine;
1 2 3 public class Machine { private String machineName; private Double machinePrice;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <bean id ="happyFactory6" class ="com.atguigu.spring.component.HappyFactory" > <property name ="factoryName" > <null /> </property > <property name ="factoryAge" value ="20" /> <property name ="registerMoney" value ="500" /> <property name ="machine" ref ="machine" /> <property name ="machine.machineName" value ="iceCreamMachine" /> <property name ="machine.machinePrice" value ="50.50" /> </bean > <bean id ="machine" class ="com.atguigu.spring.component.Machine" />
6、实验六[重要,给级联属性赋值,引用另外一个 bean] 给属性赋值的时候,引用另外一个 bean。(这个实验中配置的方式是符合 Spring 使用习惯的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <bean id ="machine2" class ="com.atguigu.spring.component.Machine" > <property name ="machineName" value ="bananaMachine" /> <property name ="machinePrice" value ="555.66" /> </bean > <bean id ="happyFactory7" class ="com.atguigu.spring.component.HappyFactory" > <property name ="factoryName" value ="bananaFactory" /> <property name ="factoryAge" value ="33" /> <property name ="registerMoney" value ="33.44" /> <property name ="machine" ref ="machine2" /> </bean >
7、实验七[比较重要,以内部 bean 的形式给属性赋值] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <bean id ="happyFactory8" class ="com.atguigu.spring.component.HappyFactory" > <property name ="factoryName" value ="bananaFactory" /> <property name ="factoryAge" value ="33" /> <property name ="registerMoney" value ="33.44" /> <property name ="machine" > <bean class ="com.atguigu.spring.component.Machine" > <property name ="machineName" value ="catMachine" /> <property name ="machinePrice" value ="66.33" /> </bean > </property > </bean >
8、实验八[给集合属性赋值,list 标签内部引用外部 bean] 1 2 public class HappyFactory {private List<ProductLine> productLineList;
1 2 public class ProductLine { private String productName;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <bean id ="happyFactory9" class ="com.atguigu.spring.component.HappyFactory" > <property name ="factoryName" value ="bananaFactory" /> <property name ="factoryAge" value ="33" /> <property name ="registerMoney" value ="33.44" /> <property name ="productLineList" > <list > <bean class ="com.atguigu.spring.component.ProductLine" > <property name ="productName" value ="lineOne" /> </bean > <bean class ="com.atguigu.spring.component.ProductLine" > <property name ="productName" value ="lineTwo" /> </bean > <bean class ="com.atguigu.spring.component.ProductLine" > <property name ="productName" value ="lineThree" /> </bean > <ref bean ="productLine" /> </list > </property > </bean > <bean id ="productLine" class ="com.atguigu.spring.component.ProductLine" > <property name ="productName" value ="lineFour" /> </bean >
9、实验九[集合类型的 bean,直接配置集合类型] 1 2 public class HappyFactory {private List<Manager> managerList;
1 2 3 public class Manager { private String managerName; private String managerSalary;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <util:list id ="managerTeamId" > <bean class ="com.atguigu.spring.component.Manager" > <property name ="managerName" value ="tom" /> <property name ="managerSalary" value ="500.500" /> </bean > <bean class ="com.atguigu.spring.component.Manager" > <property name ="managerName" value ="jerry" /> <property name ="managerSalary" value ="600.500" /> </bean > <bean class ="com.atguigu.spring.component.Manager" > <property name ="managerName" value ="bob" /> <property name ="managerSalary" value ="700.500" /> </bean > </util:list > <bean id ="happyFactory10" class ="com.atguigu.spring.component.HappyFactory" > <property name ="managerTeam" ref ="managerTeamId" /> </bean >
10、实验十[比较重要,FactoryBean] 学习这个接口是为了理解 Spring 整合其他第三方框架时采取的策略。
①FactoryBean 接口说明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package org.springframework.beans.factory;public interface FactoryBean <T > { T getObject () throws Exception ; Class<?> getObjectType(); boolean isSingleton () ; }
举例:Spring 整合 Mybatis 时,配置的是 SqlSessionFactoryBean,但是获取到的实际的对象是 SqlSessionFactory。SqlSessionFactoryBean 就实现了 FactoryBean 接口。
所以,FactoryBean 形式适用于整合第三方框架时,去创建复杂的核心对象。
② 创建 FactoryBean 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class HappyFactoryBean implements FactoryBean <HappyFactory > { private String factoryName; public String getFactoryName () { return factoryName; } public void setFactoryName (String factoryName) { this .factoryName = factoryName; } @Override public HappyFactory getObject () throws Exception { HappyFactory happyFactory = new HappyFactory(); happyFactory.setFactoryName(factoryName); happyFactory.setRegisterMoney(555.55 ); happyFactory.setFactoryAge(20 ); happyFactory.setMachine(new Machine("uuuMachine" , 555.22 )); happyFactory.setManagerTeam(Arrays.asList(new Manager("tom" ,55.22 ),new Manager("jerry" ,66.33 ))); return happyFactory; } @Override public Class<?> getObjectType() { return HappyFactory.class; } @Override public boolean isSingleton () { return true ; } }
③ 配置 FactoryBean 1 2 3 4 5 6 7 <bean id ="happyFactory11" class ="com.atguigu.spring.component.HappyFactoryBean" > <property name ="factoryName" value ="haoziweizhi" /> </bean >
11、实验十一[比较重要,测试 bean 的作用域] 测试 bean 的作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <bean id ="managerSingleton" class ="com.atguigu.spring.component.Manager" > <property name ="managerName" value ="justin" /> <property name ="managerSalary" value ="5000.55" /> </bean > <bean id ="managerPrototype" scope ="prototype" class ="com.atguigu.spring.component.Manager" > <property name ="managerName" value ="justin" /> <property name ="managerSalary" value ="5000.55" /> </bean >
junit 测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void testScope () { Object managerSingleton01 = iocContainer.getBean("managerSingleton" ); Object managerSingleton02 = iocContainer.getBean("managerSingleton" ); System.out.println("managerSingleton01.hashCode() = " + managerSingleton01.hashCode()); System.out.println("managerSingleton02.hashCode() = " + managerSingleton02.hashCode()); Object managerPrototype01 = iocContainer.getBean("managerPrototype" ); Object managerPrototype02 = iocContainer.getBean("managerPrototype" ); System.out.println("managerPrototype01.hashCode() = " + managerPrototype01.hashCode()); System.out.println("managerPrototype02.hashCode() = " + managerPrototype02.hashCode()); }
通过 debug 跟踪源码,发现:
scope 属性为 singleton 时:bean 的实例对象在 IOC 容器初始化时创建 scope 属性为 prototype 时:bean 的实例对象在调用 getBean()方法时创建 实用价值:将来在 SpringMVC 中,使用 Controller 类处理请求,此时有个问题——SpringMVC 会不会为每一个请求单独创建一个 Controller 对象?基于默认值配置,Controller 对象也是单实例的。所以不要在处理请求的过程中修改 Controller 的成员变量———-会有线程安全问题。
12、实验十二[应付面试,bean 的后置处理器] ① 创建后置处理器类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization ( // bean是被处理的对象 Object bean, // beanName是被处理对象的bean的id String beanName) throws BeansException { System.out.println("Before bean = " + bean); System.out.println("Before beanName = " + beanName); return bean; } @Override public Object postProcessAfterInitialization (Object bean, String beanName) throws BeansException { System.out.println("After bean = " + bean); System.out.println("After beanName = " + beanName); return bean; } }
② 注册后置处理器类 1 <bean id ="myBeanPostProcessor" class ="com.atguigu.spring.component.MyBeanPostProcessor" />
③ 执行的时机 postProcessBeforeInitialization():在初始化操作前 postProcessAfterInitialization():在初始化操作后 13、实验十三[应付面试,bean 的初始化方法和销毁方法] ① 在 bean 所属的 Java 类中创建初始化方法和销毁方法 1 2 3 4 5 6 7 8 9 public void doInit () { System.out.println("小孩儿刚生下来要洗澡" ); } public void doDestroy () { System.out.println("告别之前留几句话" ); }
② 配置 bean 时指定初始化方法和销毁方法 1 2 3 4 5 6 7 <bean id ="managerInitAndDestroy" init-method ="doInit" destroy-method ="doDestroy" class ="com.atguigu.spring.component.Manager" > </bean >
③junit 测试 1 2 3 4 5 6 @Test public void testBeanLifeCycle () { Object managerInitAndDestroy = iocContainer.getBean("managerInitAndDestroy" ); System.out.println("managerInitAndDestroy = " + managerInitAndDestroy); ((ClassPathXmlApplicationContext)iocContainer).close(); }
打印效果:
1 2 3 4 5 6 7 8 9 10 Manager constructor Before bean = Manager{managerName='null' , managerSalary=null } Before beanName = managerInitAndDestroy 小孩儿刚生下来要洗澡 After bean = Manager{managerName='null' , managerSalary=null } After beanName = managerInitAndDestroy managerInitAndDestroy = Manager{managerName='null' , managerSalary=null } 十二月 02 , 2020 4 :43 :54 下午 org.springframework.context.support.ClassPathXmlApplicationContext doClose 信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@4141d797: startup date [Wed Dec 02 16 :43 :54 CST 2020 ]; root of context hierarchy 告别之前留几句话
④bean 的生命周期 创建 bean 的对象 设置 bean 的属性 调用 bean 后置处理器的 before 方法(如果有的话) 调用 bean 的初始化方法(如果有的话) 调用 bean 后置处理器的 after 方法(如果有的话) bean 可用 调用 bean 的销毁方法(如果有的话) 关闭 IOC 容器 14、实验十四[重要,引用外部属性文件] ① 准备外部属性文件(jdbc.properties) 1 2 3 4 wechat.dev.driver =com.mysql.jdbc.Driver wechat.dev.url =jdbc:mysql://localhost:3306/mybatis0223 wechat.dev.username =root wechat.dev.password =root
② 在 Spring 配置文件(applicationContext.xml)中引入外部属性文件 1 2 3 4 5 <context:property-placeholder location ="classpath:jdbc.properties" />
③ 基于外部属性文件配置数据源 1 2 3 4 5 6 7 <bean id ="dataSource" class ="com.alibaba.druid.pool.DruidDataSource" > <property name ="driverClassName" value ="${wechat.dev.driver}" /> <property name ="url" value ="${wechat.dev.url}" /> <property name ="username" value ="${wechat.dev.username}" /> <property name ="password" value ="${wechat.dev.password}" /> </bean >
④junit 测试 1 2 3 4 5 6 @Test public void testDBConnection () throws SQLException { DataSource dataSource = iocContainer.getBean(DataSource.class); Connection connection = dataSource.getConnection(); System.out.println("connection = " + connection); }
打印结果:connection = com.mysql.jdbc.JDBC4Connection@3745e5c6
15、实验十五[基于 XML 的自动装配] ① 概念
自动装配:在 IOC 容器中,组件 A 需要组件 B;同时组件 B 在 IOC 容器中也正好存在,IOC 容器替我们自动把这两个组件组装好。
组装:
② 实现方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <bean id ="empDao3" class ="com.atguigu.spring.component.EmpDao" /> <bean id ="empDao2" class ="com.atguigu.spring.component.EmpDao" /> <bean id ="empService" class ="com.atguigu.spring.component.EmpService" autowire ="byName" /> </bean >
16、实验十六[重要,通过注解配置 bean] ① 组件相关的注解 @Controller:控制器(注入服务)
@Service:对应业务逻辑层的 Service 类(注入 dao)
@Repository:对应持久化层的 Dao 或 Mapper(实现 dao 访问)
@Component:相当于配置文件中的<bean id=”” class=””/>(实现 bean 的注入)
@Component 泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services 等的时候),我们就可以使用@Component 来标注这个类
从程序运行的角度来说,上面四个注解的作用是一样的,即使标记在了不对应的类上,Spring 在运行过程中也不会报错。但是为了我们自己开发过程中不造成不必要的误解,我们还是要标记在正确的类上。
② 在类上标记注解 1 2 3 @Controller public class EmpController {}
1 2 3 @Service public class EmpService {}
1 2 3 @Repository public class EmpDao {}
但是仅仅把注解标记在类上,还不足以让 IOC 容器创建它们的对象。
初学者的误区:
认为注解对应的功能都是在注解的代码里写的。但其实并没有。
例如:@Controller 注解代码
1 2 3 4 5 6 7 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { String value () default "" ; }
@Controller 注解的代码中其实有效的就是声明了一个 value 属性。
注解本身和它里面可以设置的属性一起告诉框架,我们在标记了注解的这个地方要干嘛。
具体干活还是框架去干。
但是框架怎么知道这有个注解?
答案是:扫描。
③ 配置自动扫描的包 加入 spring-aop-4.0.0.RELEASE.jar 包。
1 <context:component-scan base-package ="com.atguigu.spring.component" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <context:component-scan base-package ="com.atguigu.spring.component" /> <context:component-scan base-package ="com.atguigu.spring.component,com.atguigu.spring.toy" /> <context:component-scan base-package ="com.atguigu.spring.*" resource-pattern ="Stu*.class" /> <context:component-scan base-package ="com.atguigu.spring.*" > <context:exclude-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan > <context:component-scan base-package ="com.atguigu.spring.*" use-default-filters ="false" > <context:include-filter type ="annotation" expression ="org.springframework.stereotype.Controller" /> </context:component-scan >
④junit 测试 1 2 3 4 5 6 7 8 9 10 11 12 private ApplicationContext iocContainer = new ClassPathXmlApplicationContext("applicationContext.xml" );@Test public void testGetAnnotationBean () { EmpController empController = iocContainer.getBean(EmpController.class); EmpService empService = iocContainer.getBean(EmpService.class); EmpDao empDao = iocContainer.getBean(EmpDao.class); System.out.println("empController = " + empController); System.out.println("empService = " + empService); System.out.println("empDao = " + empDao); }
⑤bean 的 id [1]默认情况 使用上面注解将类加入 IOC 容器后,bean 的 id 是类名首字母小写。例如:下面代码中,这个类在 IOC 容器中的 bean 的 id 是 taskServiceSimpleImpl
1 2 3 @Service public class TaskServiceSimpleImpl implements TaskService {}
[2]指定 bean 的 id 1 2 3 @Service(value = "zhutou") public class TaskServiceSimpleImpl implements TaskService {}
或
1 2 3 @Service("zhutou") public class TaskServiceSimpleImpl implements TaskService {}
⑥bean 的范围 和基于 XML 的情况一样,通过注解加入 IOC 容器的 bean,默认是单一实例的。如果使用@Scope(value = “prototype”)设置为多实例对象,那么每次 getBean()会创建新的对象
1 2 3 4 5 6 7 @Scope(value = "prototype") @Controller(value = "fengLaoShiZhenShuai") public class EmpController {}
17、实验十七[通过注解自动装配] ① 使用@Autowired 注解的基本情况[重要] 下面的代码是最基本的用法,但是以后我们用的就是这个最基本的用法。
1 2 3 4 5 6 7 8 9 10 11 @Controller public class EmpController { @Autowired private EmpService empService; public String getMessage () { return empService.getMessage(); } }
1 2 3 4 5 6 7 8 9 10 @Service public class EmpService { @Autowired private EmpDao empDao; public String getMessage () { return empDao.selectMessage(); } }
② 新旧代码对比 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class EmpServlet extends BaseServlet { private EmpService empService = new EmpService(); } ----------------华丽的分割线------------------ @Controller public class EmpController { @Autowired private EmpService empService; }
18、实验十八[自动装配特殊情况] ① 可以加@Autowired 注解的位置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Controller public class EmpController { @Autowired private EmpService empService; @Autowired public EmpController (EmpService empService) { System.out.println("EmpController构造器中:empService = " + empService); } @Autowired public void setEmpService (EmpService empService) { System.out.println("EmpController的set方法中:empService = " + empService); }
②@Autowired 注解设置 required 属性 required 属性可选值:
true:表示当前位置必须注入成功,否则会抛出装配失败的异常 false:不是必须装配,允许装配不成功 1 2 3 4 @Autowired(required = false) private DeptService deptService;
但是实际开发时,但凡是写了@Autowired 注解,那就肯定是需要装配成功的。
③@Autowired 注解查找目标 bean 的顺序 第一步:根据类型查找 第二步:如果根据类型找到的是多个,那么根据变量名或方法名作为 bean 的 id 继续查找 上面两种方式都找不到会抛出异常
④@Qualifier 注解 帮助@Autowired 注解指定目标 bean 的名称
1 2 3 4 @Autowired @Qualifier(value = "zhutou") private TaskService taskServiceSimpleImpl;
19、实验十九[扫描包相关特殊情况] ① 扫描多个包可以使用逗号分开 1 2 <context:component-scan base-package="com.atguigu.spring.component,com.atguigu.spring.toy"/>
② 使用 resource-pattern 属性进行过滤 此时只有符合条件的类会被扫描
1 2 3 4 5 <!-- 配置自动扫描的包 --> <context:component-scan base-package ="com.atguigu.spring.component,com.atguigu.spring.toy" resource-pattern="Shop*.class" />
③ 排除特定类 1 2 3 4 5 6 7 8 9 <context:component-scan base-package ="com.atguigu.spring.component" > <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan >
④ 仅扫描特定类 1 2 3 4 5 6 7 8 9 10 11 12 13 <context:component-scan base-package="com.atguigu.spring.component" use-default-filters="false" > <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan >