很长一段时间关注在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 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功能逐渐增加的;
这里不得不说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容器监听器引导
...
contextConfigLocation
/WEB-INF/smart-dao.xml, /WEB-INF/smart-service.xml
org.springframework.web.context.ContextLoaderListener
...
不支持监听器的低版本web容器,采用自启动的Servlet;
...
contextConfigLocation
/WEB-INF/smart-dao.xml, /WEB-INF/smart-service.xml
springContextLoaderServlet
org.springframework.web.context.ContextLoaderServlet
1
...
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更加方便;