JavaWeb相关知识点整理

一、Thymeleaf服务器端Java模板引擎

​ 模板引擎:jsp、Velocity、Freemarker、Thymeleaf(Spring Boot推荐)

​ 思想:模板引擎作用是写一个页面模板,比如一些值是动态的,写一些表达式,这些值从哪里来?是组装一些数据,找到,然后把模板和数据交给模板引擎,模板引擎按照数据解析表达式填充到指定位置,最终生成想要的内容,写出去。

JavaWeb相关知识点整理_第1张图片

什么是模板引擎?

​ 相对html+js的传统设计,现在很多网站都采用div&css+标签化+ 模块化 的设计 。模板引擎根据一定的语义,将数据填充到模板中,产生最终的HTML页面 模板引擎。主要分两种,客户端引擎和服务端引擎。

​ 客户端渲染: 模板和数据分别传送到客户端,在客户端由JavaScript模板引擎渲染出最终的HTML视图。将模板渲染放置在客户端做,可以降低服务端的压力,并且如果前端内容分别来自多个后台系统,而这些后台的架构各不相同(Java、.NET、Ruby等),则服务器端渲染需要采用不同的技术,模板资源无法共享。

​ 服务端渲染:引擎在服务器端将模板和数据合成,返回最终的html页面,相对于客户端渲染,数据存储更加安全。主要有freemarker、velocity、thymeleaf等。

JavaWeb相关知识点整理_第2张图片

什么是Thymeleaf?

​ Thymeleaf是一个现代服务器端Java模板引擎,适用于Web和独立环境,能够处理HTML,XML,JavaScript,CSS甚至纯文本。Thymeleaf利用最少的IO操作来获得更快的速度,使用thymeleaf模板引擎加快了前后端开发工作的并行运作。Thymeleaf还提供了国际化。Thymeleaf提供了最基础的两个编程API:ServletContextTemplateResolverTemplateEngineServletcontexttemplateresolver负责解析模板、Templateengine使用templateengine process()方法处理模板数据。模板引擎表达式可以从properties文件和WebContext获取属性值从而展示到页面。

​ Thymeleaf的主要目标是提供一种优雅且高度可维护的模板创建方式。为实现这一目标,它以自然模板的概念为基础,将其逻辑注入模板文件,其方式不会影响模板被用作设计原型。这改善了设计沟通,缩小了设计和开发团队之间的差距。

​ thymeleaf最大的优势后缀为html,所以能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用。还有就是thymeleaf可以很好的和spring集成,Spring的官方文档中很多部分的渲染都是用这个。

​ Thymeleaf跟velocity, jsp等有很大的差别,它的语法结构更像angular等在tag的属性里加上一些东西来做渲染的,因为它本身就是一个加入了特别的tag property的Html文件,只是不经过服务端渲染的话,数据和逻辑等东西不会在html中体现而已。

学习链接记录:

https://tutorial.e-learn.cn/read/thymeleaf/standardurlsyntax

https://blog.csdn.net/howard2005/article/details/79346404?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

Thymeleaf的特点

  • 动静结合:Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。
  • 开箱即用:它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、该jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
  • 多方言支持:Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。
  • 与SpringBoot完美整合,SpringBoot提供了Thymeleaf的默认配置,并且为Thymeleaf设置了视图解析器,我们可以像以前操作jsp一样来操作Thymeleaf。代码几乎没有任何区别,就是在模板语法上有区别。

问题:动态页面技术已经有JSP,为什么还要用Thymeleaf?

主要原因包括以下几点:

  1. 使用模块引擎来编写动态页面,让开发人员无法在页面上编写 Java 代码,使得java代码和前端代码绝对的分离。
  2. SpringBoot默认整合Thymeleaf,不需要任何配置直接整合成功,打jar包发布不需要做任何配置。
  3. Thymeleaf相对于其他的模板引擎(如:Freemaker、velocity),有强大的工具支持。
  4. 相对于Jsp页面,执行效率高。

Thymeleaf:

官网:http://www.thymeleaf.org/
1.引入thymeleaf
<!--thymeleaf模板引擎,无需再引入web模块-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.thymeleaf使用&语法

在properties或yml中可以配置thymeleaf模板解析器属性,下面是部分yml格式的属性

  # THYMELEAF (ThymeleafAutoConfiguration)
spring.thymeleaf:
  #开启模板缓存(默认值:true)
  cache: true

  #Check that the template exists before rendering it.
  check-template: true

  #检查模板位置是否正确(默认值:true)
  check-template-location: true

  #开启MVC Thymeleaf视图解析(默认值:true)
  enabled: true

  #模板编码
  encoding: UTF-8

  #要被排除在解析之外的视图名称列表,用逗号分隔
  spring.thymeleaf.excluded-view-names: 

  #要运用于模板之上的模板模式。另见StandardTemplate-ModeHandlers(默认值:HTML5)
  mode: HTML5

  #在构建URL时添加到视图名称前的前缀(默认值:classpath:/templates/)
  prefix: classpath:/templates/

  #在构建URL时添加到视图名称后的后缀(默认值:.html)
  suffix: .html

  #Thymeleaf模板解析器在解析器链中的顺序。默认情况下,它排第一位。顺序从1开始,
  #只有在定义了额外的TemplateResolver Bean时才需要设置这个属性。
  template-resolver-order:

  #可解析的视图名称列表,用逗号分隔
  spring.thymeleaf.view-names:

只要把HTML页面放在classpath/templates/,thymeleaf就能自动渲染;

使用:

​ 1.导入thymeleaf的名称空间(语法提示)

<html lang="en" xmlns:th="http://www.thymeleaf.org">

​ 2.使用thymeleaf语法

​ th:text="${hello}"

3.语法规则

  1. th:test:改变当前元素里面的文本内容

​ th:任意html属性;-------来替换原生属性的值

JavaWeb相关知识点整理_第3张图片

2)表达式语法:

简单表达式:
	可用值表达式(后台设置): ${…} 获取变量值:OGNL
			1)获取对象的属性、调用方法
			2)使用内置的基本对象,如#ctx、#vars、#locale、#request、#response、#session、						#servletContext
			  	#ctx:上下文对象
  				#vars:上下文变量
  				#locale:上下文语言环境
  				#httpServletRequest:(只有在Web上下文)HttpServletRequest对象
  				#httpSession:(只有在Web上下文)HttpSession对象。
				用法:US.

			3)内置的工具对象,如#numbers、#message、#uris、#dates
				#dates: java.util的实用方法。对象:日期格式、组件提取等.
				#calendars:类似于#日期,但对于java.util。日历对象
				#numbers:格式化数字对象的实用方法。
				#strings:字符串对象的实用方法:包含startsWith,将/附加等。
				#objects:实用方法的对象。
				#bools:布尔评价的实用方法。
				#arrays:数组的实用方法。
				#lists:list集合。
				#sets:set集合。
				#maps:map集合。
				#aggregates:实用程序方法用于创建聚集在数组或集合.
				#messages:实用程序方法获取外部信息内部变量表达式,以同样的方式,因为它们将获得使用    							#{…}语法
				#ids:实用程序方法来处理可能重复的id属性(例如,由于迭代)。
			   
			
	所有可用值表达式: *{…} 选择表达式:和${}在功能上一样
		补充:配合 th:object = "${session.user}"
			
Name: Sebastian. Surname: Pepper. Nationality: Saturn.
消息表达式: #{…} 国际化时使用,也可以使用内置的对象,比如date格式化数据 链接表达式: @{…} 用来配合link src href使用的语法,可以链接目录下的静态资源,也可以链接到后端请求处 理接口 片段表达式: ~{…} 用来引入公共部分代码片段,并进行传值操作使用的语法。 字面值: 文本: ‘one text’,’another text’,… 数字: 1,2,1.2,… 布尔: true,false 空值:null 文字标记: something,main,name,one,sometext, main,… 文本操作: 并置:+ 替换:|The name is ${name}| html 链接地址: //渲染后的结果 链接地址: 数学操作: 二元操作:+, - , * , / , % 一元操作: - (负) 布尔操作 一元 : and or 二元 : !,not 比较: 比较:> , < , >= , <= ( gt , lt , ge , le ) 等于:== , != ( eq , ne ) 条件: If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue) 无操作 使用_ 来禁止转义。

https://blog.csdn.net/u014042066/article/details/75614906?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

二、Lambda表达式

​ Lambda 表达式,也可称为闭包,它是 Java 8 发布的新特性。

​ Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

​ 使用 Lambda 表达式可以使代码变的更加简洁紧凑。

新特性

Java8 新增了非常多的特性:

  • Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
  • 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
  • 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
  • Date Time API − 加强对日期与时间的处理。
  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
  • Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

使用匿名内部类存在的问题

当需要启动一个线程去完成任务时,通常会通过Runnable接口来定义任务内容,并使用Thread类来启动线程。

//使用匿名内部类
//面向对象冗余
//创建一个Runnable接口的匿名内部类对象来指定线程要执行的任务内容,再将其交给一个线程来启动
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是匿名内部类");
            }
        }).start();



//Lambda表达式
//函数式编程思想,给一个函数即可,函数相当于java方法
        new Thread(()->{
            System.out.println("我是Lambda");
        }).start();
//Lambda表达式替代匿名内部类

Lambda表达式格式

(参数类型  参数名称) -> {
	代码体;
}
//(参数类型  参数名称)     :参数列表
//代码体;                :方法体
//->                    :分隔参数列表和方法体

Lambda表达式相当于对接口的抽象方法的重写

Lambda基础语法

//语法格式一:无参数 无返回值
	()-> { System.out.println("hello"); }
//语法格式二:有一个参数 无返回值
	(x)-> { System.out.println(x); }
//语法格式三:有一个参数,小括号可省略不写
	x -> { System.out.println(x); }
//语法格式四:有多个参数 有返回值,且方法体中有多条语句
	(x,y) -> {
		System.out.println(x,y);
		return ... ;
	}
//语法格式五:有多个参数 有返回值,方法体中只有一条语句,return和大括号都可以省略不写
	(x,y) -> {
		... ;
	}
//语法格式六:参数列表数据类型可以省略不写(JVM编译器可通过上下文推断出数据类型,即“类型推断”)

学习链接记录:

https://blog.csdn.net/zxzxzx0119/article/details/82392396

https://segmentfault.com/a/1190000018857239

https://www.cnblogs.com/haixiang/p/11029639.html

​ 需要注意 lambda 表达式隐含了 return 关键字,所以在单个的表达式中,我们无需显式的写 return 关键字,但是当表达式是一个语句集合的时候则需要显式添加 return 关键字,并用花括号**{ }** 将多个表达式包围起来,下面看几个例子:

JavaWeb相关知识点整理_第4张图片

lambda 表达式需要”函数式接口“的支持

​ lambda 表达式的使用需要借助于 函数式接口,也就是说只有函数式接口出现地方,我们才可以将其用 lambda 表达式进行简化。那么什么是函数接口?函数接口的定义如下:

函数式接口定义为仅含有一个抽象方法的接口。可以使用注解**@FunctionalInterface**修饰,检查是否为函数式接口。

​ 按照这个定义,我们可以确定一个接口如果声明了两个或两个以上的方法就不叫函数式接口,需要注意一点的是 java 8 为接口的定义引入了默认的方法,我们可以用 default 关键字在接口中定义具备方法体的方法,这个在后面的文章中专门讲解,如果一个接口存在多个默认方法,但是仍然仅含有一个抽象方法,那么这个接口也符合函数式接口的定义。

自定义函数式接口

​ 我们在前面例子中实现的苹果筛选接口就是一个函数式接口(定义如下),正因为如此我们可以将筛选逻辑参数化,并应用 lambda 表达式:

JavaWeb相关知识点整理_第5张图片

​ AppleFilter 仅包含一个抽象方法accept(Apple apple),依照定义可以将其视为一个函数式接口。在定义时我们为该接口添加了@FunctionalInterface注解,用于标记该接口是一个函数式接口,不过该注解是可选的,当添加了该注解之后,编译器会限制了该接口只允许有一个抽象方法,否则报错,所以推荐为函数式接口添加该注解。

jdk 自带的函数式接口

​ jdk 为 lambda 表达式已经内置了丰富的函数式接口,如下表所示(仅列出部分):

JavaWeb相关知识点整理_第6张图片

​ 其中最典型的三个接口是Predicate、Consumer,以及Function,其余接口几乎都是对这三个接口的定制化。

三、Stream API(java.util.stream.*)

​ Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用sql执行的数据库查询。也可以使用Stream API来并行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+

以上的流程转换为 Java 代码为:

List<Integer> transactionsIds = 
widgets.stream()
             .filter(b -> b.getColor() == RED)
             .sorted((x,y) -> x.getWeight() - y.getWeight())
             .mapToInt(Widget::getWeight)
             .sum();

什么是Stream?

流(Stream)到底是什么呢?

是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

“集合讲的是数据,流讲的是计算!”。

注意:

(1)Stream自己不会存储元素。

(2)Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream。

(3)Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream的三个操作步骤

一、创建Stream

从一个数据源,如集合、数组中获取流。

创建Stream的四种方式:
  • ​ 第一种方式:通过 Collection 系列集合提供的方法 stream() 或者 parallelStream()
    Java8 中的 Collection 接口被扩展, 供了两个获取流的方法:
  1. default Stream< E> stream() : 返回一个顺序流

  2. default Stream< E> parallelStream() : 返回一个并行流

  • ​ 第二种方式:由数组创建流
    通过 Arrays中的静态方法 stream() 创建数据源 。
    static < T> Stream< T> stream(T[] array): 返回一个流

重载形式,能够处理对应基本类型的数组:

  1. public static IntStream stream(int[] array)
  2. public static LongStream stream(long[] array)
  3. public static DoubleStream stream(double[] array)
  • ​ 第三种方式:由值创建流

可以使用静态方法 Stream.of(), 通过显示值 创建一个流。它可以接收任意数量的参数。
public static< T> Stream< T> of(T… values) : 返回一个流

  • ​ 第四种方式:由函数创建流,创建无限流。
    可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。
  1. 迭代:public static< T> Stream< T> iterate(final T seed, final UnaryOperator< T> f)
  2. 生成:public static< T> Stream< T> generate(Supplier< T> s)
二、中间操作

一个操作的中间链,对数据源的数据进行操作。

三、终止操作

一个终止操作,执行中间操作链,并产生结果。

JavaWeb相关知识点整理_第7张图片

要注意的是,对流的操作完成后需要进行关闭操作(或者用JAVA7的try-with-resources)。

Stream中间操作–筛选与切片

  • filter:接收Lambda,从流中排除某些操作;
  • limit:截断流,使其元素不超过给定对象
  • skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
  • distinct:筛选,通过流所生成元素的hashCode()和equals()去除重复元素。

Stream中间操作–映射

  • map–接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap–接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

Stream中间操作–排序

  • sorted()–自然排序(Comparable)
  • sorted(Comparator com)–定制排序(Comparator)

终止操作–查找与匹配

  • allMatch–检查是否匹配所有元素
  • anyMatch–检查是否至少匹配一个元素
  • noneMatch–检查是否没有匹配所有元素
  • findFirst–返回第一个元素
  • findAny–返回当前流中的任意元素
  • count–返回流中元素的总个数
  • max–返回流中最大值
  • min–返回流中最小值

归约

Stream API的归约操作可以将流中元素反复结合起来,得到一个值。

收集

collect:将流转换为其他形式,接收一个Collector接口实现 ,用于给Stream中汇总的方法

注意流的关闭

try(final Stream<Integer> integerStream = personList.stream().map(Person::getAge)) {
	final Optional<Integer> minAge = 	integerStream.collect(Collectors.minBy(Integer::compareTo));
	System.out.println(minAge.get());
}
//最好将流的操作放到try-with-resources

学习链接记录:
https://www.jianshu.com/p/8e3b8a483bd8

https://www.jianshu.com/p/998192617b90

四、注解

注解就是对于代码中某些鲜活个体的贴上去的一张标签。简化来讲,注解如同一张标签。

什么是注解(Annotation)?

注释:用文字描述程序,该程序员看的

注解:用来说明程序的一个标识,给计算机看的,也称为元数据,是一种代码级别的说明。它是jdk1.5之后引入的一个特性,是一种特殊的接口,可以用在类、成员变量、方法、包、参数等。

​ 注意:注解本身没有任何功能,只是标识性作用。通过反射去获取到注解,再根据是否有这个注解及其中的一些属性去判断执行哪种业务逻辑。

作用:

  1. 编写文档:通过代码里的注解标识生成API文档(Swagger)
  2. 代码分析:通过注解对代码进行逻辑上的分析(通过反射操作业务)
  3. 编译检查:通过注解让编译器能实现基本的编译检查(Override、SuppressWarnning)

自定义注解

格式:

​ 元注解

public @interface 注解名称 {
	属性列表
}
//注解的本质就是一个继承了 Annotation 接口的接口

属性:

  • 注解属性类型可以有以下列出的类型
  • 1.基本数据类型
  • 2.String
  • 3.枚举类型
  • 4.注解类型
  • 5.Class类型
  • 6.以上类型的一维数组类型

​ 注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

​ 在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。注解中属性可以有默认值,默认值需要用 default 关键值指定。

元注解

​ 元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它annotation类型作说明。Java5.0定义的元注解:

  1. @Target,描述该注解作用范围,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等。当注解未指定Target值时,则此注解可以用于任何元素之上,多个值使用{}包含并用逗号隔开

    public enum ElementType {
       /**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
       TYPE,
     
       /** 标明该注解可以用于字段(域)声明,包括enum实例 */
       FIELD,
     
       /** 标明该注解可以用于方法声明 */
       METHOD,
     
       /** 标明该注解可以用于参数声明 */
       PARAMETER,
     
       /** 标明注解可以用于构造函数声明 */
       CONSTRUCTOR,
     
       /** 标明注解可以用于局部变量声明 */
       LOCAL_VARIABLE,
     
       /** 标明注解可以用于注解声明(应用于另一个注解上)*/
       ANNOTATION_TYPE,
     
       /** 标明注解可以用于包声明 */
       PACKAGE,
     
       /**
        * 标明注解可以用于类型参数声明(1.8新加入)
        * @since 1.8
        */
       TYPE_PARAMETER,
     
       /**
        * 类型使用声明(1.8新加入)
        * @since 1.8
        */
       TYPE_USE
    }
    
  2. @Retention,保留时间,可选值

  3. @Documented,是否会保存到 Javadoc 文档中

  4. @Inherited,是否可以被继承,默认为 false

注解是不支持继承的,因此不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口。

JDK 提供的注解

注解 作用 注意事项
@Override 它是用来描述当前方法是一个重写的方法,在编译阶段对方法进行检查 jdk1.5中它只能描述继承中的重写,jdk1.6中它可以描述接口实现的重写,也能描述类的继承的重写
@Deprecated 它是用于描述当前方法是一个过时的方法
@SuppressWarnings 对程序中的警告去除。

https://blog.csdn.net/shengzhu1/article/details/81271409

https://blog.csdn.net/shengzhu1/article/details/81271409

反射注解的工作原理:

首先,我们通过键值对的形式可以为注解属性赋值,像这样:@Hello(value = “hello”)。

接着,你用注解修饰某个元素,编译器将在编译期扫描每个类或者方法上的注解,会做一个基本的检查,你的这个注解是否允许作用在当前位置,最后会将注解信息写入元素的属性表。

然后,当你进行反射的时候,虚拟机将所有生命周期在 RUNTIME 的注解取出来放到一个 map 中,并创建一个 AnnotationInvocationHandler 实例,把这个 map 传递给它。

最后,虚拟机将采用 JDK 动态代理机制生成一个目标注解的代理类,并初始化好处理器。

那么这样,一个注解的实例就创建出来了,它本质上就是一个代理类,你应当去理解好 AnnotationInvocationHandler 中 invoke 方法的实现逻辑,这是核心。一句话概括就是,通过方法名返回注解属性值

五、反射机制

​ 要想理解反射的原理,首先要了解什么是类型信息。Java让我们在运行时识别对象和类的信息,主要有两种方式:一种是传统的RTTI(Run-Time Type Identification),它假定我们在编译时已经知道了所有的类型信息;另一种是反射机制,它允许我们在运行时发现和使用类的信息。
使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)

框架:半成品软件,可以在此基础上进行开发,但是大多数框架本身不能运行,其中分布式事物类的框架是可以运行的。

反射:是框架的灵魂。将类的各个组成部分封装为其他对象(Class类、Field类、Method类、Constructor类)。

反射的好处:可以在程序运行的过程中去操作对象、字节码文件,不需要重新编译;提高重新扩展性、复用性;解耦。

什么是反射?

​ Java反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。

JavaWeb相关知识点整理_第8张图片

个人理解:反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释。

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。

反射机制的相关类

与Java反射相关的类如下:

类名 用途
Class类 代表类的实体,在运行的Java应用程序中表示类和接口
Field类 代表类的成员变量(成员变量也称为类的属性)
Method类 代表类的方法
Constructor类 代表类的构造方法

反射常用API:

获取反射中的Class对象
在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。
在 Java API 中,获取 Class 类对象有三种方法:

  1. 使用 Class.forName 静态方法。当知道某类的全路径名时,可以使用此方法获取 Class 类对象。用的最多,但可能抛出 ClassNotFoundException 异常。
    Class c1 = Class.forName(“java.lang.String”);
  2. 直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高。这说明任何一个类都有一个隐含的静态成员变量 class。这种方法只适合在编译前就知道操作的 Class。
    Class c2 = String.class;
  3. 通过对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object类型的对象,而我不知道你具体是什么类,用这种方法。
String str = new String("Hello");
Class c3 = str.getClass();

需要注意的是:一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 c1、c2和c3进行 equals 比较,发现都是true。

通过反射创建类对象
通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。

  1. 通过 Class 对象的 newInstance() 方法。
    Class clz = Phone.class;
    Phone phone = (Phone)clz.newInstance();
  2. 通过 Constructor 对象的 newInstance() 方法
Class clz = Phone.class;
Constructor constructor = clz.getConstructor();
Phone phone= (Phone)constructor.newInstance();

通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

Class clz = Phone.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Phone phone = (Phone)constructor.newInstance("华为",6666);

通过反射获取类属性、方法、构造器
我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。

Class clz = Phone.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

输出结果是:
price
而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:

Class clz = Phone.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

输出结果是:
name
price
与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。

学习链接记录:
https://www.jianshu.com/p/1fc45c89e76b

https://www.jianshu.com/p/9be58ee20dee

反射的作用

Java反射描述的是,在运行状态中:

1、对于任意一个类,都能够知道这个类的所有属性和方法

2、对于任意一个类,都能够调用它的任意一个属性和方法

之所以强调属性、方法,是因为属性、方法是开发者对于一个类最关注的两个部分。实际上通过反射,不仅仅可以获知类的属性、方法,还可以获知类的父类、接口、包等信息。

用途

​ 在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。当然,也不是所有的都适合反射,之前就遇到一个案例,通过反射得到的结果与预期不符。阅读源码发现,经过层层调用后在最终返回结果的地方对应用的权限进行了校验,对于没有权限的应用返回值是没有意义的缺省值,否则返回实际值起到保护用户的隐私目的。

​ 反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现;JDBC原生代码注册驱动;hibernate 的实体类;Spring的AOP等等。但是凡事都有两面性,反射使用不当会造成很高的资源消耗。

new对象和反射得到对象的区别

  1. 在使用反射的时候,必须确保这个类已经加载并已经连接了。使用new的时候,这个类可以没有被加载,也可以已经被加载。
  2. new关键字可以调用任何public构造方法,而反射只能调用无参构造方法。
  3. new关键字是强类型的,效率相对较高。 反射是弱类型的,效率低。
  4. 反射提供了一种更加灵活的方式创建对象,得到对象的信息。如Spring 中AOP等的使用,动态代理的使用,都是基于反射的。解耦。

六、动态代理

什么是代理?

​ 从字面意思来看,代理比较好理解,无非就是代为处理的意思。举个例子,你在上大学的时候,总是喜欢逃课。因此,你拜托你的同学帮你答到,而自己却窝在宿舍玩游戏… 你的这个同学恰好就充当了代理的作用,代替你去上课。

代理模式(Proxy/Surrogate)

​ 定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。它是一种对象结构型模式。

​ 动机:通过引入一个新的对象来实现对真实对象的操作或将新的对象作为真实对象的一个替身,这种实现机制即为代理模式。通过引入一个代理对象来间接访问一个对象,这就是代理模式的模式动机。

JavaWeb相关知识点整理_第9张图片

Java 代理模式实现方式,主要有如下五种方法**

  • 静态代理,工程师编辑代理类代码,实现代理模式;在编译期就生成了代理类。

  • 基于 JDK 实现动态代理,通过jdk提供的工具方法Proxy.newProxyInstance动态构建全新的代理类(继承Proxy类,并持有InvocationHandler接口引用 )字节码文件并实例化对象返回。(jdk动态代理是由java内部的反射机制来实例化代理对象,并代理的调用委托类方法)

  • 基于CGlib 动态代理模式 基于继承被代理类生成代理子类,不用实现接口。只需要被代理类是非final 类即可。(cglib动态代理底层是借助asm字节码技术

  • 基于 Aspectj 实现动态代理(修改目标类的字节,织入代理的字节,在程序编译的时候 插入动态代理的字节码,不会生成全新的Class )

  • 基于 instrumentation 实现动态代理(修改目标类的字节码、类装载的时候动态拦截去修改,基于javaagent) -javaagent:spring-instrument-4.3.8.RELEASE.jar (类装载的时候 插入动态代理的字节码,不会生成全新的Class )

1.静态代理

本地和远程有共有的一个接口。

由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

JavaWeb相关知识点整理_第10张图片

静态代理的局限性:

  • 如果同时代理多个类,依然会导致类无限制扩展
  • 如果类中有多个方法,同样的逻辑需要反复实现
2.动态代理

​ 本地有时不一定继承本地和远程共有的接口,在程序运行时,运用反射机制动态创建而成。

JavaWeb相关知识点整理_第11张图片
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强

深入AOP开发的基石—动态代理

AOP

​ 如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。这也就是**AOP(面向切面编程)**的基本原理。

​ AOP(AspectOrientedProgramming):将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码—解耦。

动态代理

JDK动态代理是基于Java的反射机制实现的,主要涉及到java.lang.reflect包中的Proxy和InvocationHandler。 InvocationHandler是一个接口,通过实现这个接口定义一个横切的逻辑。然后通过反射机制调用目标类的方法,这样就能动态的把非业务逻辑和业务逻辑动态的拼接在一起。proxy则利用InvocationHandler创建代理实例,来间接的调用代理的方法。

学习链接记录:

https://blog.csdn.net/u014589856/article/details/79551155

https://www.jianshu.com/p/366f7b8a6200

https://www.cnblogs.com/gonjan-blog/p/6685611.html

https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984

https://baijiahao.baidu.com/s?id=1609057569006094300&wfr=spider&for=pc

​ 其实所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用。

​ 代理对象就是把被代理对象包装一层,在其内部做一些额外的工作,比如用户需要上facebook,而普通网络无法直接访问,网络代理帮助用户先,然后再访问facebook。这就是代理的作用了。

​ 纵观静态代理与动态代理,它们都能实现相同的功能,而我们看从静态代理到动态代理的这个过程,我们会发现其实动态代理只是对类做了进一步抽象和封装,使其复用性和易用性得到进一步提升而这不仅仅符合了面向对象的设计理念,其中还有AOP的身影,这也提供给我们对类抽象的一种参考。关于动态代理与AOP的关系,个人觉得AOP是一种思想,而动态代理是一种AOP思想的实现。

动态代理底层实现

动态代理具体步骤:

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

既然生成代理对象是用的Proxy类的静态方newProxyInstance,那么我们就去它的源码里看一下它到底都做了些什么?

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
         //生成代理类对象
        Class<?> cl = getProxyClass0(loader, intfs);

        //使用指定的调用处理程序获取代理类的构造函数对象
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //如果Class作用域为私有,通过 setAccessible 支持访问
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //获取Proxy Class构造函数,创建Proxy代理实例。
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

常用的动态代理代理场景

远程代理:为不同地理的对象提供局域网代表对象

虚拟代理:根据需要将资源消耗很大的对象进行延迟,真正需要的时候再创建

保护代理:控制对一个对向访问的权限

智能引用代理:提供对目标对象额外的服务

如:

  • 日志集中打印
  • 事务
  • 权限管理
  • AOP

你可能感兴趣的:(JavaWeb相关知识点整理)