当前位置: 首页 > news >正文

适合大学生浏览的网站wordpress 网址

适合大学生浏览的网站,wordpress 网址,网站开发人员的职业要求,做律师咨询网站虽然说 Spring 容器上手简单#xff0c;可以仅仅通过学习一些有限的注解#xff0c;即可达到快速使用的目的。但在工程实践中#xff0c;我们依然会从中发现一些常见的错误。尤其当你对 Spring 的生命周期还没有深入了解时#xff0c;类初始化及销毁过程中潜在的约定就不会…虽然说 Spring 容器上手简单可以仅仅通过学习一些有限的注解即可达到快速使用的目的。但在工程实践中我们依然会从中发现一些常见的错误。尤其当你对 Spring 的生命周期还没有深入了解时类初始化及销毁过程中潜在的约定就不会很清楚。  这会导致这样一些状况发生有些错误我们可以在 Spring 的异常提示下快速解决但却不理解背后的原理而另一些错误并不容易在开发环境下被发现从而在产线上造成较为严重的后果。 接下来我们就具体解析下这些常见案例及其背后的原理。 案例 1构造器内抛空指针异常 先看个例子。在构建宿舍管理系统时有 LightMgrService 来管理 LightService从而控制宿舍灯的开启和关闭。我们希望在 LightMgrService 初始化时能够自动调用 LightService 的 check 方法来检查所有宿舍灯的电路是否正常代码如下 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; Component public class LightMgrService {Autowiredprivate LightService lightService;public LightMgrService() {lightService.check();} } 我们在 LightMgrService 的默认构造器中调用了通过 Autoware 注入的成员变量 LightService 的 check 方法 Service public class LightService {public void start() {System.out.println(turn on all lights);}public void shutdown() {System.out.println(turn off all lights);}public void check() {System.out.println(check all lights);} } 以上代码定义了 LightService 对象的原始类。 从整个案例代码实现来看我们的期待是在 LightMgrService 初始化过程中LightService 因为标记为 Autowired所以能被自动装配好然后在 LightMgrService 的构造器执行中LightService 的 check 方法能被自动调用最终打印出 check all lights。 然而事与愿违我们得到的只会是 NullPointerException错误示例如下 这是为什么呢 案例解析 显然这是新手最常犯的错误但是问题的根源是我们对 Spring 类初始化过程没有足够的了解。下面这张时序图描述了 Spring 启动时的一些关键结点 这个图初看起来复杂我们不妨将其分为三部分 第一部分将一些必要的系统类比如 Bean 的后置处理器类注册到 Spring 容器其中就包括我们这节课关注的 CommonAnnotationBeanPostProcessor 类 第二部分将这些后置处理器实例化并注册到 Spring 的容器中第三部分实例化所有用户定制类调用后置处理器进行辅助装配、类初始化等等。 第一部分和第二部分并非是我们今天要讨论的重点这里仅仅是为了让你知道 CommonAnnotationBeanPostProcessor 这个后置处理类是何时被 Spring 加载和实例化的。 这里我顺便给你拓展两个知识点 很多必要的系统类尤其是 Bean 后置处理器比如 CommonAnnotationBeanPostProcessor、AutowiredAnnotationBeanPostProcessor 等都是被 Spring 统一加载和管理的并在 Spring 中扮演了非常重要的角色通过 Bean 后置处理器Spring 能够非常灵活地在不同的场景调用不同的后置处理器比如接下来我会讲到示例问题如何修正修正方案中提到的 PostConstruct 注解它的处理逻辑就需要用到 CommonAnnotationBeanPostProcessor继承自 InitDestroyAnnotationBeanPostProcessor这个后置处理器。 现在我们重点看下第三部分即 Spring 初始化单例类的一般过程基本都是 getBean()-doGetBean()-getSingleton()如果发现 Bean 不存在则调用 createBean()-doCreateBean() 进行实例化。 查看 doCreateBean() 的源代码如下 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Nullable Object[] args)throws BeanCreationException {//省略非关键代码if (instanceWrapper null) {instanceWrapper createBeanInstance(beanName, mbd, args);}final Object bean instanceWrapper.getWrappedInstance();//省略非关键代码Object exposedObject bean;try {populateBean(beanName, mbd, instanceWrapper);exposedObject initializeBean(beanName, exposedObject, mbd);}catch (Throwable ex) {//省略非关键代码 } 上述代码完整地展示了 Bean 初始化的三个关键步骤createBeanInstancepopulateBeaninitializeBean分别对应实例化 Bean注入 Bean 依赖以及初始化 Bean 例如执行 PostConstruct 标记的方法 这三个功能这也和上述时序图的流程相符。 而用来实例化 Bean 的 createBeanInstance 方法通过依次调用 DefaultListableBeanFactory.instantiateBean() SimpleInstantiationStrategy.instantiate()最终执行到 BeanUtils.instantiateClass()其代码如下 public static T T instantiateClass(ConstructorT ctor, Object... args) throws BeanInstantiationException {Assert.notNull(ctor, Constructor must not be null);try {ReflectionUtils.makeAccessible(ctor);return (KotlinDetector.isKotlinReflectPresent() KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));}catch (InstantiationException ex) {throw new BeanInstantiationException(ctor, Is it an abstract class?, ex);}//省略非关键代码 }这里因为当前的语言并非 Kotlin所以最终将调用 ctor.newInstance() 方法实例化用户定制类 LightMgrService而默认构造器显然是在类实例化的时候被自动调用的Spring 也无法控制。而此时负责自动装配的 populateBean 方法还没有被执行LightMgrService 的属性 LightService 还是 null因而得到空指针异常也在情理之中。 问题修正 通过源码分析现在我们知道了问题的根源就是在于使用 Autowired 直接标记在成员属性上而引发的装配行为是发生在构造器执行之后的。所以这里我们可以通过下面这种修订方法来纠正这个问题 Component public class LightMgrService {private LightService lightService;public LightMgrService(LightService lightService) {this.lightService lightService;lightService.check();} } 当使用上面的代码时构造器参数 LightService 会被自动注入 LightService 的 Bean从而在构造器执行时不会出现空指针。可以说使用构造器参数来隐式注入是一种 Spring 最佳实践因为它成功地规避了案例 1 中的问题。 另外除了这种纠正方式有没有别的方式 实际上Spring 在类属性完成注入之后会回调用户定制的初始化方法。即在 populateBean 方法之后会调用 initializeBean 方法我们来看一下它的关键代码 protected Object initializeBean(final String beanName, final Object bean, Nullable RootBeanDefinition mbd) {//省略非关键代码 if (mbd null || !mbd.isSynthetic()) {wrappedBean applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);}try {invokeInitMethods(beanName, wrappedBean, mbd);}//省略非关键代码 } 这里你可以看到 applyBeanPostProcessorsBeforeInitialization 和 invokeInitMethods 这两个关键方法的执行它们分别处理了 PostConstruct 注解和 InitializingBean 接口这两种不同的初始化方案的逻辑。这里我再详细地给你讲讲。 1. applyBeanPostProcessorsBeforeInitialization 与 PostConstruct applyBeanPostProcessorsBeforeInitialization 方法最终执行到后置处理器 InitDestroyAnnotationBeanPostProcessor 的 buildLifecycleMetadata 方法CommonAnnotationBeanPostProcessor 的父类 private LifecycleMetadata buildLifecycleMetadata(final Class? clazz) {//省略非关键代码 do {//省略非关键代码final ListLifecycleElement currDestroyMethods new ArrayList();ReflectionUtils.doWithLocalMethods(targetClass, method - {//此处的 this.initAnnotationType 值即为 PostConstruct.classif (this.initAnnotationType ! null method.isAnnotationPresent(this.initAnnotationType)) {LifecycleElement element new LifecycleElement(method);currInitMethods.add(element);//非关键代码 } 在这个方法里Spring 将遍历查找被 PostConstruct.class 注解过的方法返回到上层并最终调用此方法。 2. invokeInitMethods 与 InitializingBean 接口 invokeInitMethods 方法会判断当前 Bean 是否实现了 InitializingBean 接口只有在实现了该接口的情况下Spring 才会调用该 Bean 的接口实现方法 afterPropertiesSet()。 protected void invokeInitMethods(String beanName, final Object bean, Nullable RootBeanDefinition mbd)throws Throwable {boolean isInitializingBean (bean instanceof InitializingBean);if (isInitializingBean (mbd null || !mbd.isExternallyManagedInitMethod(afterPropertiesSet))) {// 省略非关键代码 else {((InitializingBean) bean).afterPropertiesSet();}}// 省略非关键代码 } 学到此处答案也就呼之欲出了。我们还有两种方式可以解决此问题。 1. 添加 init 方法并且使用 PostConstruct 注解进行修饰 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; Component public class LightMgrService {Autowiredprivate LightService lightService;PostConstructpublic void init() {lightService.check();} } 2. 实现 InitializingBean 接口在其 afterPropertiesSet() 方法中执行初始化代码 import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; Component public class LightMgrService implements InitializingBean {Autowiredprivate LightService lightService;Overridepublic void afterPropertiesSet() throws Exception {lightService.check();} } 方案很明显针对本案例而言后续的两种方案并不是最优的。但是在一些场景下这两种方案各有所长不然 Spring 为什么要提供这个功能呢对吧 案例 2意外触发 shutdown 方法 上述实例我给你讲解了类初始化时最容易遇到的问题同样在类销毁时也会有一些相对隐蔽的约定导致一些难以察觉的错误。 接下来我们再来看一个案例还是沿用之前的场景。这里我们可以简单复习一下 LightService 的实现它包含了 shutdown 方法负责关闭所有的灯关键代码如下 import org.springframework.stereotype.Service; Service public class LightService {//省略其他非关键代码public void shutdown(){System.out.println(shutting down all lights);}//省略其他非关键代码 } 在之前的案例中如果我们的宿舍管理系统在重启时灯是不会被关闭的。但是随着业务的需求变化我们可能会去掉 Service 注解而是使用另外一种产生 Bean 的方式创建一个配置类 BeanConfiguration标记 Configuration来创建一堆 Bean其中就包含了创建 LightService 类型的 Bean并将其注册到 Spring 容器 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class BeanConfiguration {Beanpublic LightService getTransmission(){return new LightService();} } 复用案例 1 的启动程序稍作修改让 Spring 启动完成后立马关闭当前 Spring 上下文。这样等同于模拟宿舍管理系统的启停 SpringBootApplication public class Application {public static void main(String[] args) {ConfigurableApplicationContext context SpringApplication.run(Application.class, args);context.close();} } 以上代码没有其他任何方法的调用仅仅是将所有符合约定的类初始化并加载到 Spring 容器完成后再关闭当前的 Spring 容器。按照预期这段代码运行后不会有任何的 log 输出毕竟我们只是改变了 Bean 的产生方式。 但实际运行这段代码后我们可以看到控制台上打印了 shutting down all lights。显然 shutdown 方法未按照预期被执行了这导致一个很有意思的 bug在使用新的 Bean 生成方式之前每一次宿舍管理服务被重启时宿舍里所有的灯都不会被关闭。但是修改后只有服务重启灯都被意外关闭了。如何理解这个 bug? 案例解析 通过调试我们发现只有通过使用 Bean 注解注册到 Spring 容器的对象才会在 Spring 容器被关闭的时候自动调用 shutdown 方法而使用 ComponentService 也是一种 Component将当前类自动注入到 Spring 容器时shutdown 方法则不会被自动执行。 我们可以尝试到 Bean 注解类的代码中去寻找一些线索可以看到属性 destroyMethod 有非常大段的注释基本上解答了我们对于这个问题的大部分疑惑。 使用 Bean 注解的方法所注册的 Bean 对象如果用户不设置 destroyMethod 属性则其属性值为 AbstractBeanDefinition.INFER_METHOD。此时 Spring 会检查当前 Bean 对象的原始类中是否有名为 shutdown 或者 close 的方法如果有此方法会被 Spring 记录下来并在容器被销毁时自动执行当然如若没有那么自然什么都不会发生。 下面我们继续查看 Spring 的源代码来进一步分析此问题。 首先我们可以查找 INFER_METHOD 枚举值的引用很容易就找到了使用该枚举值的方法 DisposableBeanAdapter#inferDestroyMethodIfNecessary private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {String destroyMethodName beanDefinition.getDestroyMethodName();if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||(destroyMethodName null bean instanceof AutoCloseable)) {if (!(bean instanceof DisposableBean)) {try {//尝试查找 close 方法return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();}catch (NoSuchMethodException ex) {try {//尝试查找 shutdown 方法return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();}catch (NoSuchMethodException ex2) {// no candidate destroy method found}}}return null;}return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null); }我们可以看到代码逻辑和 Bean 注解类中对于 destroyMethod 属性的注释完全一致 destroyMethodName 如果等于 INFER_METHOD且当前类没有实现 DisposableBean 接口那么首先查找类的 close 方法如果找不到就在抛出异常后继续查找 shutdown 方法如果找到了则返回其方法名close 或者 shutdown。 接着继续逐级查找引用最终得到的调用链从上到下为 doCreateBean-registerDisposableBeanIfNecessary-registerDisposableBean(new DisposableBeanAdapter)-inferDestroyMethodIfNecessary。 然后我们追溯到了顶层的 doCreateBean 方法代码如下 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Nullable Object[] args)throws BeanCreationException {//省略非关键代码 if (instanceWrapper null) {instanceWrapper createBeanInstance(beanName, mbd, args);}//省略非关键代码// Initialize the bean instance.Object exposedObject bean;try {populateBean(beanName, mbd, instanceWrapper);exposedObject initializeBean(beanName, exposedObject, mbd);}//省略非关键代码 // Register bean as disposable.try {registerDisposableBeanIfNecessary(beanName, bean, mbd);}catch (BeanDefinitionValidationException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, Invalid destruction signature, ex);}return exposedObject; } 到这我们就可以对 doCreateBean 方法做一个小小的总结了。可以说 doCreateBean 管理了 Bean 的整个生命周期中几乎所有的关键节点直接负责了 Bean 对象的生老病死其主要功能包括 Bean 实例的创建Bean 对象依赖的注入定制类初始化方法的回调Disposable 方法的注册。 接着继续查看 registerDisposableBean 方法 public void registerDisposableBean(String beanName, DisposableBean bean) {//省略其他非关键代码synchronized (this.disposableBeans) {this.disposableBeans.put(beanName, bean);}//省略其他非关键代码 } 在 registerDisposableBean 方法内DisposableBeanAdapter 类其属性 destroyMethodName 记录了使用哪种 destory 方法被实例化并添加到 DefaultSingletonBeanRegistry#disposableBeans 属性内disposableBeans 将暂存这些 DisposableBeanAdapter 实例直到 AnnotationConfigApplicationContext 的 close 方法被调用。 而当 AnnotationConfigApplicationContext 的 close 方法被调用时即当 Spring 容器被销毁时最终会调用到 DefaultSingletonBeanRegistry#destroySingleton。此方法将遍历 disposableBeans 属性逐一获取 DisposableBean依次调用其中的 close 或者 shutdown 方法 public void destroySingleton(String beanName) {// Remove a registered singleton of the given name, if any.removeSingleton(beanName);// Destroy the corresponding DisposableBean instance.DisposableBean disposableBean;synchronized (this.disposableBeans) {disposableBean (DisposableBean) this.disposableBeans.remove(beanName);}destroyBean(beanName, disposableBean); }很明显最终我们的案例调用了 LightService#shutdown 方法将所有的灯关闭了。 问题修正 现在我们已经知道了问题的根源解决起来就非常简单了。 我们可以通过避免在 Java 类中定义一些带有特殊意义动词的方法来解决当然如果一定要定义名为 close 或者 shutdown 方法也可以通过将 Bean 注解内 destroyMethod 属性设置为空的方式来解决这个问题。 第一种修改方式比较简单所以这里只展示第二种修改方式代码如下 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class BeanConfiguration {Bean(destroyMethod)public LightService getTransmission(){return new LightService();} }另外针对这个问题我想再多提示一点。如果我们能养成良好的编码习惯在使用某个不熟悉的注解之前认真研读一下该注解的注释也可以大概率规避这个问题。 不过说到这里你也可能还是会疑惑为什么 Service 注入的 LightService其 shutdown 方法不能被执行这里我想补充说明下。 想要执行则必须要添加 DisposableBeanAdapter而它的添加是有条件的 protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {AccessControlContext acc (System.getSecurityManager() ! null ? getAccessControlContext() : null);if (!mbd.isPrototype() requiresDestruction(bean, mbd)) {if (mbd.isSingleton()) {// Register a DisposableBean implementation that performs all destruction// work for the given bean: DestructionAwareBeanPostProcessors,// DisposableBean interface, custom destroy method.registerDisposableBean(beanName,new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));}else {//省略非关键代码}} } 参考上述代码关键的语句在于 !mbd.isPrototype() requiresDestruction(bean, mbd) 很明显在案例代码修改前后我们都是单例所以区别仅在于是否满足 requiresDestruction 条件。翻阅它的代码最终的关键调用参考 DisposableBeanAdapter#hasDestroyMethod public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {return true;}String destroyMethodName beanDefinition.getDestroyMethodName();if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) ||ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME));}return StringUtils.hasLength(destroyMethodName); } 如果我们是使用 Service 来产生 Bean 的那么在上述代码中我们获取的 destroyMethodName 其实是 null而使用 Bean 的方式默认值为 AbstractBeanDefinition.INFER_METHOD参考 Bean 的定义 public interface Bean {//省略其他非关键代码String destroyMethod() default AbstractBeanDefinition.INFER_METHOD; } 继续对照代码你就会发现 Service 标记的 LightService 也没有实现 AutoCloseable、DisposableBean最终没有添加一个 DisposableBeanAdapter。所以最终我们定义的 shutdown 方法没有被调用。 重点回顾 通过以上两个案例相信你对 Spring 生命周期尤其是对于 Bean 的初始化和销毁流程已经有了一定的了解。这里带你再次回顾下重点 1. DefaultListableBeanFactory 类是 Spring Bean 的灵魂而核心就是其中的 doCreateBean 方法它掌控了 Bean 实例的创建、Bean 对象依赖的注入、定制类初始化方法的回调以及 Disposable 方法的注册等全部关键节点。 2. 后置处理器是 Spring 中最优雅的设计之一对于很多功能注解的处理都是借助于后置处理器来完成的。虽然这节课对其没有过多介绍但在第一个案例中Bean 对象“补充”初始化动作却是在 CommonAnnotationBeanPostProcessor继承自 InitDestroyAnnotationBeanPostProcessor这个后置处理器中完成的。
http://www.zqtcl.cn/news/775002/

相关文章:

  • 怎么添加网站背景音乐定制建站
  • 西安网站建设和推广公司php企业网站开发实训报告
  • 市网站制作番禺绿叶居
  • 网站漂浮物怎么做电子商务网站开发实训体会
  • 电商网站建设定制济南网站建设及推广
  • 网站标签化wordpress 输出评论内容 函数
  • 明星个人网站设计汕头网络推广公司
  • 深圳有哪些做网站公司好武夷山建设局网站
  • 怎么设立网站赚广告费一个网站可以做多少关键字
  • 网站刚建好怎么做能让百度收录成都定制网站建
  • thinkphp网站开发技术做电脑租赁网站
  • 网站设计评语中午版wordpress
  • 邢台企业手机网站建设汕头网站制作后缀
  • 微网站后台内容设置做网站语言排名2018
  • 嘉兴网站制作网站建设外贸营销推广平台有哪些
  • 网站开发集广州高端网站定制开发价格
  • 网站开发培训成都网站建设 报价单 doc
  • 苏州哪里有做淘宝网站的WordPress模板博客主题
  • 网站做中转做任务 网站
  • 深圳住房建设局网站网站的建设教程
  • 6免费建站的网站在线建筑设计
  • 哪些网站做任务可以赚钱的建设厅网站如何查询企业信息
  • 深圳网站设计+建设首选深圳市服装网站建设需求分析报告
  • 肥城网站制作浙江省建设厅信息港官网
  • 手机网站建设进度南宁企业网站设计
  • 建设学校网站方案大淘客网站上的推广怎么做
  • 哪个网站可以免费学设计南阳网站建设页面
  • 外贸公司建网站一般多少钱南京网站建设小程
  • 洛阳霞光做网站公司手机编程教学
  • 深圳正规网站建设公司顺德网页制作公司