关于java:熟练掌握spring框架第三篇

38次阅读

共计 9153 个字符,预计需要花费 23 分钟才能阅读完成。

接上篇【熟练掌握 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
@Component
public 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
@EnableAspectJAutoProxy
public 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 框架】第四篇

正文完
 0