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

战 神 · May 21, 2019 · Last by 战 神 replied at May 23, 2019 · 1208 hits

关于我

让我介笑一下我计几,老衲年方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 条回复 时间 点赞
战 神 关闭了讨论 22 May 08:53
战 神 重新开启了讨论 22 May 12:16

帅帅帅帅帅帅帅帅帅帅😎

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

战 神 #5 · May 22, 2019 作者

鸣人哥哥太不厚道了🐴

战 神 #6 · May 22, 2019 作者
Harry 回复

谢谢电总对我的认可😍

帅炸天了

战 神 #9 · May 23, 2019 作者
真菜鸟 回复

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

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up