请像对待每一行代码一样对待每篇文章 —- 鹰嘴豆
学习目标
SpringMVC的执行过程
在xml中注册serlvet,配置匹配的映射路径,这个selvet是DispatcherServlet类
DispatcherServlet继承HttpServletBean祖父类,里面的init方法进行数据的初始化,例如springmvc容器的初始化
初始化好容器以后,回调OnRefresh方法进行初始化springMVC的默认策略类(配置在DispatcherServlet.properties)
根据request请求,调用Servlet提供的接口service来分发request请求方法到具体的doXXX()方法中
在各自的doXXX方法中调用processRequest处理请求,这个方法处理请求,并发出一个事件,具体委托doService处理
开始学习
加载Properties的代码片段
这个例子是加载spring提供的默认的策略类的properties文件
spring提供了对properties处理的封装,基于spring的框架可以复用这些工具类
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
}
}
spring中对枚举的处理
枚举和类差不多,可以有static{}静态处理,可以有静态方法处理
下面代码可以看到spring对enum干净的处理方式 ```java public enum HttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
private static final Map<String, HttpMethod> mappings = new HashMap<String, HttpMethod>(8);
static {
for (HttpMethod httpMethod : values()) {
mappings.put(httpMethod.name(), httpMethod);
}
}
/**
* Resolve the given method value to an {@code HttpMethod}.
* @param method the method value as a String
* @return the corresponding {@code HttpMethod}, or {@code null} if not found
* @since 4.2.4
*/
public static HttpMethod resolve(String method) {
return (method != null ? mappings.get(method) : null);
}
/**
* Determine whether this {@code HttpMethod} matches the given
* method value.
* @param method the method value as a String
* @return {@code true} if it matches, {@code false} otherwise
* @since 4.2.4
*/
public boolean matches(String method) {
return (this == resolve(method));
}
}
3. 获取spring的上下文容器
1. 使用WebApplicationContextUtils工具类从servlet上下文中获取根上下文,获取子上下文,
```java
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
// 使用下面的key存放在servlet上下文中
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
spring初始化handlerMapping
获取父容器、子容器中指定类型的bean
- 容器有一个api为getBeansOfType,通过这个api获取指定类型的bean,这个api的原理是什么呢?
spring的循环依赖发现 - depend-on
- 每个bean C都有一个depend-on的集合A存储依赖的beans,还有一个集合B是存储depend-on=”target” 这个target bean 作为key, target所在的bean的集合作为value。循环遍历集合A,在集合B中以bean C为key查找出集合,在集合中找到这个遍历的集合A的值是否存在,如果存在就表示循环依赖,dependenciesForBeanMap这个map的key存放的是depend-on中的bean名字,value是一个集合,存放的是depend-on所在的bean。
递归查找方法重写的个数,从接口,超类,当前类中去查找
public static int getMethodCountForName(Class<?> clazz, String methodName) { Assert.notNull(clazz, "Class must not be null"); Assert.notNull(methodName, "Method name must not be null"); int count = 0; Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method method : declaredMethods) { if (methodName.equals(method.getName())) { count++; } } Class<?>[] ifcs = clazz.getInterfaces(); for (Class<?> ifc : ifcs) { count += getMethodCountForName(ifc, methodName); } if (clazz.getSuperclass() != null) { count += getMethodCountForName(clazz.getSuperclass(), methodName); } return count; }
spring在实例化bean的时候会先执行BeanPostProcessors,使用它去得到一个bean对象代替直接实例化目标bean的class,作为创建的bean实例
先是用工厂方法初始化实例,如果没有工厂方法则使用默认的构造函数初始化实例
判断对象是否是cglib代理,只要获取对象的className,判断name是否包含$$符号就可以
cglib会自己生产一个带$$名字的对象,它继承了指定代理的class,只需要通过getSuperClass就可以获取到这个被代理的class
两个class比较直接用==即可
public static void main(String[] args) { HelloOne helloOne = new HelloOne(); Class<?> helloOneClass = helloOne.getClass(); Method[] methods = helloOneClass.getMethods(); for(Method method : methods) { // 判断方法所在的class是否是HelloOne if (method.getDeclaringClass() == HelloOne.class) { System.out.println("你很牛逼"); } } System.out.println(); }
获取基本类型的class
- java提供getPrimitiveClass获取基本类型的Class,例如Double.TYPE获取到Double的Class类型
TYPE = (Class<Double>) Class.getPrimitiveClass("double")
- java提供getPrimitiveClass获取基本类型的Class,例如Double.TYPE获取到Double的Class类型
- 判断方法的返回值是否是Void
method.getReturnType() != Void.TYPE
返回基本类型的包装类型
public static Class<?> getBoxedClass(Class<?> c) { if (c == Integer.TYPE) { c = Integer.class; } else if (c == Boolean.TYPE) { c = Boolean.class; } else if (c == Long.TYPE) { c = Long.class; } else if (c == Float.TYPE) { c = Float.class; } else if (c == Double.TYPE) { c = Double.class; } else if (c == Character.TYPE) { c = Character.class; } else if (c == Byte.TYPE) { c = Byte.class; } else if (c == Short.TYPE) { c = Short.class; } return c; }
Integer.type和Integer.class什么区别?
- 判断方法修饰符是不是public和static
Modifier.isPublic(method.getModifiers()) && !Modifier.isStatic(method.getModifiers())
- 根据属性拼接setter方法并执行
String setter = "set" + property.substring(0, 1).toUpperCase() + property.substring(1); Object value = method.invoke(annotation);
spring通过工厂方法、构造函数,普通bean反射初始化来完成对象的创建,在spring中分别存在下面的方法进行初始化
AbstractAutowireCapableBeanFactory.autowireConstructor AbstractAutowireCapableBeanFactory.instantiateBean AbstractAutowireCapableBeanFactory.autowireConstructor
- getBeanPostProcessors,在对象初始化后,会使用beanPostProcessor来进行属性注解的注入,比如
AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition
Application Context实现的规范
传值的方式
通过构造函数传参,这需要构造一个对象
- 这个构造对象中提供了一些功能函数
通过方法调用传参
全局变量,比如静态变量、内部类公用外部类属性,当前线程的对象复用
isAssignableFrom 和 instanceof
A.class.isAssignableFrom(B). 表示A是父类或者接口, B是子类或者实现接口,B必须是Class<?>
A instanceof B A是B的子类或者A是B的实现, B是类名,A是具体实例
Assert 抛出的是IllegalArgumentException异常,可以用来校验参数
public static void notNull(@Nullable Object object, String message) { if (object == null) { throw new IllegalArgumentException(message); } }
- 将对象转换成指定的类型
BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass))
- 集合转数组,数组转集合
public static String[] toStringArray(Collection<String> collection) { if (collection == null) { return null; } // 集合转数组 return collection.toArray(new String[collection.size()]); } // 数组转集合 Arrays.asList(nameArr)
- StringTokenizer的使用,用String.split代替它
public static void main(String[] args) { StringTokenizer stringTokenizer = new StringTokenizer("12,3;45,6;7", ",;"); while (stringTokenizer.hasMoreElements()) { System.out.println(stringTokenizer.nextToken()); } } //输出: 12 3 45 6 7
子类转成父类再转成子类
子类转成父类不需要强转,父类转成子类需要强转
子类转成父类以后,再将父类转成子类,不会丢失原先子类的其他方法,可以看成是同一个内存空间,父子在方法区域做了不可见的操作
public static void main(String[] args) { HelloOne helloOne = new HelloOne(); // 子类HelloOne转父类NiuBi NiuBi niuBi = helloOne; // 子类HelloOne转接口Hello类型 Hello hello = helloOne; // good是NiuBi才有的方法,这里需要强转回到HelloOne ((HelloOne) hello).good(); // 自由Hello才有testHello,这里需要强转成HelloOne这个实现类 String show = ((HelloOne) niuBi).testHello(); System.out.println(show); }
对象间的值传递
构造函数传递,需要构造一个对象,这个对象提供所需要的功能,在构造时传递外部参数,这些参数可能也是一些功能,在构造函数执行时可以初始化一些数据
方法形参传递,可以不用构造一个对象,也就是说可以是一个静态的方法,传递给形参的参数可以是外部赋予的一个功能,通过这个外部的功能对象就能完成需要的功能
很多的功能类就像是机器,只要上油就能功能,在面相对象中,传参就是上油,参数可以包装(用另外的类包装,包装的目的是构建一个新的更细的功能类,继续上油运行)
类成员属性可以在定义的时候就初始化成默认的功能类
在执行某个功能方法时对组合的功能(成员属性)赋值,所附的值可以在方法中创建
接口/继承/组合
接口,作用是制定一种规范,这种规范在实现类中实现,从接口中可以了解到这个类的功能
继承,复用功能,层次结构深,一些设计模式实现的必要前提(模板方法)
组合,setter/构造函数等方式传递, 也可以通过具体的方法的形参依赖来完成功能的服用
获取系统环境以及属性
public static void main(String[] args) { System.out.println(System.getenv()); System.out.println(System.getProperties()); }
接口与类继承与实现
接口可以extends接口,extends多个接口用逗号隔开
类implements 接口,可以实现多个接口,用逗号隔开
类A实现了接口A, 接口B继承了接口A, 那么类B继承A再实现接口B,这时候类B不需要再实现接口A的方法,因为已经继承了类A,只需要实现接口B即可 ```java /**
- 类功能描述
- HelloOne实现了Hello接口,所以NiuBi就不需要在实现Hello接口中的方法 *
- @author 鹰嘴豆
- @date 2019/2/14
- 时间 作者 版本 描述
- ==================================================== */ public class NiuBi extends HelloOne implements HelloExt, Hello{
@Override
public void showTimer() {
}
@Override
public void printShow() {
}
}
4. 如果所实现的多个接口中包含有重复的接口方法,那么只需要实现一个接口即可
31. 深层次的接口与类关系
1. 如下类图,接口继承接口,每个接口都是某个功能的规范,不同层次结构上的类实现了不同层次的接口,只要实现了这些特定功能的规范,就说明这个层次结构上的类具有这个功能特性,被子类继承以后就会继承这个功能特性<br />
2. 重复接口方法只要实现一遍即可,上面的类图很难避免同一个接口规范被重复继承,不过没关系,无论你是继承父类还是自己实现,只要接口规范实现过一次即可
32. ApplicationContext和BeanFactory的区别
1. 看下类图的区别,ApplicationContext继承ListableBeanFactory和HierarchicalBeanFactory,也就是它有这两个接口的功能特性(容器功能, 父子容器?)<br />
2. BeanFactory继承,有注册功能,自动写入功能,但也有容器,父子容器的功能<br />
3. ApplicationContext组合了BeanFactory功能(ApplicationContext有个属性是BeanFactory类型),也就是说ApplicationContext是基于BeanFactory进一步实现的功能类,复用BeanFactory功能,适当实现个性化的功能,或者优化BeanFactory中的功能,青春于蓝胜于蓝,推动BeanFactory的创建
33. 将继承层次深的对象分解功能
1. 继承层次深的对象,实现了多个接口,继承了多个类,这时候可以将这个对象分解成不同功能的类,装配到不同粒度的对象里面,完成功能的实现
2. 对象间的通信,某个类需要完成任务时,需要借助另外一个类的功能,这时候就可以考虑,如何进行通信,有几种方式,继承、接口实现、组合、方法间传递, 指定默认功能类
34. spring的注册接口中包含了大量的集合缓存<br />
35. Spring的refresh的执行过程
1. prepareRefresh,在开始refresh的时候记录刷新的时间、刷新的活动标志及初始化数据,如servlet的上下文,参数等
2. obtainFreshBeanFactory,创建BeanFactory并将xml中的元数据信息缓存注册,beanFactory用用listableBeanFactory,Register等接口功能,实现了容器,注册等功能特性
3. postProcessBeanFactory, 准备post-processor 后置处理器,处理serlvet的servletContext和servletConfig的参数,并注册一些作用域功能
4. invokeBeanFactoryPostProcessors,执行所有ApplicationContext上下文中的BeanFactoryPostProcessor的后置处理类。BeanFactoryPostProcessor的子类BeanDefinitionRegistryPostProcessor注册ApplicationContext上下文中的带注解@Configuration,或者其他Lite如@Import @ComponetScan,@Bean等的配置类里面定义的元数据
5. registerBeanPostProcessors,注册所有的BeanPostProcessors对象,缓存在BeanFactory的集合中
6. initMessageSource,注册DelegatingMessageSource到BeanFactory的单例对象缓存集合中
7. initApplicationEventMulticaster,注册SimpleApplicationEventMulticaster到BeanFactory的单例缓存集合(singletonObjects)中
8. onRefresh,给上下文初始化其他特殊的bean,先自动查找ApplicationContext是否存在themeSource的bean,如果找不到默认初始化ResourceBundleThemeSource
9. registerListeners,注册ApplicationListener, 先注册静态的指定的监听器,然后在ApplicationContext上下文中查找所有ApplicationListener类型的监听器,并注册,使用这些监听器广播一些早先存储的事件
10.
36. 集合在遍历时别的地方做修改
1. java集合会在Iterator迭代的时候实现遍历时是否修改的检查,所以在forEach迭代时做集合的更改就会爆异常,下面采用副本拷贝就可以解决
1. ```java
public static void main(String[] args) {
// 构建集合
List<String> names = new ArrayList<>();
names.add("yingzuidou");
names.add("designer");
// 在遍历是增加元素,使用forEach打印改变集合之前的所有元素,所以forEach比for(,,)安全
for (String name : names) {
System.out.println(name);
List<String> updates = new ArrayList<>(names.size() + 1);
updates.addAll(names);
updates.add("good");
names = updates;
}
System.out.println(names);
}
BeanFactoryPostProcessor和BeanPostProcessor两个接口的区别
BeanPostProcessor,对新的bean实例做自定义修改,ApplicationContext自动发现BeanPostProcessor的bean,并在随后的任何bean创建时应用这个BeanPostProcessor功能,做实例自定义修改。例如在某种类型对象创建时给对象组合其他的功能对象,spring定义了一些BeanPostProcessor处理器对指定类型对象做进一步的处理
BeanFactoryPostProcessor自定义修改Bean元数据的属性定义,适配Bean元数据属性,ApplicationContext会自动发现所有的BeanFactoryPostProcessor,并在bean创建之前去更改bean的属性
org.springframework.asm.ClassReader读取class的字节码类
spring的元数据基于java配置
- 有lite配置和full配置两种,full配置表示类使用@Configuration标记,lite配置是使用以下的注解,或者在类中使用@Bean
static { candidateIndicators.add(Component.class.getName()); candidateIndicators.add(ComponentScan.class.getName()); candidateIndicators.add(Import.class.getName()); candidateIndicators.add(ImportResource.class.getName()); }
- 有lite配置和full配置两种,full配置表示类使用@Configuration标记,lite配置是使用以下的注解,或者在类中使用@Bean
学习总结
线程安全的处理方法
将内容放到Request属性中
将内容方法ThreadLocal中
技巧总结
缓存,一般使用集合进行缓存
反射进行一些操作
对spring封装的一些理解
类理解成功能类,更具体可以理解成加工厂
将原生未处理的猪切成不同粒度的部分,将这些原生的部分包装到指定的功能类中,这些功能类提供不同的加工方法对原生的猪肉加工
包装的方法可以通过构造函数传递猪肉,也可以通过setter进行传递
贡献者列表
编辑人 | 编辑时间 | 编辑内容 |
---|---|---|
鹰嘴豆 | 2019/1/31 | 初稿 |