二、Spring与IOC
1. 控制反转IOC的概述
控制反转(IOC:Inversion of Control)是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。
IOC是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式有两种:依赖注入和依赖查找。依赖注入的方式应用更加广泛。- 依赖注入【DI:Dependency Injection,DI】:是指在程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
- 依赖查找【DL:Dependency Lookup,DL】:容器提供回调接口和上下文环境给组件,程序代码则需要提供具体的查找方式。比较典型的是依赖与JNDI服务接口(Java Naming and Directory Interface)的查找。 依赖注入是目前最优秀的解耦方式。依赖注入让Spring的bean之间以配置文件的方式组织在一起,而不是以硬编码的方式耦合在一起的。
总结:
IOC控制反转就是将对象的创建全给了Spring容器。 DI依赖注入的前提是有IOC的环境,Spring创建这个类的过程中,Spring将类依赖的属性设置进去。2. Spring的工厂类
Bean组件主要解决:Bean的定义,Bean的创建以及对Bean的解析。
开发者关心Bean的创建,其他由Spring内部帮你完成。 (1)Bean的创建是典型的工厂模式,它的顶级接口是BeanFactory,ApplicationContext工厂:
ApplicationContext用于加载Spring的配置文件,在程序中充当“容器”的角色,其实现类有两个,通过ctrl+t查看。
A:配置文件在类路径下:使用ClassPathXmlApplicationContext实现类进行加载。 B:配置文件在本地目录中或者在项目根路径下:使用FileSystemXmlApplicationContext实现类进行加载。@Test public void classPathXmlTest() { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); IStudentService studentService = (IStudentService) ac.getBean("studentService"); studentService.some(); } @Test public void FileSystemXmlTest() { ApplicationContext ac = new FileSystemXmlApplicationContext("d:/applicationContext.xml"); IStudentService studentService = (IStudentService) ac.getBean("studentService"); studentService.some(); }
BeanFactory工厂:
BeanFactory接口对象也可作为Spring容器出现。BeanFactory接口是ApplicationContext接口的父类。
若要创建BeanFactory容器,需要使用其实现类XmlBeanFactory,该类可以加载Spring配置文件。而Spring配置文件以资源Resource的形式出现在XmlBeanFactory类的构造器参数中。Resource是一个接口,其具体有两个实现类: A:ClassPathResource:指定类路径下的资源文件 B:FileSystemResource:指定项目根路径或者本地磁盘路径下的资源文件。@Test public void BeanFactoryTest() { BeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml")); IStudentService studentService = (IStudentService) factory.getBean("studentService"); studentService.some(); }
BeanFactory和ApplicationContext的区别:
虽然这两个接口容器所要加载的Spring配置文件是同一个文件,但在代码中的这两个容器对象却不是同一个对象,即不是同一个容器:它们对于容器内对象的装配(创建)时机是不同的。
ApplicationContext容器中对象的装配时机:
ApplicationContext容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。以后代码中若要使用到这些对象,只需从内存中直接获取即可,执行效率比较高,但占用内存。
ApplicationContext容器中对象的装配时机:
BeanFactory容器,对容器中对象的装配与加载采用延迟加载策略,即在第一次调用getBean()时,才真正装配该对象。
@Test public void BeanFactoryTest() { // 获取容器:此时容器中的对象还未进行装配 BeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml")); // 执行下面语句时,会对studentService对象进行装配 IStudentService studentService = (IStudentService) factory.getBean("studentService"); studentService.some(); }
3. Bean的装配
Bean的装配,即Bean对象的创建。容器根据代码要求创建Bean对象后再传递给代码的过程,称为Bean的装配。
装配的方式:默认形式、- 默认装配方式 代码通过getBean()方法从容器中获取指定的Bean实例,容器首先会调用Bean类的无参构造器,利用反射,创建空值的实例对象。
- 动态工厂Bean 有些时候,项目中需要通过工厂类来创建Bean实例,而不能像前面例子中似的,直接由Spring容器来装配Bean实例。使用工厂模式创建Bean实例,就会使工厂类与要创建的Bean类耦合到一起。 (1)动态工厂Bean作为普通Bean使用 将动态工厂Bean作为普通Bean来使用是指,在配置文件中注册动态工厂Bean后,测试类直接通过getBean()获取到工厂对象,再由工厂对象调用其相应方法创建相应的目标对象。配置文件中无需注册目标对象的Bean。因为目标对象的创建不由Spring容器来管理。 但这样做的缺点是:不仅工厂类与目标类耦合到一起了,测试类与工厂类也耦合到一起了。
public class ServiceFactory{ public IUserService getUserService(){ return new UserServiceImpl(); }}
@Testpublic void factoryTest(){ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); // 从Spring容器中获取factory ServiceFactory factroy = ac.getBean("factory"); IUserService userService = factory.getUserService(); userService.doSome();}
(2)使用Spring的动态工厂Bean
Spring对于使用动态工厂来创建的Bean,由专门的属性定义。 factory-bean指定相应的工厂Bean,由factory-method指定创建所用方法。此时配置文件中至少会有两个bean的定义:工厂类的bean,与工厂类所要创建的目标类Bean。而测试类中不再需要获取工厂Bean对象了,可以直接获取目标Bean对象,实现测试类与工厂类间的解耦。@Testpublic void springFactoryTest(){ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); IUserService userService = ac.getBean("userService"); userService.doSome();}
(3)静态工厂Bean
使用工厂模式中的静态工厂来创建实例Bean。 此时需要注意:静态工厂无需工厂实例,所以不再需要定义静态工厂。 而对于工厂所要创建的Bean,其实不是由自己的类创建的,所以无需指定自己的类。但其实是由工厂类创建的,所以需要指定所用工厂类,故class属性指定的是工厂类而非自己的类。当然,还需要通过factory-method属性指定工厂方法。public class ServiceFactory{ public static IUserService getUserService(){ return new UserServiceImpl(); }}
4. 容器中Bean的作用域
当通过Spring容器创建一个Bean实例时,不仅可以完成Bean的实例化,还可以通过scope属性,为Bean指定特定的作用域。Spring支持五种作用域。
(1)singleton:单例模式。即在整个Spring容器中,使用Singleton定义的Bean将是单例的,只有一个实例,默认为单例的。 (2)prototype:多例模式。即每次使用getBean()方法获取实例都是一个新的实例。 (3)request:对于每次HTTP请求,都将会产生一个不同的Bean实例。 (4)session:对于每个不同的HTTP session,都将会产生一个不同的Bean实例。 (5)global-session:注意:
1.对于scope的值为request、session、global-session,只有在Web应用是使用Spring时,该作用于才有效。 2.对于scope的值为singleton的单例模式,该bean是在容器被创建时就被装配好了。 3.对于scope的值为prototype的原型模式,Bean实例是在代码中使用该Bean实例时才进行装配的。
5. Bean的后处理器
Bean后处理器是一种特殊的Bean,容器中所有的Bean在初始化时,均会自动执行该类的两个方法。由于该Bean是由其他Bean自动调用执行,不是程序员手工调用,故此Bean无需id属性。
需要做的是,在Bean后处理器类方法中,只要对Bean类与Bean类中的方法进行判断,就可实现对指定的Bean的指定方法进行功能扩展与增强。方法返回地Bean对象,即是增强过的对象。 代码中需要自定义Bean后处理器类。该类是实现了接口BeanPostProcessor的类。该接口中包含两个方法,分别在目标Bean初始化完毕之前与之后执行。它们的返回值为:功能被扩展或增强后的Bean对象。 Bean初始化完毕有一个标志,一个方法将被执行。即当该方法被执行时,表示该Bean被初始化完毕。所以,Bean后处理器中两个方法的执行,是在这个方法之前之后执行。这个方法在后面将会讲到。public Object postProcessBeforeInitialzation(Object bean,String beanId) throws BeansException
该方法会在目标Bean初始化完毕之前由容器自动调用。public Object postProcessAfterInitialzation(Object bean,String beanId) throws BeansException
该方法会在目标Bean初始化完毕之后由容器自动调用。 它们的参数是:第一个参数是系统即将初始化的Bean实例,第二个参数是该Bean实例的id属性值。若Bean没有id就是name属性值。 举例:
程序中有一个业务接口IService,其中两个业务方法some()和other()。有两个Bean:StudentServiceImpl和TeacherServiceImpl(),均实现了IService接口。 要求:对StudentServiceImpl的some()方法进行增强,输出其开始执行时间与执行结束时间。public interface IService{ void some(); void other();}public class StudentServiceImpl implements IService{ @Override public void some(){ System.out.println(this.getClass().getSimpleName()+",执行some()方法"); } @Override public void other(){ System.out.println(this.getClass().getSimpleName()+",执行other()方法"); }}public class MyBeanPostProcessor implements BeanPostProcessor{ @Override public Object postProcessBeforeInitialization(Object bean,String beanName){ System.out.println("执行postProcessBefreInitialization()"); // 即使不对bean进行增强,也要使用方法返回bean,不能为默认的null,否则将抛出空指针异常。 return bean; } @Override public Object postProcessAfterInitialization(final Object bean,String beanName){ if("studentService".equals(beanName)){ Proxu.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(),new InvocationHandler(){ public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{ if("some".equals(method.getName())){ System.out.println("目标方法执行开始时间:"+System.currentTimeMillis()); // 执行目标方法 Object result = method.invoke(bean,args); System.out.println("目标方法执行结束时间:"+System.currentTimeMillis()); return result; } return method.invoke(bean,args); }});return proxy; } return bean; } }
@Testpublic void factoryTest(){ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); // 从Spring容器中获取factory IService studentService= (IService)ac.getBean("studentService"); studentService.some(); studentService.other(); IService teacherService= (IService)ac.getBean("teacherService"); teacherService.some(); teacherService.other();}
6. 定制Bean的生命始末
可以为Bean定制初始化后的生命行为,也可以为Bean定制销毁前的生命行为。
步骤:- 这些方法需要在Bean类中实现定义好:方法名是随意的public void方法。
- 其次在配置文件的标签中添加如下属性
- init-method:指定初始化方法的方法名
- destory-method:指定销毁方法的方法名
注意:如果希望看到Bean的destory-method的执行结果,需要满足两个条件:
(1)Bean为singleton,即单例的。 (2)要确保容器关闭,接口ApplicationContext没有close()方法,但其实现类有。所以,可以将ApplicationContext强转为其实现类对象,或者直接创建的就是实现类对象。@Testpublic void destoryMethodTest(){ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); IUserService userService = (IUserService) ac.getBean("userService"); userService.doSome(); userService.doOther(); // 关闭容器对象 ((ClassPathXmlApplicationContext)ac).close();}
7. Bean的生命周期
Bean实例从创建到最后销毁,需要经过很多过程,执行很多生命周期方法。
- 调用无参构造器,创建实例对象。
- 调用参数的setter,为属性注入值。
- 若Bean实现了BeanNameAware接口,则会执行接口方法setBeanName(String beanId),使bean类可以获取其在容器中的id名称。
- 若Bean实现了BeanFactoryAware接口,则执行接口方法setBeanFactory(BeanFactory factory),使Bean类可以获取到BeanFactory对象。
- 若定义并注册了Bean后处理器BeanPostProcessor,则执行接口方法postProcessBeforeInitialization()。
- 若Bean实现了InitializingBean接口,则执行接口afterPropertiesSet()。该方法在Bean的所有属性的set方法执行完毕后执行,是Bean初始化结束的标志,即Bean实例化结束。
- 若设置了init-method方法,则执行。
- 若定义并注册了Bean后处理器BeanPostProcssor,则执行接口方法postProcessAfterInitialization()。
- 执行业务方法。
- 若Bean实现了DisposableBean接口,则执行接口方法destory()。
- 若设置了destory-method方法,则执行。