Java [SpingBoot 从零单排系列] 前言,以及 Spring 里你需要知道的一些事

战 神 · 2019年05月21日 · 最后由 战 神 回复于 2019年05月23日 · 1673 次阅读

关于我

让我介笑一下我计几,老衲年方 29 岁,来自深圳,毕业于一家三本野鸡大学,徘徊于野鸡与非野鸡公司的一名普通测试开发工程师(鸡你太美?),摸滚打爬 5 年,理想是有一天能进入 BAT,走在面试大公司的路上,失败的次数已经记不清了。但我很清楚,也许努力不一定会成功,但是如果不努力,就一定不会成功。(这碗毒鸡汤我干了,你们随意),我的职业规划? 我也母鸡,我只管拼命学,多积累,拓展自己的视野,学不动了我再看。去年参加了霍格沃兹学院第六期的培训,学习到了许多新鲜的东西,群里的小宝贝都亲切的叫我 :战神。 见过我的兄弟请直接在评论区打个帅,谢谢。兴趣 :唱、跳、RAP 跟打篮球 。

又黑我坤坤..... 🙉

前言

谈到为什么要写这个话题的帖子,是因为近期面试了多家公司,一是对自己当前的一门技术做总结,另一方面是把面试遇到的问题以及想法分享出来。由于当前行业对测开的技术要求已经逐渐上升到 WEB 的工具开发了,所以选了这门技术来进行分享,主题定为【从零单排 SpringBoot】,本帖主要是原理贴,搭建放在下一篇。本篇围绕一个个点来叙述 :Spring 是什么 ,在此也想了很久,尽可能的用通俗易懂又不流水账的方式来叙述,下一篇再讲 : SpringMVC 与 SpringBoot 的差别 以及 SpringBoot 的一些特性

PS :本人现在很少打 DOTA2 了,下棋可以找我,但不搞基,如果有长得漂亮的小姐姐一起学习的话的请直接联系我,微信号 :samchengge

Spring 是什么?

让我们开始进入正题, 假如你是一名熟悉 java 语言的小测试,公司要求你做一个自动化工具平台,Spring 框架是你的不二之选,简单来说,Spring 是一个分层的 JavaSE/EE full-stack(一站式) 轻量级开源框架。

框架的结构以及特征:
这里主要介绍两点,也是我面试中被问到最多的两点特性 :IoC 与 AOP

1 .控制反转(IoC Inversion of Control):

什么叫控制反转?,网上的解释真的太多了,我说一下我的理解吧。当我们在使用一个类型的实例实现某个功能时,需要先 new 出该类型的一个实例,并赋值给我们声明的某个引用变量,这样我们才能够使用该变量进行操作。而 new 和赋值本是我们自己的权限,此处便是将该控制权限反转交给了 spring 框架。IoC 可以理解为是 Spring 的一个容器,IoC 的思想最核心的地方在于,资源不由使用资源的双方管理,而由不使用资源的第三方管理。

Spring 的 IoC 容器提供两种基本的容器类型:BeanFactoryApplicationContext

BeanFactory:基础类型 IoC 容器,提供基本的容器服务,如果没有特殊指定,采用延迟初始化策略,也就是当客户端需要容器中某个对象时,才对该受管理的对象初始化及其依赖注入操作。所以,相对来说,BeanFactory 容器启动较快,所需资源有限,对于资源有限,并且功能要求不严格的场景,使用 BeanFactory 容器是比较合适的。

ApplicationContext:ApplicationContext 是在 BeanFactory 基础之上构建的,是一个比较高级的容器,除了拥有 BeanFactory 的全部功能外,也提供其他高级特性,比如事件发布、国际化信息支持等。ApplicationContext 所管理的对象,默认 ApplicationContext 启动之后全部初始化并绑定完成,所以其启动较慢,占用资源较多。在系统资源充足,并需要提供较多功能的使用场景,ApplicationContext 是一个不错的选择。

关于 IoC 的用法,以 SpringMVC 中的一个例子来说明吧,以 BeanFactory 为例介绍 IoC 的使用 :

一个 javaBean 对象

 /* HelloWolrd类,待依赖注入的类 */

public class HelloWorld {
    private String name;

    public HelloWorld() {
    }

    public HelloWorld(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void hello() {
        System.out.println("hello: " + this.name);
    }
}

SpringMVC 中 springConfig.xml 中对上方 javaBean 的配置项,通过配置 xml 的方式为 bean 对象注入属性值

<!-- 配置bean,属性配置方式 -->
<bean id="helloWorld"  class="com.sam.testspring.HelloWorld" scope="singleton">
    <property name="name" value="spring"/>
</bean>

<!-- 通过构造方法配置bean属性 -->
<bean id="helloWorld2" class="com.sam.testspring.HelloWorld">
    <constructor-arg value="spring2" index="0"></constructor-arg>
</bean>

<bean id="helloWorld3" class="com.sam.testspring.HelloWorld">
    <property name="name">
        <bean class="java.lang.String">
            <constructor-arg value="spring3"/>
        </bean>
    </property>
</bean>

使用 BeanFactory 进行测试,下面我们可以看到通过初始化 BeanFactory,来加载配置文件并创建 HelloWorld 对象,并在控制台可以输出对应的方法值

BeanFactory factory = new XmlBeanFactory(new ClassPathResource("springConfig.xml"));

HelloWorld helloWorld = (HelloWorld) factory.getBean("helloWorld");
helloWorld.hello();

HelloWorld helloWorld2 = factory.getBean("helloWorld2", HelloWorld.class);
helloWorld2.hello();

HelloWorld helloWorld3 = factory.getBean("helloWorld3", HelloWorld.class);
helloWorld3.hello();

为什么需要知道与明白控制反转 ?

  1. SpringMVC 与 SpirngBoot 中 配置文件的依赖都是通过 IoC 来完成的。

  2. 帮助你完成业务上的配置化管理。

  3. Spring 中基于注解的注入方式也是通过 IoC 容器来实现的,后续也会聊到常见的注解使用。

这里谈一下 DI(Dependency injection),即 依赖注入:在容器实例化后,来处理对象依赖关系。IoC 容器:就是具有依赖注入功能的容器

2 . AOP (Aspect Oriented Programming),面向切面编程:

之前读到一句话 : J2EE 框架 Spring 通过动态代理的 Hook 机制优雅地实现了 AOP 编程 。这里先给出三个关键字 :

- Hook:又叫钩子,通常是指对一些方法进行拦截,具体一点来说就是程序运行中,创建代理对象,然后把原始对象替换为代理对象。
      
- 动态代理:代理设计模式的一种实现,即代理类在程序运行时创建的代理方式被称为动态代理。       

- AOP:面向切面编程,是一种编程思想,也就是说面向某个功能模块编程,在代码层面上将该模块与其它有交叉的模块进行隔离。       

那么我们先聊一下,什么叫动态代理,以及 java 中动态代理的机制 :

  • 代理的基本构成

 - 抽象角色:声明真实对象和代理对象的共同接口,这样可在任何使用真实对象的地方都可以使用代理对象。

 - 代理角色:代理对象内部含有真实对象的引用,从而可以在任何时候操作真实对象。代理对象提供一个与真实对象相同的接口,以便可以在任何时候替代真实对象。代理对象通常在客户端调用传递给真实对象之前或之后,执行某个操作,而不是单纯地将调用传递给真实对象,同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

 - 真实角色:即为代理对象所代表的目标对象,代理角色所代表的真实对象,是我们最终要引用的对象。

  下图有三种角色:Subject 抽象角色、RealSubject 真实角色、Proxy 代理角色。其中:Subject 角色负责定义 RealSubject 和 Proxy 角色应该实现的接口;RealSubject 角色用来真正完成业务服务功能;Proxy 角色负责将自身的 request 请求,调用 RealSubject 对应的 request 功能来实现业务功能,自己不真正做业务。

那么我们可以总结出动态代理的一个特点 : 代理对象与被代理对象不存在继承与被继承关系

然后我们用代码块来描述一下动态代理的作用

  • 接口 : Subject
package com.proxy;

public interface Subject {

    void hello();

}
  • 接口实现类 : RealSubject
package com.proxy;

public class RealSubject implements Subject{

    public void hello() {
        System.out.println("大杂好,我系古天乐,系兄弟揍来砍死我");
    }
}
  • InvocationHandler :是 proxy 代理实例的调用处理程序实现的一个接口,每一个动态代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实例都关联到了一个 handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler 这个接口的 invoke 方法来进行调用。我们来看看 InvocationHandler 这个接口的唯一一个方法 invoke 方法以及它的入参 :

    • proxy:  指代我们所代理的那个真实对象
    • method:  指代的是我们所要调用真实对象的某个方法的 Method 对象
    • args:  指代的是调用真实对象某个方法时接受的参数
package com.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class Handler implements InvocationHandler {

    //要代理的真实对象 ,即 RealSubject 这个类
    private Object subject ;


    public Handler(Object subject){
        this.subject = subject;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("大家好 ,我系渣渣辉,我在古天乐前面玩了贪玩蓝月 ");

        method.invoke(subject,args);

        System.out.println("大家好,我系大哥成龙,我在古天乐后面玩了贪玩蓝月");
        return null;
    }
}
  • 接着我们完成我们的代理测试,

首先创建我们要代理的对象 realSubject ,然后把代理对象传入 hanler 中,然后我们我们再来看 Proxy 类,Proxy 就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是 newProxyInstance 方法。

方法原文: public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

- loader :第一个参数 ,一个 classloader 对象,定义了由哪个 classloader 对象对生成的代理类进行加载,我们传入 handler.getClass().getClassLoader() ,我们这里使用 handler 这个类的 ClassLoader 对象来加载我们的代理对象

- interfaces:第二个参数,一个 interface 对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法,我们传入 realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了

- h:一个 InvocationHandler 对象,表示的是当动态代理对象调用方法的时候会关联到哪一个 InvocationHandler 对象上,并最终由其调用,第三个参数 handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上

package com.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class TestProxy {

    public static void main(String[] args) {



        Subject realSubject = new RealSubject();


        InvocationHandler invocationHandler = new Handler(realSubject);

        Subject subject = (Subject)Proxy.newProxyInstance(invocationHandler.getClass().getClassLoader(),realSubject.getClass().getInterfaces(),invocationHandler);

        System.out.println(subject.getClass().getName());

        subject.hello();

    }
}

这里最终我们看到控制台输出的内容 :
一是 com.sun.proxy.$Proxy0,这是 System.out.println(subject.getClass().getName()) 这句打印的,
这是通过 Proxy.newProxyInstance 创建的代理对象是在 jvm 运行时动态生成的一个对象,它并不是我们的 InvocationHandler 类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以 $ 开头,proxy 为中,最后一个数字表示对象的标号。

二是前置动作 渣渣辉 do something** 跟后置动作 成龙 do something** 都出来了,这样我们就完成了对 古天乐快来砍死我 这个事务流程的前后置处理,是不是看起来有点像 jmeter 里面的前置处理器跟后置处理器呢?这其实就是 AOP 中的一个理念。

明白动态代理的概念之后,我们举个小例子后再来看一下 AOP 在实际业务中的应用:

先只关注红色标注的内容,首先是我们定义了对 tools.service.flow 这个包名下的所有类进行切片,对这个包下
所有的服务的动作进行统一的管理,
Object result 参数是切片下类方法的返回对象,假如返回对象为 Result 对象的话,我们对结果进行切片处理,
比如我们拿到的结果属于 Result 对象,对对象中的一些数据进行处理、更改并返回 或者 对改持久化处理等等。

结尾

本篇针对 Spring 中 IoC 跟 AOP 的两个特性做了一个叙述,当然 Spring 里面还有许多其他的特性,比如Spring DAO 、Spring ORM等等, 单独把这两个拎出来是因为面试涉及得太多了,特别是我近期一个面试,
面试官问我: 你知道 Spring 动态代理吗? 当然可能由于面了三轮算法已经脑抽了,根本不知道什么跟什么,于是就说了不知道。。事后再看了一下,原来是 Spring AOP 的实现方式,
同时在项目中用的非常多。最后,欢迎大家纠正本文的问题或者本人的问题,后面我会继续总结 Spring 里面相关的知识点. 🍔 啦啦啦啦

共收到 6 条回复 时间 点赞
战 神 关闭了讨论 05月22日 08:53
战 神 重新开启了讨论 05月22日 12:16

帅帅帅帅帅帅帅帅帅帅😎

我是来拆台的。老傅的战神是自封的。没有同学主动称之为战神。😏 😏 😏 😏

鸣人哥哥太不厚道了🐴

Harry 回复

谢谢电总对我的认可😍

帅炸天了

真菜鸟 回复

你真的是很笨呀 8035 号王鹏笨

需要 登录 後方可回應,如果你還沒有帳號按這裡 注册