《精通Spring 4.X企业应用开发实战》读书笔记1-1(IoC容器和Bean)
很长一段时间关注在Java Web开发的方向上,提及到Jave Web开发就绕不开Spring全家桶系列,使用面向百度,谷歌的编程方法能够完成大部分的工作。但是这种不系统的了解总觉得自己的知识有所欠缺。所以有了系统了解Spring的想法,了解了Spring,才能够更好的学习Spring全家桶系列,Spring的书籍也是琳琅满目,当然也可以阅读Spring官方的reference,相信那个才是最好的材料,但是鉴于英语的阅读速度有限。所以就挑选了这本《精通Spring 4.X企业应用开发实战》。这里记录一下读书笔记,主要是方便以后快速的查阅~
之前零碎的信息了解到Spring的两大基础其实就是 IoC和AOP了,最近花了很久的时间阅读了《精通Spring 4.X企业应用开发实战》关于 DI 的这部分内容。还是了解了不少内容,当然也产生了不少的疑问。
IoC容器
IoC(Inversion of Control,控制反转),另外一个词DI(Dependency Injection,依赖注入),在Spring中可以把这两个词等价起来,纠结这些概念我觉得没有必要,我的理解,它就是一种成熟的软件设计模式,能够实现软件开发的高内聚,低耦合。系统在改动,扩展起来能够轻松应对。网络上面很多关于这个概念的解释,他们也都举例说明了它的好处,但是都是很小的实例,不是一个复杂的系统对于减轻应对改动、扩展的功效印象并不深刻。所以还是应该潜心码代码,当自己码的代码到达一定的规模,自然而然应该就会了解到IoC的概念,设计模式们的优雅之处了。
用书中的一句话来描述,某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定(一头雾水)。简单点说由Spring容器集中管理实现类,需要该实现类的时候由Spring容器根据名称或类型动态的注入该实现类。
从注入方法上区分,IoC主要包含构造函数注入、属性注入和接口注入;Spring支持构造函数注入和属性注入。Spring容器通过xml配置文件、注解描述、JavaConfig、Groovy DSL四种方式对实现类的信息及其它们之间的依赖关系进行描述,目前比较常用的应该是基于注解,然后配合JavaConfig的方式(Spring boot在我看来是一个anti-xml的产品,最近它比较流行,xml配置文件的方式在spring boot中有点显得格格不入),不过《精通Spring 4.X企业应用开发实战》这本书不太好的地方就在于,书中还是保留了大量的基于xml配置文件的介绍,不过也是了解一下历史吧(Spring因为一直向下保持兼容,导致即使Spring boot现在流行起来,网上还是大量的关于Spring xml配置的介绍,在推广注解、Java Config的方向上还是阻力重重。这也是之前看到相对于Guice这种后起的IoC框架,Spring的不足之处)。
说到这里其实我之前面试的时候被问到一个问题:Spring有什么优缺点,我们看Spring的书,满满的都是Spring的优点,轻量级的框架,当初就是因为EJB太笨重才有的Spring;依赖注入帮助我们设计出低耦合的程序;AOP特性使得我们写出异常简洁的代码,java web程序几乎离不开这个框架的身影,现在Spring已然是全家桶的解决方案,原有的SSH框架到现在的SSM轻量级框架,其中Spring,SpringMVC都属于Spring社区的,Spring的优点太多了,我也只是依照我个人理解简述了一下。缺点呢?前段时间看到一篇对比Spring和Guice的,角度挺好的,Spring虽然随着发展,拥抱了注解、java配置的形式对bean进行描述,但是依然保留了xml形式的bean定义,导致现在网上搜索资料很多都是xml形式的解答,这样使得社区变化的比较慢。但是依然推荐使用Spring,因为相对于Guice,Spring有一个更加优秀的社区,你遇到问题能够搜索到很多的Spring解答,社区也活跃。如果你使用Guice,遇到了问题社区相对于Spring活跃度就大打折扣了。
Java反射(这个知识本身很基础,据说效率不高,但是帮助你理解Spring框架,xml的bean声明都是反射生成的吧?)
Java中有一个特殊的类,Class,每个Class内部都有一个Class类,是不是很拗口。但是确实是这样,这是一个特殊的类,它包含类的所有信息;所有的构造函数,所有的Field,所有的Method...可以动态的创建对象;用代码展示比较合适(很容易理解,就是异常声明的有点多)。
package com.test.spider; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class JavaRefection { public static class MockClass { private String firstName; private String secondName; public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } @Override public String toString() { return "MockClass [firstName=" + firstName + ", secondName=" + secondName + "]"; } } public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { Class c1 = MockClass.class; Class c2 = Class.forName("com.test.spider.JavaRefection$MockClass"); Class c3 = new MockClass().getClass(); //check that 3 methods obtain same class instance System.out.println("Whether T.class is same with Class.forName():" + String.valueOf(c1 == c2) + "\n" + "Whether Class.forName is same with object.getClass():" + String.valueOf(c2 == c3)); //using nullary constructor MockClass mockClass1 = (MockClass)c1.newInstance(); //another method, first get Constructor Constructor<MockClass> constructor = c1.getConstructor(new Class[]{}); MockClass mockClass2 = (MockClass)constructor.newInstance(new Object[]{}); //get Method Method[] methods = c1.getDeclaredMethods(); for(Method method : methods) { System.out.println(method.getName()); } //get Field Field[] fields = c1.getDeclaredFields(); for(Field field : fields) { System.out.println(field.getName()); } fields[0].setAccessible(true); fields[0].set(mockClass2, "Hello"); fields[1].setAccessible(true); fields[1].set(mockClass2, "World!"); System.out.println(mockClass2.toString()); //invoke method System.out.println(methods[1].invoke(mockClass2, new Object[]{})); System.out.println(methods[1].invoke(mockClass1, new Object[]{})); } }
用书中正式的话描述:每个类在JVM中都拥有一个对应的java.lang.Class对象,提供类结构信息的描述。数组、枚举、注解及基本的Java类型(int,double等),甚至void都拥有对应的Class对象。Class没有public的构造方法。Class对象是在装载类时由JVM通过调用类装载器中的defineClass()方法自动构造的。
//for classLoader //全盘负责委托机制(委托机制防止自定义的ClassLoader恶意加载基础类) ClassLoader loader = c1.getClassLoader(); System.out.println("currentLoad:"+ loader); System.out.println("parent:" + loader.getParent()); System.out.println("grandParent:" + loader.getParent().getParent()); System.out.println(loader.getResource("java/net/URL.class")); System.out.println(loader.getResource("java/lang/String.class"));
输出:
currentLoad:sun.misc.Launcher$AppClassLoader@73d16e93 parent:sun.misc.Launcher$ExtClassLoader@6d06d69c grandParent:null jar:file:/G:/Develop/JDK1.8/jre/lib/rt.jar!/java/net/URL.class jar:file:/G:/Develop/JDK1.8/jre/lib/rt.jar!/java/lang/String.class jar:file:/G:/Develop/JDK1.8/jre/lib/rt.jar!/java/net/URL.class
ClassLoader,(1)装载:查找和导入class文件(2)链接:执行校验(检查class文件正确性)、准备(静态变量分配存储空间)和解析步骤(符号引用转换成直接饮用),其中解析步骤是可选的;(3)初始化:对类的静态变量,静态代码块执行初始化工作;
几个ClassLoader的关系,以及Class、Object、ClassLoader的关系:
介绍反射的概念还是因为Spring中不少地方应该都用到了反射的知识,我个人理解,xml中声明bean的时候需要指明class属性,其中需要使用全类名,此时我相信Spring创建这个bean对象的时候使用的就是反射的技术。首先使用Class.forName("")获取到其类型,然后newInstance()或者获取到构造函数,使用有参数的构造函数;还有一个能想到的使用反射的地方。目前注入bean的时候可以在类的private属性上面添加@Autowired注释注入bean,即使没有相关的set方法,这里一定使用了反射技术,不然private类型是无法被外部的对象访问的,反射可以动态调整属性的访问类别,然后直接设置属性;应该还有很多地方。
IoC容器
话题继续切回IoC容器,Spring中的IoC容器,BeanFactory、ApplicationContext,ApplicationContext相比BeanFactory有更强大的功能,一般直接使用ApplicationContext;
BeanFactory
最初接触Spring的时候,不太能理解Bean是什么概念,有时候又看到JavaBean,最初JavaBean只是一个可复用的Java类,有一定的条件,比如无参构造函数、setter,getter获取属性以及可序列化,然后发展为后来的EJB(企业级JavaBean),为了简化企业级开发,就有了Spring框架,Spring里面的Bean就比较宽泛了,能被Spring容器实例化的Java类都可以成为Bean,所以几乎所有的类都可以是Bean。当然也可以不纠结这些概念。
XmlBeanDefinitionReader和DefaultListableBeanFactory,继承图如上图。(Idea真的挺好用的,Ctrl+N可以搜索到想要的类,然后生成类图,当然也可以看代码),通过Idea查看接口、类拥有什么方法最方便了,反正可以看出从BeanFactory,ListableBeanFactory,HierarchicalBeanFactory,ConfigurableBeanFactory(类装载器,属性编辑器,容器初始化后置处理器等),AutowireCapableBeanFactory功能逐渐增加的;
<?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="car" class="com.smart.beanfactory.Car"> <property name="brand" value="redCA72"></property> <property name="color" value="black"></property> <property name="maxSpeed" value="200"></property> </bean> </beans>
这里不得不说Idea的xml代码提示也挺好用的。
public class BeanFactoryTest { @Test public void getBean() throws Exception { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource resource = resolver.getResource("classpath:beans.xml"); System.out.println(resource.getURL()); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(factory); xmlBeanDefinitionReader.loadBeanDefinitions(resource); Car car = (Car)factory.getBean("car"); System.out.println(car.toString()); } }
ApplicationContext
以上是最基本的ApplicationContext接口的类图
Lifecycle这个接口好像tomcat的设计中也有类似的设计,tomcat的Lifecyle负责容器的生命周期?
主要使用的4个类
ClassPathXmlApplicationContext,构造参数中的路径classpath:... FileSystemXmlApplicationContext,构造参数中的路径file:... AnnotationConfigApplicationContext,基于@Configuration注解bean以及@Component方式声明Bean支持 GenericGroovyApplicationContext,基于Groovy声明Bean的方式
第三种目前使用的最多,SpringBoot中推崇的方式,Xml的方式遗留系统中使用较多;
WebApplicationContext
WebApplicationContext的初始化方式不同于BeanFactory、ApplicationContext,因为其需要ServletContext实例。web.xml中配置自启动的Servlet或自定义Web容器监听器(ServletContextListener),借助而这种任何一个,完成启动Spring Web应用上下文的工作。
ContextLoaderServlet和ContextLoaderListener分别是Spring提供的用于启动WebApplicationContext的Servlet和Web容器的监听器。
两者的内部都实现了启动WebApplicationContext实例的逻辑,根据Web容器具体情况进行选择,web.xml中进行配置即可。
通过Web容器监听器引导
... <!-- 指定配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/smart-dao.xml, /WEB-INF/smart-service.xml </param-value> </context-param> <!-- 声明web容器监听器 --> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> ...
不支持监听器的低版本web容器,采用自启动的Servlet;
... <!-- 指定配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/smart-dao.xml, /WEB-INF/smart-service.xml </param-value> </context-param> <!-- 声明自启动的servlet --> <servlet> <servlet-name>springContextLoaderServlet</servlet-name> <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> ...
Bean的生命周期
Bean的完整生命周期从Spring容器着手实例化Bean开始,知道最终销毁Bean,其中经过了许多关键点,每个关键点都设计特定的方法调用,方法主要分为4个种类:
1. Bean自身的方法:如调用Bean构造函数实例化Bean、调用Setter设置Bean的属性以及通过Bean的init-method和destroy-method所指定的方法
2.Bean级生命周期接口方法:如BeanAware、BeanFactoryAware、InitializingBean和DisposableBean,这些接口方法由Bean类实现
3.容器级别生命周期接口方法:InstantiationAwareBeanPostProcessor和BeanPostProcessor这两个接口实现,一般称它们的实现类为“后处理器”。后处理器接口一般不由Bean本身实现,它们独立于Bean,实现类以容器附加装置的形式注册到Spring容器中,并通过接口反射未Spring容器扫描识别。当Spring容器创建任何Bean的时候,这些后处理器就会发生作用,所以这些后处理器的影响是全局性的。
4.工厂后处理器接口方法:包括AspectJWeavingEnabler、CustomAutowireConfigurer、ConfigurationClassPostProcessor等方法。工厂后处理器也是容器级的,在应用上下文装配配置文件后立即调用。
Bean级生命周期和容器级生命周期接口是个性和共性的结合,Bean级别解决了个性化处理问题,容器级解决了某些Bean共性化处理的问题,Spring容器中可以注册多个后处理器,他们同时实现org.springframework.core.Ordered接口即可。
BeanFactory
public class BeanFactoryTest { @Test public void getBean() throws Exception { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource resource = resolver.getResource("classpath:beans.xml"); System.out.println(resource.getURL()); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(factory); xmlBeanDefinitionReader.loadBeanDefinitions(resource); factory.addBeanPostProcessor(new BeanPostProcessor1()); factory.addBeanPostProcessor(new BeanPostProcessor2()); factory.addBeanPostProcessor(new MyInstantiationAwareBeanPostProcessor1()); factory.addBeanPostProcessor(new MyInstantiationAwareBeanPostProcessor2()); Car car = (Car)factory.getBean("car"); System.out.println(car.toString());
factory.destorySingletons(); } } public class Car implements BeanFactoryAware, BeanNameAware, InitializingBean, DisposableBean{ private String brand; private String color; private String maxSpeed; public String getBrand() { return brand; } public String getColor() { return color; } public String getMaxSpeed() { return maxSpeed; } public void setBrand(String brand) { this.brand = brand; } public void setColor(String color) { this.color = color; } public void setMaxSpeed(String maxSpeed) { this.maxSpeed = maxSpeed; } @Override public String toString() { return "Car{" + "brand='" + brand + '\'' + ", color='" + color + '\'' + ", maxSpeed='" + maxSpeed + '\'' + '}'; } private BeanFactory beanFactory; private String beanName; public void selfInit() { System.out.println("init-method指定的方法的调用。"); } public void selfDestory() { System.out.println("destory-method指定的方法的调用。"); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("调用BeanFactoryAware接口的setBeanFactory方法。"); this.beanFactory = beanFactory; } @Override public void setBeanName(String s) { System.out.println("调用BeanNameAware接口的setBeanName方法。参数:" + s); this.beanName = s; } @Override public void destroy() throws Exception { System.out.println("调用DisposableBean接口的destory方法。"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("调用InitializingBean接口的afterPropertiesSet方法。"); } } public class BeanPostProcessor1 implements BeanPostProcessor, Ordered { @Override public Object postProcessBeforeInitialization(Object o, String s) throws BeansException { System.out.println("调用BeanPostProcessor0接口的postProcessBeforeInitialization方法:" + s); return o; } @Override public Object postProcessAfterInitialization(Object o, String s) throws BeansException { System.out.println("调用BeanPostProcessor0接口的postProcessAfterInitialization方法:" + s); return o; } @Override public int getOrder() { return 0; } } public class BeanPostProcessor2 implements BeanPostProcessor, Ordered { @Override public Object postProcessBeforeInitialization(Object o, String s) throws BeansException { System.out.println("调用BeanPostProcessor1接口的postProcessBeforeInitialization方法:" + s); return o; } @Override public Object postProcessAfterInitialization(Object o, String s) throws BeansException { System.out.println("调用BeanPostProcessor1接口的postProcessAfterInitialization方法:" + s); return o; } @Override public int getOrder() { return 1; } } public class MyInstantiationAwareBeanPostProcessor1 extends InstantiationAwareBeanPostProcessorAdapter implements Ordered { @Override public int getOrder() { return 0; } public Object postProcessBeforeInstantiation(Class<?> var1, String var2) throws BeansException { System.out.println("调用InstantiationAwareBeanPostProcessor0接口的postProcessBeforeInstantiation方法:" + var2); return null; } public boolean postProcessAfterInstantiation(Object var1, String var2) throws BeansException { System.out.println("调用InstantiationAwareBeanPostProcessor0接口的postProcessAfterInstantiation方法:" + var2); return true; } public PropertyValues postProcessPropertyValues(PropertyValues var1, PropertyDescriptor[] var2, Object var3, String var4) throws BeansException { System.out.println("调用InstantiationAwareBeanPostProcessor0接口的postProcessPropertyValues方法:" + var4); return var1; } } public class MyInstantiationAwareBeanPostProcessor2 extends InstantiationAwareBeanPostProcessorAdapter implements Ordered { @Override public int getOrder() { return 1; } public Object postProcessBeforeInstantiation(Class<?> var1, String var2) throws BeansException { System.out.println("调用InstantiationAwareBeanPostProcessor1接口的postProcessBeforeInstantiation方法:" + var2); return null; } public boolean postProcessAfterInstantiation(Object var1, String var2) throws BeansException { System.out.println("调用InstantiationAwareBeanPostProcessor1接口的postProcessAfterInstantiation方法:" + var2); return true; } public PropertyValues postProcessPropertyValues(PropertyValues var1, PropertyDescriptor[] var2, Object var3, String var4) throws BeansException { System.out.println("调用InstantiationAwareBeanPostProcessor1接口的postProcessPropertyValues方法:" + var4); return var1; } }
Spring提倡的是非侵入式编程,以上其实框架代码和业务代码融合了;所以在应用编程的时候,尽量少用4个Bean生命周期接口类,使用init-method,destory-method,或者支持@PostConstruct,@PreDestory的InitDestoryAnnotationBeanPostProcessor(ApplicationContext默认装载)这些不入侵代码的编程方式。
ApplicationContext
其与BeanFactory中的bean的生命周期一点不同在于生命周期中多了一些接口调用;
另外一点最大的不同时ApplicationContext利用Java反射机制自动识别出配置文件中定义的BeanPostProcessor、InstantiationAwareBeanPostProcessor和BeanFactoryPostProcessor,并且自动将他们注册到ApplicationContext中;BeanFactory的代码中展示需要手动调用addBeanPostProcessor()等方法注册;
所以应用中使用ApplicationContext更加方便;