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

什么是虚拟网站铁路建设网站

什么是虚拟网站,铁路建设网站,平台期是什么意思,网络营销十大成功案例Spring 的核心是围绕 Bean 进行的。不管是 Spring Boot 还是 Spring Cloud#xff0c;只要名称中带有 Spring 关键字的技术都脱离不了 Bean#xff0c;而要使用一个 Bean 少不了要先定义出来#xff0c;所以定义一个 Bean 就变得格外重要了。 当然#xff0c;对于这么重要…        Spring 的核心是围绕 Bean 进行的。不管是 Spring Boot 还是 Spring Cloud只要名称中带有 Spring 关键字的技术都脱离不了 Bean而要使用一个 Bean 少不了要先定义出来所以定义一个 Bean 就变得格外重要了。 当然对于这么重要的工作Spring 自然给我们提供了很多简单易用的方式。然而这种简单易用得益于 Spring 的“约定大于配置”但我们往往不见得会对所有的约定都了然于胸所以仍然会在 Bean 的定义上犯一些经典的错误。 接下来我们就来了解下那些经典错误以及它们背后的原理你也可以对照着去看看自己是否也曾犯过后来又是如何解决的。 案例 1隐式扫描不到 Bean 的定义 在构建 Web 服务时我们常使用 Spring Boot 来快速构建。例如使用下面的包结构和相关代码来完成一个简易的 Web 版 HelloWorld 其中负责启动程序的 Application 类定义如下 package com.spring.puzzle.class1.example1.application //省略 import SpringBootApplication public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);} } 提供接口的 HelloWorldController 代码如下 package com.spring.puzzle.class1.example1.application //省略 import RestController public class HelloWorldController {RequestMapping(path hi, method RequestMethod.GET)public String hi(){return helloworld;}; } 上述代码即可实现一个简单的功能访问http://localhost:8080/hi 返回 helloworld。两个关键类位于同一个包即 application中。其中 HelloWorldController 因为添加了 RestController最终被识别成一个 Controller 的 Bean。 但是假设有一天当我们需要添加多个类似的 Controller同时又希望用更清晰的包层次和结构来管理时我们可能会去单独建立一个独立于 application 包之外的 Controller 包并调整类的位置。调整后结构示意如下 案例解析 要了解 HelloWorldController 为什么会失效就需要先了解之前是如何生效的。对于 Spring Boot 而言关键点在于 Application.java 中使用了 SpringBootApplication 注解。而这个注解继承了另外一些注解具体定义如下 Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Inherited SpringBootConfiguration EnableAutoConfiguration ComponentScan(excludeFilters { Filter(type FilterType.CUSTOM, classes TypeExcludeFilter.class),Filter(type FilterType.CUSTOM, classes AutoConfigurationExcludeFilter.class) }) public interface SpringBootApplication { //省略非关键代码 } 从定义可以看出SpringBootApplication 开启了很多功能其中一个关键功能就是 ComponentScan参考其配置如下 ComponentScan(excludeFilters { Filter(type FilterType.CUSTOM, classes TypeExcludeFilter.class) 当 Spring Boot 启动时ComponentScan 的启用意味着会去扫描出所有定义的 Bean那么扫描什么位置呢这是由 ComponentScan 注解的 basePackages 属性指定的具体可参考如下定义 public interface ComponentScan {/*** Base packages to scan for annotated components.* p{link #value} is an alias for (and mutually exclusive with) this* attribute.* pUse {link #basePackageClasses} for a type-safe alternative to* String-based package names.*/ AliasFor(value) String[] basePackages() default {}; //省略其他非关键代码 } 而在我们的案例中我们直接使用的是 SpringBootApplication 注解定义的 ComponentScan它的 basePackages 没有指定所以默认为空即{}。此时扫描的是什么包这里不妨带着这个问题去调试下调试位置参考 ComponentScanAnnotationParser#parse 方法调试视图如下 从上图可以看出当 basePackages 为空时扫描的包会是 declaringClass 所在的包在本案例中declaringClass 就是 Application.class所以扫描的包其实就是它所在的包即 com.spring.puzzle.class1.example1.application。 对比我们重组包结构前后我们自然就找到了这个问题的根源在调整前HelloWorldController 在扫描范围内而调整后它已经远离了扫描范围不和 Application.java 一个包了虽然代码没有一丝丝改变但是这个功能已经失效了。 所以综合来看这个问题是因为我们不够了解 Spring Boot 的默认扫描规则引起的。我们仅仅享受了它的便捷但是并未了解它背后的故事所以稍作变化就可能玩不转了。 问题修正 针对这个案例有了源码的剖析我们可以快速找到解决方案了。当然了我们所谓的解决方案肯定不是说把 HelloWorldController 移动回原来的位置而是真正去满足需求。在这里真正解决问题的方式是显式配置 ComponentScan。具体修改方式如下  SpringBootApplication ComponentScan(com.spring.puzzle.class1.example1.controller) public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);} } 通过上述修改我们显式指定了扫描的范围为 com.spring.puzzle.class1.example1.controller。不过需要注意的是显式指定后默认的扫描范围即 com.spring.puzzle.class1.example1.application就不会被添加进去了。另外我们也可以使用 ComponentScans 来修复问题使用方式如下 ComponentScans(value { ComponentScan(value com.spring.puzzle.class1.example1.controller) }) 顾名思义可以看出 ComponentScans 相比较 ComponentScan 多了一个 s支持多个包的扫描范围指定。 此时细心的你可能会发现如果对源码缺乏了解很容易会顾此失彼。以 ComponentScan 为例原有的代码扫描了默认包而忽略了其它包而一旦显式指定其它包原来的默认扫描包就被忽略了。 案例 2定义的 Bean 缺少隐式依赖 初学 Spring 时我们往往不能快速转化思维。例如在程序开发过程中有时候一方面我们把一个类定义成 Bean同时又觉得这个 Bean 的定义除了加了一些 Spring 注解外并没有什么不同。所以在后续使用时有时候我们会不假思索地去随意定义它例如我们会写出下面这样的代码 Service public class ServiceImpl {private String serviceName;public ServiceImpl(String serviceName){this.serviceName serviceName;}} ServiceImpl 因为标记为 Service 而成为一个 Bean。另外我们 ServiceImpl 显式定义了一个构造器。但是上面的代码不是永远都能正确运行的有时候会报下面这种错误 Parameter 0 of constructor in com.spring.puzzle.class1.example2.ServiceImpl required a bean of type java.lang.String that could not be found. 那这种错误是怎么发生的呢下面我们来分析一下。 案例解析 当创建一个 Bean 时调用的方法是 AbstractAutowireCapableBeanFactory#createBeanInstance。它主要包含两大基本步骤寻找构造器和通过反射调用构造器创建实例。对于这个案例最核心的代码执行你可以参考下面的代码片段 // Candidate constructors for autowiring? Constructor?[] ctors determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors ! null || mbd.getResolvedAutowireMode() AUTOWIRE_CONSTRUCTOR ||mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {return autowireConstructor(beanName, mbd, ctors, args); } Spring 会先执行 determineConstructorsFromBeanPostProcessors 方法来获取构造器然后通过 autowireConstructor 方法带着构造器去创建实例。很明显在本案例中只有一个构造器所以非常容易跟踪这个问题。 autowireConstructor 方法要创建实例不仅需要知道是哪个构造器还需要知道构造器对应的参数这点从最后创建实例的方法名也可以看出参考如下即 ConstructorResolver#instantiate private Object instantiate(String beanName, RootBeanDefinition mbd, Constructor? constructorToUse, Object[] argsToUse) 那么上述方法中存储构造参数的 argsToUse 如何获取呢换言之当我们已经知道构造器 ServiceImpl(String serviceName)要创建出 ServiceImpl 实例如何确定 serviceName 的值是多少 很明显这里是在使用 Spring我们不能直接显式使用 new 关键字来创建实例。Spring 只能是去寻找依赖来作为构造器调用参数。 那么这个参数如何获取呢可以参考下面的代码片段即 ConstructorResolver#autowireConstructor argsHolder createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,getUserDeclaredConstructor(candidate), autowiring, candidates.length 1); 我们可以调用 createArgumentArray 方法来构建调用构造器的参数数组而这个方法的最终实现是从 BeanFactory 中获取 Bean可以参考下述调用 return this.beanFactory.resolveDependency(new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter); 如果用调试视图我们则可以看到更多的信息 如图所示上述的调用即是根据参数来寻找对应的 Bean在本案例中如果找不到对应的 Bean 就会抛出异常提示装配失败。 问题修正 从源码级别了解了错误的原因后现在反思为什么会出现这个错误。追根溯源正如开头所述因为不了解很多隐式的规则我们定义一个类为 Bean如果再显式定义了构造器那么这个 Bean 在构建时会自动根据构造器参数定义寻找对应的 Bean然后反射创建出这个 Bean。 了解了这个隐式规则后解决这个问题就简单多了。我们可以直接定义一个能让 Spring 装配给 ServiceImpl 构造器参数的 Bean例如定义如下 //这个bean装配给ServiceImpl的构造器参数“serviceName” Bean public String serviceName(){return MyServiceName; } 再次运行程序发现一切正常了。 所以我们在使用 Spring 时不要总想着定义的 Bean 也可以在非 Spring 场合直接用 new 关键字显式使用这种思路是不可取的。 另外类似的假设我们不了解 Spring 的隐式规则在修正问题后我们可能写出更多看似可以运行的程序代码如下 Service public class ServiceImpl {private String serviceName;public ServiceImpl(String serviceName){this.serviceName serviceName;}public ServiceImpl(String serviceName, String otherStringParameter){this.serviceName serviceName;} } 如果我们仍用非 Spring 的思维去审阅这段代码可能不会觉得有什么问题毕竟 String 类型可以自动装配了无非就是增加了一个 String 类型的参数而已。 但是如果你了解 Spring 内部是用反射来构建 Bean 的话就不难发现问题所在存在两个构造器都可以调用时到底应该调用哪个呢最终 Spring 无从选择只能尝试去调用默认构造器而这个默认构造器又不存在所以测试这个程序它会出错。 案例 3原型 Bean 被固定 接下来我们再来看另外一个关于 Bean 定义不生效的案例。在定义 Bean 时有时候我们会使用原型 Bean例如定义如下 Service Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class ServiceImpl { } 然后我们按照下面的方式去使用它 RestController public class HelloWorldController {Autowiredprivate ServiceImpl serviceImpl;RequestMapping(path hi, method RequestMethod.GET)public String hi(){return helloworld, service is : serviceImpl;}; } 结果我们会发现不管我们访问多少次http://localhost:8080/hi访问的结果都是不变的如下 helloworld, service is : com.spring.puzzle.class1.example3.error.ServiceImpl4908af 很明显这很可能和我们定义 ServiceImpl 为原型 Bean 的初衷背道而驰如何理解这个现象呢 案例解析 当一个属性成员 serviceImpl 声明为 Autowired 后那么在创建 HelloWorldController 这个 Bean 时会先使用构造器反射出实例然后来装配各个标记为 Autowired 的属性成员装配方法参考 AbstractAutowireCapableBeanFactory#populateBean。 具体到执行过程它会使用很多 BeanPostProcessor 来做完成工作其中一种是 AutowiredAnnotationBeanPostProcessor它会通过 DefaultListableBeanFactory#findAutowireCandidates 寻找到 ServiceImpl 类型的 Bean然后设置给对应的属性即 serviceImpl 成员。 关键执行步骤可参考 AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject protected void inject(Object bean, Nullable String beanName, Nullable PropertyValues pvs) throws Throwable {Field field (Field) this.member;Object value;//寻找“bean”if (this.cached) {value resolvedCachedArgument(beanName, this.cachedFieldValue);}else {//省略其他非关键代码value beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);}if (value ! null) {//将bean设置给成员字段ReflectionUtils.makeAccessible(field);field.set(bean, value);} } 待我们寻找到要自动注入的 Bean 后即可通过反射设置给对应的 field。这个 field 的执行只发生了一次所以后续就固定起来了它并不会因为 ServiceImpl 标记了 SCOPE_PROTOTYPE 而改变。 所以当一个单例的 Bean使用 autowired 注解标记其属性时你一定要注意这个属性值会被固定下来。 问题修正 通过上述源码分析我们可以知道要修正这个问题肯定是不能将 ServiceImpl 的 Bean 固定到属性上的而应该是每次使用时都会重新获取一次。所以这里我提供了两种修正方式 1. 自动注入 Context 即自动注入 ApplicationContext然后定义 getServiceImpl() 方法在方法中获取一个新的 ServiceImpl 类型实例。修正代码如下 RestController public class HelloWorldController {Autowiredprivate ApplicationContext applicationContext;RequestMapping(path hi, method RequestMethod.GET)public String hi(){return helloworld, service is : getServiceImpl();};public ServiceImpl getServiceImpl(){return applicationContext.getBean(ServiceImpl.class);}} 2. 使用 Lookup 注解 类似修正方法 1也添加一个 getServiceImpl 方法不过这个方法是被 Lookup 标记的。修正代码如下 RestController public class HelloWorldController {RequestMapping(path hi, method RequestMethod.GET)public String hi(){return helloworld, service is : getServiceImpl();};Lookuppublic ServiceImpl getServiceImpl(){return null;} } 通过这两种修正方式再次测试程序我们会发现结果已经符合预期每次访问这个接口都会创建新的 Bean。 重点回顾 不难发现要使用好 Spring就一定要了解它的一些潜规则例如默认扫描 Bean 的范围、自动装配构造器等等。如果我们不了解这些规则大多情况下虽然也能工作但是稍微变化则可能完全失效例如在案例 1 中我们也只是把 Controller 从一个包移动到另外一个包接口就失效了。 另外通过这三个案例的分析我们也能感受到 Spring 的很多实现是通过反射来完成的了解了这点对于理解它的源码实现会大有帮助。例如在案例 2 中为什么定义了多个构造器就可能报错因为使用反射方式来创建实例必须要明确使用的是哪一个构造器。 最后我想说在 Spring 框架中解决问题的方式往往有多种不要拘泥于套路。就像案例 3使用 ApplicationContext 和 Lookup 注解都能解决原型 Bean 被固定的问题一样。
http://www.zqtcl.cn/news/366043/

相关文章:

  • 宝格丽网站建设哈尔滨网站建设王道下拉強
  • 烟台网站建设的公司世界500强企业排名2021
  • 网络营销做得比较成功的案例吴中seo网站优化软件
  • 怎么设立网站美区下载的app怎么更新
  • 建立网站ppt做酒店网站所用到的算法
  • 上海网站建设的价格低太仓做网站的公司
  • 怎样登录建设互联网站怎么做中英文网站
  • 云网站7china中小企业网站建设好么
  • 美丽南方官网网站建设国际新闻最新消息今天摘抄
  • 牛商网营销型网站多少钱江门营销型网站建设多少钱
  • 小榄公司网站建设网站交互做的比较好的
  • 深圳定制网站建设怎么改版网站
  • 免费学软件的自学网站江阴建设局网站
  • 网站做多久苍南县网站集约化建设
  • 深圳电子烟网站建设罗湖建设公司网站建设
  • 酒店 深圳 网站建设新项目首码对接平台
  • 岳阳市住房和城乡建设局网站上海专业网站建设网
  • 营销型网站建设设定包括哪些方面网站建设后的心得
  • 建立网站来网上销售的英文潢川城乡建设局网站
  • 仿站建站教程网站怎么接广告
  • 免费下载代码项目的网站长春网站建设找新生科技
  • 博兴县建设局网站做网站要用什么服务器吗
  • 成都中小企业网站建设公司怎么挑选网站建设公司
  • 万源网站建设在ppt里面做网站链接
  • 做网站时怎么添加动态信息中铁航空港建设集团网站
  • 文化礼堂建设情况网站网站建设运行
  • 自己做网站很难asp网站开发四酷全书:新闻_论坛_电子商城_博客
  • 网站建设入什么会计科目从网络安全角度考量请写出建设一个大型电影网站规划方案
  • 品牌建设+网站网站建设 淘宝客末班
  • 建设商业网站学校建设门户网站的好处