作用域 订阅
作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。对于对象而言(其他也是一样的),在main函数中,对象的作用域为他所在的最近的一对花括号内。在后花括号处析构函数被调用;全局的对象的作用域为声明之后的整个文件,析构函数在最后被调用。另外,临时产生的对象在使用完后立即会被析构。 展开全文
作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。对于对象而言(其他也是一样的),在main函数中,对象的作用域为他所在的最近的一对花括号内。在后花括号处析构函数被调用;全局的对象的作用域为声明之后的整个文件,析构函数在最后被调用。另外,临时产生的对象在使用完后立即会被析构。
信息
外文名
scope
属    于
程序设计语言中非常重要
中文名
作用域
目    的
减少名字冲突
作用域程序设计概念
作用域(scope)作用域在许多程序设计语言中非常重要。通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。对于对象而言(其他也是一样的),在main函数中,对象的作用域为他所在的最近的一对花括号内。在后花括号处析构函数被调用;全局的对象的作用域为声明之后的整个文件,析构函数在最后被调用。另外,临时产生的对象在使用完后立即会被析构。下面是一个简单的例子。#include using namespace std;class X {public:X() {cout << "X::X()\n";}~X() {cout << "X::~X()\n";}};X f1(X x1) //完全不使用引用{cout << "f1(X f)\n";return x1;}X& f2(X& x2) //完全使用引用{cout << "f2(X f)\n";return x2;}X Globle_X; //全局版本int main(){cout << "--------\n";{X Local_X; //局部版本cout << "--------\n";}cout << "--------\n";X Normal_X;f1(Normal_X);cout << "--------\n";f2(Normal_X);cout << "--------\n";}程序执行结果为:X::X()--------X::X()--------X::~X()--------X::X()f1(X f)X::~X()X::~X()--------f2(X f)--------X::~X()大家对照着就能看出来了……
收起全文
精华内容
下载资源
问答
  • // 全局 函数作用 块级作用域(在各自的块干自己的事情 不影响) var a = '全局'; (function(){ var a = '局部' console.log(a); }()) console.log(a) let a = &...
  • JavaScript 作用域作用域作用域作用域作用域 作用域就是变量与函数的可访问范围,作用域控制着变量与函数的可见性和生命周期。换句话说,作用域决定了代码区块中变量和其他资源的可见性。 function fun() { ...
  • 给大家分享了通过函数作用域和块级作用域看javascript的作用域链的相关知识点内容,有兴趣的朋友参考学习下。
  • 这实际上是由两种作用域工作模型导致的,作用域分为词法作用域和动态作用域,分清这两种作用域模型就能够对变量查找过程有清晰的认识。本文是深入理解javascript作用域系列第二篇——词法作用域和动态作用域 词法...
  • 作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域作用域链的工作原理。今天这篇文章对JavaScript作用域作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript。任何程序...
  • 作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域作用域链的工作原理。今天这篇文章对JavaScript作用域作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript。 ...
  • 作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域作用域链的工作原理。今天这篇文章对JavaScript作用域作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript。 ...
  • 主要和大家一起聊一聊JavaScript作用域作用域链,什么是JavaScript作用域作用域链,感兴趣的小伙伴们可以参考一下
  • 作用域 本篇主要总结了作用域的定义、作用域作用域链的规则,全局变量和局部变量的关系,利用思维导图代替纯文字描述,提高收获,加油!

    作用域

    作用域的篇幅不会太长,作为自己对Js总结的第三篇文章,主要是承上启下。
    之后会涉及到执行上下文,闭包等相关专题,为了避免内容过多,作用域这一部分单独总结。

    目录

    前言

    在这里插入图片描述

    JavaScript内功系列:

    1. this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一)
    2. 从原型到原型链,修炼JavaScript内功这篇文章真的不能错过!系列(二)
    3. 本文

    一、作用域的定义

    一张导图概括本节内容

    在这里插入图片描述

    注意:除了作用域,在此送大家2020最新企业级 Vue3.0/Js/ES6/TS/React/Node等实战视频教程,点击此处免费获取,小白勿进哦

    1.1 常见的解释

    1. 一段程序代码中所用到的名字并不总是有效,而限定它的可用性的范围就是这个名字的作用域;
    2. 作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限;
    3. 通俗的讲作用域就是一套规则,用于确定在何处以及如何查找某个变量的规则
    function func(){
    	var a = 100;
    	console.log(a); // 100
    }
    console.log(a) // a is not defined a变量并不是任何地方都可以被找到的
    

    1.2 JavaScript中作用域工作模型

    JavaScript 采用是词法作用域(lexical scoping),也就是静态作用域:

    • 函数的作用域在函数定义的时候就决定了

    与之对应的还有一个动态作用域:

    • 函数的作用域是在函数调用的时候才决定的;

    1.3 全局变量和局部变量

    根据定义变量的方式又可以分为:

    局部变量:只能在函数中访问,该函数外不可访问;

    • 定义在函数中的变量
    function fn(){
    	var name = '余光';
    	console.log(name);
    }
    console.log(name); // ?
    fn(); // ?
    

    全局:任何地方都能访问到的对象拥有全局作用域。

    • 函数外定义的变量
    • 所有末定义直接赋值的变量自动声明为拥有全局作用域
    var a = 100;
    console.log('a1-',a);
    
    function fn(){
    	a = 1000;
    	console.log('a2-',a);
    }
    console.log('a3-',a);
    fn();
    console.log('a4-',a);
    

    注意:在ES6之后又提出了块级作用域,它们之间的区别我们之后再来讨论。

    在这里插入图片描述

    二、理解作用域

    根据第一节的描述,我们一一验证一下

    2.1 理解词法作用域

    var value = 1;
    function foo() {
        console.log(value);
    }
    function bar() {
        var value = 2;
        foo();
    }
    bar();
    

    我们结合定义去分析:

    • 执行bar函数,函数内部形成了局部作用域;
    • 声明value变量,并赋值2
    • 执行foo函数,函数foo的作用域内没有value这个变量,它会向外查找
    • 根据词法作用域的规则,函数定义时,foo的外部作用域为全局作用域
    • 打印结果是1

    如果是动态作用域的话:结果就是2,不知道你是否想明白了?

    2.2 全局变量

    var str = '全局变量';
    function func(){
    	console.log(str+1);
    	function childFn(){
    		console.log(str+2);
    		function fn(){
    			console.log(str+3);
    		};
    		fn();
    	};
    	childFn();
    }
    func();
    // 全局变量1
    // 全局变量2
    // 全局变量3
    

    再来分析下面的代码:

    var a = 100;
    function fn(){
    	a = 1000;
    	console.log('a1-',a);
    }
    console.log('a2-',a);
    fn();
    console.log('a3-',a);
    // a2- 100 // 在当前作用域下查找变量a => 100
    // a1- 1000 // 函数执行时,全局变量a已经被重新赋值
    // a3- 1000 // 全局变量a => 1000
    

    2.3 局部作用域

    局部作用域一般只在固定的代码片段内可访问到,最常见的就是以函数为单位的:

    function fn(){
        var name="余光";
        function childFn(){
            console.log(name);
        }
        childFn(); // 余光
    }
    console.log(name); // name is not defined
    

    三、作用域链

    3.1 当查找变量的时候都发生了什么?

    • 会先从当前上下文的变量对象中查找;
    • 如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找;
    • 一直找到全局上下文的变量对象,也就是全局对象;
    • 作用域链的顶端就是全局对象;

    这样由多个执行上下文的变量对象构成的链表就叫做作用域链,从某种意义上很类似原型和原型链。

    3.2 作用域链和原型继承查找时的区别:

    • 查找一个普通对象的属性,但是在当前对象和其原型中都找不到时,会返回undefined
    • 查找的属性在作用域链中不存在的话就会抛出ReferenceError

    3.3 作用域嵌套

    既然每一个函数就可以形成一个作用域(词法作用域 || 块级作用域),那么当然也会存在多个作用域嵌套的情况,他们遵循这样的查询规则:

    • 内部作用域有权访问外部作用域;
    • 外部作用域无法访问内部作用域;(真是是这样吗?)
    • 兄弟作用域不可互相访问;

    在《你不知道的Js》中,希望读者可以将作用域的嵌套和作用域链想象成这样:

    在这里插入图片描述

    四、思考与总结

    4.1 总结

    在这里插入图片描述

    4.2 思考

    最后,让我们看一个《JavaScript权威指南》中的两段代码:

    var scope = "global scope";
    function checkscope1(){
        var scope = "local scope";
        function f(){
            return scope;
        }
        return f(); // 注意
    }
    checkscope1();
    
    var scope = "global scope";
    function checkscope2(){
        var scope = "local scope";
        function f(){
            return scope;
        }
        return f;
    }
    checkscope2()();
    

    两段代码的结果都是"local scope",书中的回答是:JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。

    但是它们内部经历的事情是一样的吗?

    参考

    写在最后

    JavaScript内功基础部分已经总结到第三篇了,本系列大约会有15篇文章,都是我们在面试最高频的,但工作中常常被忽略的。

    JavaScript内功系列:

    1. this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一)
    2. 从原型到原型链,修炼JavaScript内功这篇文章真的不能错过!系列(二)
    3. 本文
    4. 下一篇预发:执行上下文

    关于我

    • 花名:余光
    • 沉迷JS,水平有限,虚心学习中

    其他沉淀

    如果您看到了最后,不妨收藏、点赞、评论一下吧!!!
    持续更新,您的三连就是我最大的动力,虚心接受大佬们的批评和指点,共勉!

    展开全文
  • js代码-块作用域(单纯的块作用域和函数块作用域
  • 作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域作用域链的工作原理。今天这篇文章对JavaScript作用域作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript。 一、...
  • Android 10适配要点,作用域存储

    万次阅读 多人点赞 2020-04-14 08:42:48
    在Android 10众多的行为变更当中,有一点是非常值得引起我们重视的,那就是作用域存储。这个新功能直接颠覆了长久以来我们一直惯用的外置存储空间的使用方式,因此大量App都将面临着较多代码模块的升级。然而,对于...

    本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新。

    距离Android 10系统正式发布已经过去大半年左右的时间了,你的应用程序已经对它进行适配了吗?

    在Android 10众多的行为变更当中,有一点是非常值得引起我们重视的,那就是作用域存储。这个新功能直接颠覆了长久以来我们一直惯用的外置存储空间的使用方式,因此大量App都将面临着较多代码模块的升级。

    然而,对于作用域存储这个新功能,官方的资料并不多,很多人也没有搞明白它的用法。另外它也不属于《第一行代码》现有的知识架构体系,虽然我有想过在第3版中加入这部分内容的讲解,但几经思考之后还是决定以一讲单独文章的方式来讲解这部分内容,也算是作为《第一行代码 第3版》的内容扩展吧。

    本篇文章对作用域存储进行了比较全面的解析,相信看完之后你将能够轻松地完成Android 10作用域存储的适配升级。

    理解作用域存储

    Android长久以来都支持外置存储空间这个功能,也就是我们常说的SD卡存储。这个功能使用得极其广泛,几乎所有的App都喜欢在SD卡的根目录下建立一个自己专属的目录,用来存放各类文件和数据。

    那么这么做有什么好处吗?我想了一下,大概有两点吧。第一,存储在SD卡的文件不会计入到应用程序的占用空间当中,也就是说即使你在SD卡存放了1G的文件,你的应用程序在设置中显示的占用空间仍然可能只有几十K。第二,存储在SD卡的文件,即使应用程序被卸载了,这些文件仍然会被保留下来,这有助于实现一些需要数据被永久保留的功能。

    然而,这些“好处”真的是好处吗?或 许对于开发者而言这算是好处吧,但对于用户而言,上述好处无异于一些流氓行为。因为这会将用户的SD卡空间搞得乱糟糟的,而且即使我卸载了一个完全不再使用的程序,它所产生的垃圾文件却可能会一直保留在我的手机上。

    另外,存储在SD卡上的文件属于公有文件,所有的应用程序都有权随意访问,这也对数据的安全性带来了很大的挑战。

    为了解决上述问题,Google在Android 10当中加入了作用域存储功能。

    那么到底什么是作用域存储呢?简单来讲,就是Android系统对SD卡的使用做了很大的限制。从Android 10开始,每个应用程序只能有权在自己的外置存储空间关联目录下读取和创建文件,获取该关联目录的代码是:context.getExternalFilesDir()。关联目录对应的路径大致如下:

    /storage/emulated/0/Android/data/<包名>/files
    

    将数据存放到这个目录下,你将可以完全使用之前的写法来对文件进行读写,不需要做任何变更和适配。但同时,刚才提到的那两个“好处”也就不存在了。这个目录中的文件会被计入到应用程序的占用空间当中,同时也会随着应用程序的卸载而被删除。

    那么有些朋友可能会问了,我就是需要访问其他目录该怎么办呢?比如读取手机相册中的图片,或者向手机相册中添加一张图片。为此,Android系统针对文件类型进行了分类,图片、音频、视频这三类文件将可以通过MediaStore API来进行访问,而其他类型的文件则需要使用系统的文件选择器来进行访问。

    另外,我们的应用程序向媒体库贡献的图片、音频或视频,将会自动拥有其读写权限,不需要额外申请READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限。而如果你要读取其他应用程序向媒体库贡献的图片、音频或视频,则必须要申请READ_EXTERNAL_STORAGE权限才行。WRITE_EXTERNAL_STORAGE权限将会在未来的Android版本中废弃。

    好了,关于作用域存储的理论知识就先讲到这里,相信你已经对它有了一个基本的了解了,那么接下来我们就开始上手操作吧。

    我一定要升级吗?

    一定会有很多朋友关心这个问题,因为每当适配升级面临着需要更改大量代码的时候,大多数人的第一想法都是能不升就不升,或者能晚升就晚升。而在作用域存储这个功能上面,恭喜大家,暂时确实是可以不用升级的。

    目前Android 10系统对于作用域存储适配的要求还不是那么严格,毕竟之前传统外置存储空间的用法实在是太广泛了。如果你的项目指定的targetSdkVersion低于29,那么即使不做任何作用域存储方面的适配,你的项目也可以成功运行到Android 10手机上。

    而如果你的targetSdkVersion已经指定成了29,也没有关系,假如你还不想进行作用域存储的适配,只需要在AndroidManifest.xml中加入如下配置即可:

    <manifest ... >
      <application android:requestLegacyExternalStorage="true" ...>
        ...
      </application>
    </manifest>
    

    这段配置表示,即使在Android 10系统上,仍然允许使用之前遗留的外置存储空间的用法来运行程序,这样就不用对代码进行任何修改了。当然,这只是一种权宜之计,在未来的Android系统版本中,这段配置随时都可能会失效(Android 11中已强制启用作用域存储,这段配置在Android 11当中已不再有效)。因此,我们还是非常有必要现在就来学习一下,到底该如何对作用域存储进行适配。

    另外,本篇文章中演示的所有示例,都可以到ScopedStorageDemo这个开源库中找到其对应的源码。

    开源库地址是:https://github.com/guolindev/ScopedStorageDemo

    获取相册中的图片

    首先来学习一下如何在作用域存储当中获取手机相册里的图片。注意,虽然本篇文章中我是以图片来举例的,但是获取音频、视频的用法也是基本相同的。

    不同于过去可以直接获取到相册中图片的绝对路径,在作用域存储当中,我们只能借助MediaStore API获取到图片的Uri,示例代码如下:

    val cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, "${MediaStore.MediaColumns.DATE_ADDED} desc")
    if (cursor != null) {
        while (cursor.moveToNext()) {
            val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
            val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
            println("image uri is $uri")
        }
    	cursor.close()
    }
    

    上述代码中,我们先是通过ContentResolver获取到了相册中所有图片的id,然后再借助ContentUris将id拼装成一个完整的Uri对象。一张图片的Uri格式大致如下所示:

    content://media/external/images/media/321
    

    那么有些朋友可能会问了,获取到了Uri之后,我又该怎样将这张图片显示出来呢?这就有很多种办法了,比如使用Glide来加载图片,它本身就支持传入Uri对象来作为图片路径:

    Glide.with(context).load(uri).into(imageView)
    

    而如果你没有使用Glide或其他图片加载框架,想在不借助第三方库的情况下直接将一个Uri对象解析成图片,可以使用如下代码:

    val fd = contentResolver.openFileDescriptor(uri, "r")
    if (fd != null) {
        val bitmap = BitmapFactory.decodeFileDescriptor(fd.fileDescriptor)
    	fd.close()
        imageView.setImageBitmap(bitmap)
    }
    

    上述代码中,我们调用了ContentResolver的openFileDescriptor()方法,并传入Uri对象来打开文件句柄,然后再调用BitmapFactory的decodeFileDescriptor()方法将文件句柄解析成Bitmap对象即可。

    Demo效果:

    这样我们就将获取相册中图片的方式掌握了,并且这种方式在所有的Android系统版本中都适用。

    那么接下来,我们开始学习如何将一张图片添加到相册。

    将图片添加到相册

    将一张图片添加到手机相册要相对稍微复杂一点,因为不同系统版本之间的处理方式是不太一样的。

    我们还是通过一段代码示例来直观地学习一下,代码如下所示:

    fun addBitmapToAlbum(bitmap: Bitmap, displayName: String, mimeType: String, compressFormat: Bitmap.CompressFormat) {
        val values = ContentValues()
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
        values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
        } else {
            values.put(MediaStore.MediaColumns.DATA, "${Environment.getExternalStorageDirectory().path}/${Environment.DIRECTORY_DCIM}/$displayName")
        }
        val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        if (uri != null) {
            val outputStream = contentResolver.openOutputStream(uri)
            if (outputStream != null) {
                bitmap.compress(compressFormat, 100, outputStream)
    			outputStream.close()
            }
        }
    }
    

    这段代码演示了如何将一个Bitmap对象添加到手机相册当中,我来简单解释一下。

    想要将一张图片添加到手机相册,我们需要构建一个ContentValues对象,然后向这个对象中添加三个重要的数据。一个是DISPLAY_NAME,也就是图片显示的名称,一个是MIME_TYPE,也就是图片的mime类型。还有一个是图片存储的路径,不过这个值在Android 10和之前的系统版本中的处理方式不一样。Android 10中新增了一个RELATIVE_PATH常量,表示文件存储的相对路径,可选值有DIRECTORY_DCIM、DIRECTORY_PICTURES、DIRECTORY_MOVIES、DIRECTORY_MUSIC等,分别表示相册、图片、电影、音乐等目录。而在之前的系统版本中并没有RELATIVE_PATH,所以我们要使用DATA常量(已在Android 10中废弃),并拼装出一个文件存储的绝对路径才行。

    有了ContentValues对象之后,接下来调用ContentResolver的insert()方法即可获得插入图片的Uri。但仅仅获得Uri仍然是不够的,我们还需要向该Uri所对应的图片写入数据才行。调用ContentResolver的openOutputStream()方法获得文件的输出流,然后将Bitmap对象写入到该输出流当中即可。

    以上代码即可实现将Bitmap对象存储到手机相册当中,那么有些朋友可能会问了,如果我要存储的图片并不是Bitmap对象,而是一张网络上的图片,或者是当前应用关联目录下的图片该怎么办呢?

    其实方法都是相似的,因为不管是网络上的图片还是关联目录下的图片,我们都能获取到它的输入流,只要不断读取输入流中的数据,然后写入到相册图片所对应的输出流当中就可以了,示例代码如下:

    fun writeInputStreamToAlbum(inputStream: InputStream, displayName: String, mimeType: String) {
        val values = ContentValues()
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
        values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
        } else {
            values.put(MediaStore.MediaColumns.DATA, "${Environment.getExternalStorageDirectory().path}/${Environment.DIRECTORY_DCIM}/$displayName")
        }
        val bis = BufferedInputStream(inputStream)
        val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        if (uri != null) {
            val outputStream = contentResolver.openOutputStream(uri)
            if (outputStream != null) {
                val bos = BufferedOutputStream(outputStream)
                val buffer = ByteArray(1024)
                var bytes = bis.read(buffer)
                while (bytes >= 0) {
                    bos.write(buffer, 0 , bytes)
                    bos.flush()
                    bytes = bis.read(buffer)
                }
                bos.close()
            }
        }
        bis.close()
    }
    

    这段代码中只是将输入流和输出流的部分重新编写了一下,其他部分和之前存储Bitmap的代码是完全一致的,相信很好理解。

    Demo效果:

    好了,这样我们就将相册图片的读取和存储问题都解决了,下面我们来探讨另外一个常见的需求,如何将文件下载到Download目录。

    下载文件到Download目录

    执行文件下载操作是一个很常见的场景,比如说下载pdf、doc文件,或者下载APK安装包等等。在过去,这些文件我们通常都会下载到Download目录,这是一个专门用于存放下载文件的目录。而从Android 10开始,我们已经不能以绝对路径的方式访问外置存储空间了,所以文件下载功能也会受到影响。

    那么该如何解决呢?主要有以下两种方式。

    第一种同时也是最简单的一种方式,就是更改文件的下载目录。将文件下载到应用程序的关联目录下,这样不用修改任何代码就可以让程序在Android 10系统上正常工作。但使用这种方式,你需要知道,下载的文件会被计入到应用程序的占用空间当中,同时如果应用程序被卸载了,该文件也会一同被删除。另外,存放在关联目录下的文件只能被当前的应用程序所访问,其他程序是没有读取权限的。

    以上几个限制条件如果不能满足你的需求,那么就只能使用第二种方式,对Android 10系统进行代码适配,仍然将文件下载到Download目录下。

    其实将文件下载到Download目录,和向相册中添加一张图片的过程是差不多的,Android 10在MediaStore中新增了一种Downloads集合,专门用于执行文件下载操作。但由于每个项目下载功能的实现都各不相同,有些项目的下载实现还十分复杂,因此怎么将以下的示例代码融合到你的项目当中是你自己需要思考的问题。

    fun downloadFile(fileUrl: String, fileName: String) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            Toast.makeText(this, "You must use device running Android 10 or higher", Toast.LENGTH_SHORT).show()
            return
        }
        thread {
    		try {
    			val url = URL(fileUrl)
    			val connection = url.openConnection() as HttpURLConnection
    			connection.requestMethod = "GET"
    			connection.connectTimeout = 8000
    			connection.readTimeout = 8000
    			val inputStream = connection.inputStream
    			val bis = BufferedInputStream(inputStream)
    			val values = ContentValues()
    			values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
    			values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
    			val uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
    			if (uri != null) {
    				val outputStream = contentResolver.openOutputStream(uri)
    				if (outputStream != null) {
    					val bos = BufferedOutputStream(outputStream)
    					val buffer = ByteArray(1024)
    					var bytes = bis.read(buffer)
    					while (bytes >= 0) {
    						bos.write(buffer, 0 , bytes)
    						bos.flush()
    						bytes = bis.read(buffer)
    					}
    					bos.close()
    				}
    			}
    			bis.close()
    		} catch(e: Exception) {
    			e.printStackTrace()
    		}
        }
    }
    

    这段代码总体来讲还是比较好理解的,主要就是添加了一些Http请求的代码,并将MediaStore.Images.Media改成了MediaStore.Downloads,其他部分几乎是没有变化的,我就不再多加解释了。

    注意,上述代码只能在Android 10或更高的系统版本上运行,因为MediaStore.Downloads是Android 10中新增的API。至于Android 9及以下的系统版本,请你仍然使用之前的代码来进行文件下载。

    Demo效果:


    使用文件选择器

    如果我们要读取SD卡上非图片、音频、视频类的文件,比如说打开一个PDF文件,这个时候就不能再使用MediaStore API了,而是要使用文件选择器。

    但是,我们不能再像之前的写法那样,自己写一个文件浏览器,然后从中选取文件,而是必须要使用手机系统中内置的文件选择器。示例代码如下:

    const val PICK_FILE = 1
    
    private fun pickFile() {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
        intent.addCategory(Intent.CATEGORY_OPENABLE)
        intent.type = "*/*"
        startActivityForResult(intent, PICK_FILE)
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            PICK_FILE -> {
                if (resultCode == Activity.RESULT_OK && data != null) {
                    val uri = data.data
                    if (uri != null) {
                        val inputStream = contentResolver.openInputStream(uri)
    					// 执行文件读取操作
                    }
                }
            }
        }
    }
    

    这里在pickFile()方法当中通过Intent去启动系统的文件选择器,注意Intent的action和category都是固定不变的。而type属性可以用于对文件类型进行过滤,比如指定成image/就可以只显示图片类型的文件,这里写成/*表示显示所有类型的文件。注意type属性必须要指定,否则会产生崩溃。

    然后在onActivityResult()方法当中,我们就可以获取到用户选中文件的Uri,之后通过ContentResolver打开文件输入流来进行读取就可以了。

    Demo效果:


    第三方SDK不支持作用域存储怎么办?

    阅读完了本篇文章之后,相信你对Android 10作用域存储的用法和适配基本上都已经掌握了。然而我们在实际的开发工作当中还可能会面临一个非常头疼的问题,就是我自己的代码当然可以进行适配,但是项目中使用的第三方SDK还不支持作用域存储该怎么办呢?

    这个情况确实是存在的,比如我之前使用的七牛云SDK,它的文件上传功能要求你传入的就是一个文件的绝对路径,而不支持传入Uri对象,大家应该也会碰到类似的问题。

    由于我们是没有权限修改第三方SDK的,因此最简单直接的办法就是等待第三方SDK的提供者对这部分功能进行更新,在那之前我们先不要将targetSdkVersion指定到29,或者先在AndroidManifest文件中配置一下requestLegacyExternalStorage属性。

    然而如果你不想使用这种权宜之计,其实还有一个非常好的办法来解决此问题,就是我们自己编写一个文件复制功能,将Uri对象所对应的文件复制到应用程序的关联目录下,然后再将关联目录下这个文件的绝对路径传递给第三方SDK,这样就可以完美进行适配了。这个功能的示例代码如下:

    fun copyUriToExternalFilesDir(uri: Uri, fileName: String) {
        val inputStream = contentResolver.openInputStream(uri)
        val tempDir = getExternalFilesDir("temp")
        if (inputStream != null && tempDir != null) {
            val file = File("$tempDir/$fileName")
            val fos = FileOutputStream(file)
            val bis = BufferedInputStream(inputStream)
            val bos = BufferedOutputStream(fos)
            val byteArray = ByteArray(1024)
            var bytes = bis.read(byteArray)
            while (bytes > 0) {
                bos.write(byteArray, 0, bytes)
                bos.flush()
                bytes = bis.read(byteArray)
            }
            bos.close()
            fos.close()
        }
    }
    

    好的,关于Android 10作用域存储的重要知识点就讲到这里,相信你已经可以完全掌握了。下篇文章中我们会继续学习Android 10适配,讲一讲深色主题的功能,详见链见: Android 10适配要点,深色主题

    注:本篇文章中演示的所有示例,都可以到ScopedStorageDemo这个开源库中找到其对应的源码。

    开源库地址是:https://github.com/guolindev/ScopedStorageDemo

    关于作用域存储在Android 11上的适配,请参考 Android 11新特性,Scoped Storage又有了新花样


    本篇文章是《第一行代码 第3版》的配套扩展文章,目前《第一行代码 第3版》已经出版,Kotlin、Jetpack、MVVM,你所关心的知识点都在这里,详情点击这里查看

    京东购买地址

    当当购买地址

    天猫购买地址


    关注我的技术公众号,每天都有优质技术文章推送。

    微信扫一扫下方二维码即可关注:

    展开全文
  • C++ 变量根据定义的位置的不同的生命周期,具有不同的作用域作用域可分为 6 种:全局作用域,局部作用域,语句作用域,类作用域,命名空间作用域和文件作用域。 从作用域看: 全局变量具有全局作用域。全局变量...

    C++ 变量根据定义的位置的不同的生命周期,具有不同的作用域,作用域可分为 6 种:全局作用域局部作用域语句作用域类作用域命名空间作用域和文件作用域

    从作用域看:

    全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。

    静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。

    局部变量也只有局部作用域,它是自动对象(auto),它在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回。

    静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。

    从分配内存空间看:

    全局变量,静态局部变量,静态全局变量都在静态存储区分配空间,而局部变量在栈里分配空间。

    全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。

    •  1)、静态变量会被放在程序的静态数据存储区(数据段)(全局可见)中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。
    •  2)、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。

    从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。应予以注意。

    Tips:

    • A、若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
    • B、若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
    • C、设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题,因为他们都放在静态数据存储区,全局可见;
    • D、如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量(这样的函数被称为:带“内部存储器”功能的的函数)
    • E、函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。

    -----------------------------------------------------------------------------------------------------------

    static 全局变量:改变作用范围,不改变存储位置

    static 局部变量:改变存储位置,不改变作用范围

    静态函数 :在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。

    如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数也称为内部函数。定义一个内部函数,只需在函数类型前再加一个“static”关键字即可。

    展开全文
  • Spring中bean的作用域与生命周期

    万次阅读 多人点赞 2017-06-17 22:29:18
    五种作用域中,request、session和global session三种作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。 (1)当一个bean的作用域为...

      在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。而bean的定义以及bean相互间的依赖关系将通过配置元数据来描述。

      Spring中的bean默认都是单例的,对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器内只有一个,Java的单例是基于JVM,每个JVM内只有一个实例。

    1、bean的作用域

      创建一个bean定义,其实质是用该bean定义对应的类来创建真正实例的“配方”。把bean定义看成一个配方很有意义,它与class很类似,只根据一张“处方”就可以创建多个实例。不仅可以控制注入到对象中的各种依赖和配置值,还可以控制该对象的作用域。这样可以灵活选择所建对象的作用域,而不必在Java Class级定义作用域。Spring Framework支持五种作用域,分别阐述如下表。

      五种作用域中,request、session和global session三种作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

      (1)当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:

    <bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
    

      (2)当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:

    <bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
     <!--或者-->
    <bean id="account" class="com.foo.DefaultAccount" singleton="false"/> 
    

      (3)当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

    <bean id="loginAction" class=com.foo.LoginAction" scope="request"/>
    

      针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。

      (4)当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
    

      针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

      (5)当一个bean的作用域为Global Session,表示在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

    <bean id="user" class="com.foo.Preferences "scope="globalSession"/>
    

      global session作用域类似于标准的HTTP Session作用域,不过仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。

    2、bean的生命周期

      Spring中Bean的实例化过程:

      Bean的生命周期:

      Bean实例生命周期的执行过程如下:

    • Spring对bean进行实例化,默认bean是单例;

    • Spring对bean进行依赖注入;

    • 如果bean实现了BeanNameAware接口,Spring将bean的名称传给setBeanName()方法;

    • 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory实例传进来;

    • 如果bean实现了ApplicationContextAware接口,它的setApplicationContext()方法将被调用,将应用上下文的引用传入到bean中;

    • 如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization()方法将被调用;

    • 如果bean中有方法添加了@PostConstruct注解,那么该方法将被调用;

    • 如果bean实现了InitializingBean接口,spring将调用它的afterPropertiesSet()接口方法,类似的如果bean使用了init-method属性声明了初始化方法,该方法也会被调用;

    • 如果在xml文件中通过<bean>标签的init-method元素指定了初始化方法,那么该方法将被调用;

    • 如果bean实现了BeanPostProcessor接口,它的postProcessAfterInitialization()接口方法将被调用;

    • 此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文被销毁;

    • 如果bean中有方法添加了@PreDestroy注解,那么该方法将被调用;

    • 若bean实现了DisposableBean接口,spring将调用它的distroy()接口方法。同样的,如果bean使用了destroy-method属性声明了销毁方法,则该方法被调用;

      这里特别说明一下Aware接口,Spring的依赖注入最大亮点就是所有的Bean对Spring容器的存在是没有意识的。但是在实际项目中,我们有时不可避免的要用到Spring容器本身提供的资源,这时候要让 Bean主动意识到Spring容器的存在,才能调用Spring所提供的资源,这就是Spring的Aware接口,Aware接口是个标记接口,标记这一类接口是用来“感知”属性的,Aware的众多子接口则是表征了具体要“感知”什么属性。例如BeanNameAware接口用于“感知”自己的名称,ApplicationContextAware接口用于“感知”自己所处的上下文。其实Spring的Aware接口是Spring设计为框架内部使用的,在大多数情况下,我们不需要使用任何Aware接口,除非我们真的需要它们,实现了这些接口会使应用层代码耦合到Spring框架代码中

      其实很多时候我们并不会真的去实现上面所描述的那些接口,那么下面我们就除去那些接口,针对bean的单例和非单例来描述下bean的生命周期:

    2.1 单例管理的对象

      当scope="singleton",即默认情况下,会在启动容器时(即实例化容器时)时实例化。但我们可以指定Bean节点的lazy-init="true"来延迟初始化bean,这时候,只有在第一次获取bean时才会初始化bean,即第一次请求该bean时才初始化。如下配置:

    <bean id="serviceImpl" class="cn.csdn.service.ServiceImpl" lazy-init="true"/>
    

      如果想对所有的默认单例bean都应用延迟初始化,可以在根节点beans设置default-lazy-init属性为true,如下所示:

    <beans default-lazy-init="true">
    

      默认情况下,Spring在读取xml文件的时候,就会创建对象。在创建对象的时候先调用构造器,然后调用init-method属性值中所指定的方法。对象在被销毁的时候,会调用destroy-method属性值中所指定的方法(例如调用Container.destroy()方法的时候)。写一个测试类,代码如下:

    public class LifeBean {
    	private String name;  
        
        public LifeBean(){  
            System.out.println("LifeBean()构造函数");  
        }  
        public String getName() {  
            return name;  
        }  
      
        public void setName(String name) {  
            System.out.println("setName()");  
            this.name = name;  
        }  
    
        public void init(){  
            System.out.println("this is init of lifeBean");  
        }  
          
        public void destory(){  
            System.out.println("this is destory of lifeBean " + this);  
        }  
    }
    

      life.xml配置如下:

    <bean id="life_singleton" class="com.bean.LifeBean" scope="singleton" 
    			init-method="init" destroy-method="destory" lazy-init="true"/>
    

      测试代码如下:

    public class LifeTest {
    	@Test 
    	public void test() {
    		AbstractApplicationContext container = 
    		new ClassPathXmlApplicationContext("life.xml");
    		LifeBean life1 = (LifeBean)container.getBean("life");
    		System.out.println(life1);
    		container.close();
    	}
    }
    

      运行结果如下:

    LifeBean()构造函数
    this is init of lifeBean
    com.bean.LifeBean@573f2bb1
    ……
    this is destory of lifeBean com.bean.LifeBean@573f2bb1
    

    2.2 非单例管理的对象

      当scope="prototype"时,容器也会延迟初始化bean,Spring读取xml文件的时候,并不会立刻创建对象,而是在第一次请求该bean时才初始化(如调用getBean方法时)。在第一次请求每一个prototype的bean时,Spring容器都会调用其构造器创建这个对象,然后调用init-method属性值中所指定的方法。对象销毁的时候,Spring容器不会帮我们调用任何方法,因为是非单例,这个类型的对象有很多个,Spring容器一旦把这个对象交给你之后,就不再管理这个对象了。

      为了测试prototype bean的生命周期life.xml配置如下:

    <bean id="life_prototype" class="com.bean.LifeBean" scope="prototype" init-method="init" destroy-method="destory"/>
    

      测试程序如下:

    public class LifeTest {
    	@Test 
    	public void test() {
    		AbstractApplicationContext container = new ClassPathXmlApplicationContext("life.xml");
    		LifeBean life1 = (LifeBean)container.getBean("life_singleton");
    		System.out.println(life1);
    		
    		LifeBean life3 = (LifeBean)container.getBean("life_prototype");
    		System.out.println(life3);
    		container.close();
    	}
    }
    

      运行结果如下:

     
    LifeBean()构造函数
    this is init of lifeBean
    com.bean.LifeBean@573f2bb1
    LifeBean()构造函数
    this is init of lifeBean
    com.bean.LifeBean@5ae9a829
    ……
    this is destory of lifeBean com.bean.LifeBean@573f2bb1
    

      可以发现,对于作用域为prototype的bean,其destroy方法并没有被调用。如果bean的scope设为prototype时,当容器关闭时,destroy方法不会被调用。对于prototype作用域的bean,有一点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法。但对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责(让Spring容器释放被prototype作用域bean占用资源的一种可行方式是,通过使用bean的后置处理器,该处理器持有要被清除的bean的引用)。谈及prototype作用域的bean时,在某些方面你可以将Spring容器的角色看作是Java new操作的替代者,任何迟于该时间点的生命周期事宜都得交由客户端来处理。

      Spring容器可以管理singleton作用域下bean的生命周期,在此作用域下,Spring能够精确地知道bean何时被创建,何时初始化完成,以及何时被销毁。而对于prototype作用域的bean,Spring只负责创建,当容器创建了bean的实例后,bean的实例就交给了客户端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的bean的生命周期。

    2.3 引申

      在学习Spring IoC过程中发现,每次产生ApplicationContext工厂的方式是:

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    

      这样产生ApplicationContext就有一个弊端,每次访问加载bean的时候都会产生这个工厂,所以这里需要解决这个问题。

      ApplicationContext是一个接口,它继承自BeanFactory接口,除了包含BeanFactory的所有功能之外,在国际化支持、资源访问(如URL和文件)、事件传播等方面进行了良好的支持。

      解决问题的方法很简单,在web容器启动的时候将ApplicationContext转移到ServletContext中,因为在web应用中所有的Servlet都共享一个ServletContext对象。那么我们就可以利用ServletContextListener去监听ServletContext事件,当web应用启动的是时候,我们就将ApplicationContext装载到ServletContext中。 Spring容器底层已经为我们想到了这一点,在spring-web-xxx-release.jar包中有一个已经实现了ServletContextListener接口的类ContextLoader,其源码如下:

    public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    	private ContextLoader contextLoader;
    
    	public ContextLoaderListener() {
    
    	}
    
    	public ContextLoaderListener(WebApplicationContext context) {
    		super(context);
    	}
    
    	public void contextInitialized(ServletContextEvent event) {
    		this.contextLoader = createContextLoader();
    		if (this.contextLoader == null) {
    			this.contextLoader = this;
    		}
    		this.contextLoader.initWebApplicationContext(event.getServletContext());
    	}
    
    	@Deprecated
    	protected ContextLoader createContextLoader() {
    		return null;
    	}
    
    	@Deprecated
    	public ContextLoader getContextLoader() {
    		return this.contextLoader;
    	}
    
    	public void contextDestroyed(ServletContextEvent event) {
    		if (this.contextLoader != null) {
    		this.contextLoader.closeWebApplicationContext(event.getServletContext());
    		}
    		ContextCleanupListener.cleanupAttributes(event.getServletContext());
    	}
    }
    

      这里就监听到了servletContext的创建过程, 那么 这个类又是如何将applicationContext装入到serveletContext容器中的呢?

      this.contextLoader.initWebApplicationContext(event.getServletContext())方法的具体实现中:

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
         if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
             throw new IllegalStateException(
                     "Cannot initialize context because there is already a root application context present - " +
                     "check whether you have multiple ContextLoader* definitions in your web.xml!");
         }
    
         Log logger = LogFactory.getLog(ContextLoader.class);
         servletContext.log("Initializing Spring root WebApplicationContext");
         if (logger.isInfoEnabled()) {
             logger.info("Root WebApplicationContext: initialization started");
         }
         long startTime = System.currentTimeMillis();
    
         try {
         	  // Store context in local instance variable, to guarantee that
              // it is available on ServletContext shutdown.
             if (this.context == null) {
                 this.context = createWebApplicationContext(servletContext);
             }
             if (this.context instanceof ConfigurableWebApplicationContext) {
                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                 if (!cwac.isActive()) {
                     // The context has not yet been refreshed -> provide services such as
                     // setting the parent context, setting the application context id, etc
                     if (cwac.getParent() == null) {
                         // The context instance was injected without an explicit parent ->
                         // determine parent for root web application context, if any.
                         ApplicationContext parent = loadParentContext(servletContext);
                         cwac.setParent(parent);
                     }
                     configureAndRefreshWebApplicationContext(cwac, servletContext);
                 }
             }
             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    
             ClassLoader ccl = Thread.currentThread().getContextClassLoader();
             if (ccl == ContextLoader.class.getClassLoader()) {
                 currentContext = this.context;
             }
             else if (ccl != null) {
                 currentContextPerThread.put(ccl, this.context);
             }
    
             if (logger.isDebugEnabled()) {
                 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
             }
             if (logger.isInfoEnabled()) {
                 long elapsedTime = System.currentTimeMillis() - startTime;
                 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
             }
    
             return this.context;
         }
         catch (RuntimeException ex) {
             logger.error("Context initialization failed", ex);
             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
             throw ex;
         }
         catch (Error err) {
             logger.error("Context initialization failed", err);
             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
             throw err;
         }
     }
    

      这里的重点是servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context),用key:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE value: this.context的形式将applicationContext装载到servletContext中了。另外从上面的一些注释我们可以看出: WEB-INF/applicationContext.xml, 如果我们项目中的配置文件不是这么一个路径的话 那么我们使用ContextLoaderListener 就会出问题, 所以我们还需要在web.xml中配置我们的applicationContext.xml配置文件的路径。

    <listener>
    	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <context-param>
    	<param-name>contextConfigLocation</param-name>
    	<param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    

      剩下的就是在项目中开始使用 servletContext中装载的applicationContext对象了: 那么这里又有一个问题,装载时的key是 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我们在代码中真的要使用这个吗? 其实Spring为我们提供了一个工具类WebApplicationContextUtils,接着我们先看下如何使用,然后再去看下这个工具类的源码:

    WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
    

      接着来看下这个工具类的源码:

    public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    	return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    }
    

      这里就能很直观清晰地看到 通过key值直接获取到装载到servletContext中的 applicationContext对象了。

      ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息,因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。在ContextLoaderListener中关联了ContextLoader这个类,整个加载配置过程由ContextLoader来完成。

    展开全文
  • 前端面试系列-JavaScript作用域作用域

    千次阅读 热门讨论 2021-03-22 11:24:09
    前端面试系列-JavaScript作用域作用域链 全局作用域,函数作用域,块级作用域
  • // 全局作用域:在script之间或者一个独立的js文件 script之间或者一个独立的js文件里的内容区域,在全局作用域中定义的作用域 全局作用域。 在任何位置都可以访问 // 局部变量:在函数作用域之间里的一个或者...
  • 全局作用域和函数作用域

    千次阅读 2019-02-10 17:03:25
    全局作用域和函数作用域 在ES5中,只全局作用域和函数作用域。这会导致函数作用域覆盖了全局作用域;亦或者循环中的变量泄露为全局变量。 例如: // 1.函数作用域覆盖了全局作用域,发生了变量提升,函数声明大于var...
  • python作用域,变量作用域

    千次阅读 2019-07-02 20:33:02
    变量作用域 一个程序的所有变量并不是在哪个位置都可以访问的。访问权限取决于这个变量是在哪里赋值的。 变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。 如下为两种最基本的变量的作用域: ...
  • 作用域作用域

    千次阅读 2020-04-13 19:57:09
    作用域作用域链 通常来说,一段程序代码中所用到的名字并不总是有效或可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域scope。当一个方法或成员被声明,他就拥有当前的执行上下文context环境。在有...
  • 块级作用域与函数作用域

    千次阅读 2018-03-24 10:30:29
    函数作用域:变量在定义的环境中以及嵌套的子函数中处处可见; 块级作用域:变量在离开定义的块级代码后立即被回收。 函数作用域 在ES6之前,js的作用域只有两种:函数作用域和全局作用域。使用var声明的...
  • JavaScript作用域作用域链知多少

    千次阅读 2020-09-12 19:44:33
    作用域就是代码的执行环境,全局执行环境就是全局作用域,函数的执行环境就是函数作用域(私有作用域),他们都是栈内存。全局执行环境是最外围的执行环境,根据ECMAScript所实现的宿主环境不同,表示的执行环境的对象...
  • 1.函数原型作用域: 此作用域为c++程序中最小的作用域,生存周期最短。 例:int func(int i) i为参数,作用域类型为函数原型类型。 2.局部作用域
  • 作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域作用域链的工作原理。今天这篇文章对JavaScript作用域示例详解的介绍,希望能帮助大家更好的学习JavaScript。 任何程序设计...
  • javascript 作用于作用域链的详解 一、JavaScript作用域 任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 899,273
精华内容 359,709
关键字:

作用域