接上篇【熟练掌握spring框架第二篇】

bean的生命周期

参考:http://javainsimpleway.com/sp...

这是一个比拟根底然而又比拟高频的面试题。如果面试官问你spring bean的生命周期都有哪些?那应该怎么答复呢?在答复之前能够先剖析一下这个题目。首先想想面试官问这个问题的目标是什么?换位思考,如果我是面试官,我心愿通过这个题目理解求职者对spring框架的理解水平,它是如何治理bean的。在整个bean对生命周期中都有哪些是咱们能够参加的。罕用的场景是什么?不同类型的bean的生命周期有什么不同吗?如果求职者这几个问题都能分明的示意进去,那我认为这道面试题他pass了。学习bean的生命周期目标还是为了在理论工作中能够进行自在扩大。以满足业务须要。那上面就从这几个方面剖析下bean的生命周期。

先看下上面这张图,起源:http://javainsimpleway.com/sp...

  1. 首先实例化bean
  2. populateBean
  3. 调用初始化办法之前首先调用所有bean有感知的办法,包含BeanNameAwareBeanClassLoaderAwareBeanFactoryAware
  4. 而后执行BeanPostProcessorpostProcessBeforeInitialization
  5. 执行初始化办法,如果bean实现了InitializingBean会调用他的afterPropertiesSet办法。比方之前提到的RepositoryFactoryBeanSupport就通过afterPropertiesSet进行repository的创立。
  6. 反射调用自定义init-method办法。
  7. 而后执行BeanPostProcessorpostProcessAfterInitialization

其中当执行到ApplicationContextAwareProcessorpostProcessBeforeInitialization时,调用bean的利用级的有感知的办法。比方ApplicationContextAwareEnvironmentAware这些。

咱们相熟的BeanPostProcessor还有AutowiredAnnotationBeanPostProcessor,用来进行属性主动拆卸。

RequiredAnnotationBeanPostProcessor,它能够确保申明"必须"属性的bean实际上已配置了值,否则就会爆出相似上面这样的谬误

CommonAnnotationBeanPostProcessor解决@PostConstruct@PreDestroy,执行@PostConstruct的逻辑是在它的父类InitDestroyAnnotationBeanPostProcessorpostProcessBeforeInitialization里进行的。执行@PreDestroy的逻辑是在InitDestroyAnnotationBeanPostProcessorpostProcessBeforeDestruction里进行的。

所以@PostConstruct执行的时候,bean的属性曾经装填实现了。并且只会被执行一次,能够执行一些须要依赖项的初始化工作。

@PreDestroy的原理是利用了jdk的shutdown hook,能够实现应用程序的优雅敞开。留神shutdown hook不应该执行耗时的操作,这样会导致程序不能失常退出。个别运维写脚本的时候都会设置一个超时工夫,一旦超过,就应用kill -9强制退出。

Spring治理的Bean默认是单例的。bean的所有scope有如下这些


起源:spring官网文档

request session application 只存在于web利用上下文中。websocket存在websocket环境中。这些本文不做详细描述,singleton具体读者曾经很相熟了,那么咱们着重关注下prototype这个类型。

@Component@Scope(BeanDefinition.SCOPE_PROTOTYPE)public class A {  }

定义一个简略的类,申明为scope为prototype。spring启动后调用applicationContext.getBean("a"),代码流程大抵如下。

  1. 调用AbstractBeanFactorydoGetBean办法
  2. 判断如果原型bean正在创立则间接抛出异样。
  3. 拿到相应的BeanDefinition,判断如果是Prototype类型
  4. 调用beforePrototypeCreation标记正在创立
  5. createBean创立bean,和创立单例bean是同一个办法。
  6. 调用afterPrototypeCreation革除标记

所以prototype类型的bean是不反对循环依赖的。另外因为和创立singletonbean是同一个办法,所以bean的所有有感知的办法也都是差不多的。一个很重要的不同就是原型bean@PreDestroy是不会执行的。起因很简略destroy办法是通过shutdownhook调用beanFactorydestroySingletons办法实现的。spring没有定义prototypebean的销毁动作。

更多具体的解释能够参考:https://bluebreeze0812.github...

spring 动静代理与AOP

代理模式

代理模式是GoF 23种Java罕用设计模式之一,隶属于结构型模式。一个随处可见的利用场景就是rpc框架比方dubbo外面的service调用。本地调用的service实际上是近程对象的代理对象。调用代理对象的办法理论是调用了近程对象的办法。又比方 JAVA RMI ,当然了对近程代理这里不做过多形容。明天咱们要讲的是spring的动静代理。家喻户晓,Spring代理实际上是对JDK代理CGLIB代理做了一层封装。那么咱们先来看下jdk和cglib代理。这也是烹饪spring aop这道大菜比不可少的佐料。

JDK动静代理

public class JdkProxyDemo {    public interface Calculator {        int add(int a, int b);        int subtract(int a, int b);    }    public static class CalculatorImpl implements Calculator {        @Override        public int add(int a, int b) {            return a + b;        }        @Override        public int subtract(int a, int b) {            return a - b;        }    }    public static class ProxyFactory implements InvocationHandler {        private final Calculator real;        public ProxyFactory(Calculator real) {            this.real = real;        }        @Override        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {            System.out.println("before");            Object result = method.invoke(real, args);            System.out.println("after");            return result;        }    }    public static void main(String[] args) {        Calculator real = new CalculatorImpl();        ProxyFactory proxyFactory = new ProxyFactory(real);        Calculator proxy = (Calculator) Proxy.newProxyInstance(real.getClass().getClassLoader(), new Class[]{Calculator.class}, proxyFactory);        System.out.println(proxy.add(1, 2));        System.out.println(proxy.subtract(2, 1));    }}

由下面这个简略的例子能够总结出jdk动静代理有如下特点。

  1. 创立代理对象须要三要素:类加载器,代理对象须要实现的接口列表。InvocationHandler实例。

  1. 代理对象的class是com.sun.proxy.$Proxy0实现了Calculator接口
  2. 代理对象持有InvocationHandler实例的援用,而InvocationHandler持有被代理对象的援用。
  3. InvocationHandler的invoke办法代理了接口的所有办法。你能够在被代理对象执行前后增加逻辑,你甚至不调用代理对象的办法都能够。
  4. 代理对象须要实现的接口列表是必须的。这也是jdk动静代理最大的特点。代理对象和被代理对象都实现了独特的接口。否则是无奈代理的。

cglib动静代理

字节码生成类库,它封装了ASM,它是一个字节码操作框架,相似的框架还有javaassit,大略原理就是解析.class文件而后动静批改它。

public class CglibProxyDemo {    public static class Calculator {        public int add(int a, int b) {            return a + b;        }    }    public static class CalculatorInterceptor implements MethodInterceptor {        @Override        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {            System.out.println("before add");            Object o1 = methodProxy.invokeSuper(o, objects);            System.out.println("after add");            return o1;        }    }    public static void main(String[] args) {        Enhancer enhancer = new Enhancer();        enhancer.setSuperclass(Calculator.class);        enhancer.setCallback(new CalculatorInterceptor());        Calculator calculator = (Calculator) enhancer.create();        System.out.println(calculator.add(1, 2));    }}

由下面这个简略的例子咱们能够总结出cglib动静代理有如下特点:

  1. 生成的代理对象的class是com.family.spring.core.CglibProxyDemo$Calculator$$EnhancerByCGLIB$$b4da3734
  2. 它是Calculator类的子类。遵循继承规定,子类不能笼罩父类的公有办法。也就是说公有办法是不能被代理的。
  3. MethodInterceptor定义了一个办法拦截器。这个拦截器会拦挡代理类的所有能够代理的办法。你也能够决定是否调用父类实在的办法。
  4. cglib代理和jdk代理有两个很重要的区别,第一就是不须要独特的接口,第二不须要筹备一个被代理的对象。

如果读者对于代理的class构造到底是什么样感兴趣的话。也能够应用java代理技术读取jvm外面相应的class文件,进行剖析。

spring动静代理

为什么须要AOP

软件开发是一个演变的过程,从最后的POP(面向过程程序设计)到OOP(面向对象程序设计)再到AOP(面向切面编程),将来可能还有一堆的OP,每种编程思维都是软件开发进化的产物。都是为了解决特定的问题应运而生的。那么AOP产生的背景是什么呢。我认为随着软件系统的复杂化,一些与外围业务逻辑无关的内容越来越多。比方:记录日志,权限验证,事务管制,错误信息检测。而这些逻辑又散落在程序的每一个中央。这样不仅会减少写代码的复杂性和工作量,还会大大增加代码的保护老本。比方权限验证,如果每个接口都手写代码去判断以后用户是否有该接口的拜访权限的话,那真的很蛋疼。所以聪慧的程序员们就想把这些代码放到同一个中央,而后采取动静植入的形式增加到业务代码执行前后,这样代码对立起来了,而且业务逻辑外面简直看不到增加的代码,程序员就能够聚精会神的进行CRUD了,这种设计思维有个高大上的名字就是AOP,英文全称是Aspect Oriented Programming,维基百科管这个叫编程范式。为了让这个设计理念更加专业化,还顺便引入一堆的专业术语。上面就简略论述下每个术语的含意。

术语含意
告诉 Advice相似于后面说的权限验证,springaop反对的告诉有:前置告诉,后置告诉,异样告诉,最终告诉,盘绕告诉五种
连接点 JoinPoint就是容许应用告诉的中央,比如说办法连接点(办法执行前后),异样连接点(抛出异样时)等
切点 Pointcut织入告诉的连接点就叫做切点。
切面 Aspect切面就是告诉和切点的联合,两者组合一起定义了切面三要素:要做什么何时做何地做
织入 weaving把切面利用到指标对象来创立新的代理对象的过程

有了下面的概念了解,咱们对spring aop依然是实践层面的。那么他的实现是怎么的呢。上面就以一个简略的例子一探到底。
外围代码:

@Aspect@Componentpublic class MonitorAspect {    @Pointcut("execution(* com.family.spring.core..*.*(..))  ")    public void pointCut() {    }    @Around("pointCut()")    public Object around(ProceedingJoinPoint pjp) throws Throwable {        StopWatch stopWatch = new StopWatch();        stopWatch.start();        Object result = pjp.proceed();        stopWatch.stop();        System.out.println("执行" + pjp.getSignature().getName() + "共破费了" + stopWatch.getTotalTimeMillis() + "毫秒");        return result;    }}@SpringBootApplication@EnableAspectJAutoProxypublic class SpringAopDemoApplication implements ApplicationRunner {    @Autowired    private ApplicationContext applicationContext;    public static void main(String[] args) {        SpringApplication.run(SpringAopDemoApplication.class, args);    }    @Override    public void run(ApplicationArguments args) throws Exception {        UserService userService = (UserService) applicationContext.getBean("userService");        userService.login();        userService.register();    }}//userService很简略,就定义了两个办法: login register

程序输入是这样的:

执行login共破费了1000毫秒
执行register共破费了2000毫秒
执行run共破费了3009毫秒

剖析:getBean拿到的userService必定是代理之后的对象。那它是什么时候被代理的呢。debug发现在执行bean的初始化时,会调用所有的BeanPostProcessor一一解决。其中有一个特地的Processor是:AnnotationAwareAspectJAutoProxyCreator,而这个processor就是@EnableAspectJAutoProxy引入的。关上注解 @EnableAspectJAutoProxy的源码发现,它的外围是导入了一个AspectJAutoProxyRegistrar(AspectJ主动代理登记员)的类。而这个类的作用就是往注册核心注册AnnotationAwareAspectJAutoProxyCreator这个BeanPostProcessor。是不是和之前说的@EnableJpaRepositories 一模一样。线索找到了,接下来就是解剖它的postProcessAfterInitialization办法了。

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {        if (bean != null) {            Object cacheKey = getCacheKey(bean.getClass(), beanName);            if (this.earlyProxyReferences.remove(cacheKey) != bean) {                return wrapIfNecessary(bean, beanName, cacheKey);            }        }        return bean;}//wrapIfNecessary就是用来生成代理对象的。

持续跟进,终于找到了进行对象代理的罪魁祸首了。就是咱们的ProxyFactory

ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);if (!proxyFactory.isProxyTargetClass()) {  if (shouldProxyTargetClass(beanClass, beanName)) {    proxyFactory.setProxyTargetClass(true);  }  else {    evaluateProxyInterfaces(beanClass, proxyFactory);  }}Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {  proxyFactory.setPreFiltered(true);}return proxyFactory.getProxy(getProxyClassLoader());

这是spring对jdk和cglib动静代理的一个封装类。它的getProxy里的createAopProxy办法是这样的。

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {        if (!IN_NATIVE_IMAGE &&                (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {            Class<?> targetClass = config.getTargetClass();            if (targetClass == null) {                throw new AopConfigException("TargetSource cannot determine target class: " +                        "Either an interface or a target is required for proxy creation.");            }            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {                return new JdkDynamicAopProxy(config);            }            return new ObjenesisCglibAopProxy(config);        }        else {            return new JdkDynamicAopProxy(config);        }}

翻译成自然语言就是optimizeproxyTargetClass,被代理的类没有接口这三个条件其中任何一个成立,就有机会走cglib动静代理,否则都是走jdk动静代理。另外就算判断有机会走cglib的话,如果指标类是接口还是会走jdk动静代理。上面看下sping aop中对于切面的形象

应用ProxyFactory代理对象,是必须要增加告诉的。如果没有告诉就好比代理对象收了钱,然而啥事也没干。一种简略的增加形式是,传入一个MethodInterceptor,实现拦挡。

proxyFactory.addAdvice((MethodInterceptor) invocation -> {      System.out.println("before");      Object result = invocation.proceed();      System.out.println("after");      return result;});

然而更高级的形式就是增加Advisor,能够翻译为参谋,让参谋通知我告诉是什么?spring内置了一个弱小的参谋,名为InstantiationModelAwarePointcutAdvisorImpl,它的getAdvice办法,能够动静的返回不同类型的告诉。详见:ReflectiveAspectJAdvisorFactorygetAdvice办法。后面说的那个BeanPostProcessor正是增加了这个参谋实现了盘绕告诉。

未完待续,更多内容请关注【熟练掌握spring框架】第四篇