精华内容
下载资源
问答
  • 下列对象不能作为容器的是
    千次阅读
    2014-09-10 09:38:41

      

    【摘要】对C++语言本身来说,它并不在乎用户把什么类型的对象作为STL容器的元素,因为模板类型参数在理论上可以为任何类型。比如说STL容器仅支持“值”语义而不支持“引用(&)”语义,并非因为模板类型参数不能为引用,而是因为如果容器元素为引用类型,就会出现“引用的引用”、“引用的指针”等C++语言不支持的语法和语义。智能指针是一种模拟原始指针行为的对象,因此理论上也可以作为容器的元素,就象原始指针可以作为容器元素一样。但是智能指针毕竟是一种特殊的对象,它们在原始指针共享实值对象的基础能力上增加了自动销毁实值对象的能力,如果将它作为容器的元素,可能导致容器之间共享元素对象实值,这不仅不符合STL容器的概念和“值”语义,也会存在安全隐患,同时也会存在许多应用上的限制,特别是象STL中的auto_ptr这样的智能指针。本文深入地阐述了auto_ptr这种较简单的智能指针“可以”或者“不可以”作为容器元素的根本原因,以及它作为容器元素会存在的限制和带来的问题,最后说明auto_ptr存在的真正意义、正确的使用方法以及它的替代品——带有引用计数能力的智能指针,当容器之间需要共享元素对象时,或者程序中存在大量的指针传递而担心资源泄漏时,这样的智能指针就特别有用。

    【关键字】auto_ptr  容器  智能指针

     

    一、引言

    Scott Meyers在《More Effective C++[3]一书中对智能指针及其相关问题(构造、析构、复制、提领、测试以及类型转换等)作了深入的分析,其中也提到“STLauto_ptr这种在复制时会把对实值对象的拥有权转交出去的智能指针不宜作为STL容器的元素”,而且在他的《Effective STL[4]Item 8中明确指出了这一点。Nicolai M.Josuttis的《The C++ Standard Library[5]中有一节专门针对auto_ptr的阐述也指出“auto_ptr不满足STL标准容器对元素的最基本要求”。但是他们都是从容器的需求、语义以及应用的安全性来阐述,而没有从语言的静态类型安全性和auto_ptr的实现方案角度深入地分析其原因,因此有些读者看了之后可能仍然不明就里:它是如何不满足容器需求的?它是如何违反C++的静态类型安全性从而避免误用的?

    我们知道,可以作为STL容器的元素的数据类型一般来说需要满足下列条件:

    1)可默认构造的(Default Constructible),也即具有publicdefault constructor,不论是用户显式定义的还是编译器自动合成的。但是用户定义的带参数的constructor(包括copy constructor)会抑制编译器合成default constructor。实际上并非任何情况下任何一种容器都强制要求其元素类型满足这一要求,特别是关联式容器,因为只有序列式容器的某些成员函数才可能明确地或隐含地使用元素类型的默认构造函数,如果你不使用这样的成员函数,编译器就不需要元素类型的默认构造函数;

    2)可拷贝构造(Copy Constructible)和拷贝赋值(Copy Assignable)的,即具有publiccopy constructorcopy assignment operator,不论是编译器自动合成的还是用户显式定义的。其它版本的operator=()重载并不会抑制编译器合成copy assignment operator,如果你没有显式定义它的话。这个条件可归结为:元素必须是可拷贝的(Copyable),但实际上拷贝赋值的要求也不是强制的,原因和默认构造函数类似;

    3)具有publicdestructor,不论是编译器自动合成的还是用户显式定义的;

    4)对于关联式容器,要求其元素必须是可比的(Comparable)。

    auto_ptr满足上述条件吗?至少满足前三条,因此至少可以作为序列式容器的元素;如果为auto_ptr定义了比较运算符的话,应该还可以把它作为关联式容器的元素。

    但是auto_ptr的特点是接管和转移拥有权,而不是像原始指针那样可以共享实值对象,即:auto_ptr在初始化时接管实值对象和拥有权,而在拷贝时(拷贝构造和拷贝赋值)会交出实值对象及其拥有权。因此,auto_ptr对象和它的拷贝绝对不会共享实值对象,任何两个auto_ptr也不应该共享同一个实值对象。这就是说,auto_ptr对象和它的拷贝并不相同。然而根据STL容器“值”语义的要求,可拷贝构造意味着一个对象必须和它的拷贝相同(标准中的正式定义比这稍复杂一些)。同样,可赋值意味着把一个对象赋值给另一个同类型对象将产生两个相同的对象。显然,auto_ptr不能满足这一要求,似乎与上面的结论矛盾!

    那么问题究竟出在哪里呢?

     

    二、copy constructorcopy assignment operator的形式

    在揭开auto_ptr的神秘面纱之前需要了解copy constructorcopy assignment operator的几种合法形式。任何一个类都允许两种形式的copy constructor[1]C代表任何一个类):

    C(const C& copy);

    C(C& copy);

    同样,copy assignment operator也允许类似的两种形式(返回值类型视实际需要可改变):

    C& operator=(const C& copy);

    C& operator=(C& copy);

    实际上,由于copy assignment operator为普通的运算符重载成员函数,因此还可以为下列形式:

    C& operator=(C copy);

    这两个函数具体是什么形式,取决于用户的定义或者该类的成员对象及其基类具有什么样的copy constructorcopy assignment operator。比如,如果基类的copy constructor为第一种形式,那么编译器自动为派生类合成的copy constructor也为第一种形式;相反为第二种形式。Copy assignment operator亦类似。具体细节可参考[8]

    这两种形式的区别就在于参数有无修饰符const:如果有const修饰,则该函数体不能修改实参对象(即拷贝源),也不能调用其non-const成员函数;如果没有const修饰,则该函数可以修改实参对象,也可以调用其non-const成员函数。

    从语言的角度讲,任何对象都可以放到容器中(只要不是引用,因为STL容器不支持“引用”语义),只是某些类型的对象会存在安全隐患或者其容器会受到很大的应用限制。如果要防止用户把一些不适宜的对象放入容器中,就要求对象的设计和实现者使用一些语言支持的但不常用的特征。也就是说,要能够在编译阶段就阻止这种具有潜在危险性的行为。常用的方法就是迫使其违反C++静态类型安全规则。

    下面我们来看一看auto_ptr到底是如何通过迫使其违反C++静态类型安全规则而在编译时阻止将其作为容器元素的。

    三、auto_ptr源码分析

    其实auto_ptr的拥有权管理非常简单。根据上一节的阐述,可以使用两种方案来实现auto_ptr。下面是拷贝构造函数和拷贝赋值函数采用non-const参数的一个实现版本:

    template<class T>

    class auto_ptr

    {

    private:

    T      *m_ptr;            // 原始指针

    public:

    explicit auto_ptr(T *p = 0) throw()        // explicit constructor

    : m_ptr(p){ }                          // *p必须是运行时创建的对象

     

    auto_ptr(auto_ptr& other) throw()         // 非常规copy constructor

    : m_ptr(other.release()){ }           // 转让拥有权,修改了实参对象

     

    #ifdef _SUPPORT_MEMBER_TEMPLATES_

    template<class U>

    auto_ptr(auto_ptr<U>& other) throw()

    : m_ptr(other.release()){ }           // 转让拥有权,修改了实参对象

    #endif

        

    auto_ptr& operator=(auto_ptr& other) throw() // 非常规assignment

    {

    if (&other != this) {

    delete m_ptr;                     // 释放实值对象

    m_ptr = other.release();          // 交出拥有权,修改了实参对象

    }

    return (*this);

    }

        

    #ifdef _SUPPORT_MEMBER_TEMPLATES_

    template<class U>

    auto_ptr& operator=(auto_ptr<U>& other) throw()

    {

    if (other.get() != this->get()) {

    delete m_ptr;                   // 释放实值对象

    m_ptr = other.release();        // 交出拥有权,修改了实参对象

    }

    return (*this);

    }

    #endif

            

                // 从析构函数看,m_ptr必须指向动态创建的对象

    ~auto_ptr(){ delete m_ptr; }           // destructor,“delete 0”没有任何问题!

    T& operator*() const throw(){ return *m_ptr; }

    T* operator->() const throw(){ return m_ptr; }

    T* get() const throw(){ return m_ptr; }

     

    T* release() throw(){

    T *temp = m_ptr;

    m_ptr = 0;          // 必要!修改成员,释放拥有权

    return temp;

    }

    void reset(T *p = 0) throw(){

    if (p != m_ptr) {

    delete m_ptr;

    m_ptr = p;

    }

    }

    bool owns() const{ return (m_ptr != 0); }

    …      // 这里省略了一些无关紧要的东西

    };

    如你所见,该auto_ptr实现版本的copy constructorcopy assignment operator的参数类型都是non-const的,因为这两个函数都会修改实参对象的数据成员,即调用其release方法(non-const方法)释放其对实值对象的拥有权,并把实值对象的指针置为0。如果参数类型为const的,那么这种修改就不可能直接进行。所以,一旦用一个auto_ptr对象去构造另一个auto_ptr对象,或者把一个auto_ptr对象赋值给另一个auto_ptr对象,你就不能再使用原来的那个auto_ptr对象了,因为反引用NULL指针会导致运行时异常,除非你让它重新接管一个新的实值对象。

    这个版本的auto_ptr就不能作为任何容器的元素,如果你这样做了,在编译阶段就会检查出错误,即违反了C++的静态类型安全规则。比如:

    std::list< std::auto_ptr<int> >  la;     // auto_ptr列表

    std::auto_ptr<int> p1(new int(1));

    std::auto_ptr<int> p2(new int(2));

    std::auto_ptr<int> p3(new int(3));

    la.push_back(p1);                        // compiling-error!

    la.push_back(p2);                        // compiling-error!

    la.push_back(p3);                        // compiling-error!

     

    set<auto_ptr<int> > sa;                  // auto_ptr集合:假设为auto_ptr定义了operator<

    sa.insert(p1);                           // compiling-error!

    sa.insert(p2);                           // compiling-error!

    sa.insert(p3);                           // compiling-error!

    STL容器管理元素的方法是动态创建元素的拷贝,并负责管理这些动态分配的资源,即值的深拷贝语义(deep copy),具体由一个可定制的memory allocator来负责,不过这不是我们讨论的重点,因此忽略。可以想象std::list<T>::push_back方法的实际动作如下:

    template<typename T>

    void list<T>::push_back(const T& x)

    {

    T *p = operator new(sizeof(T));  // 分配内存空间

    new (p) T(x);                    // placement new,调用T的copy constructor

    ……                             // 将p交给容器管理,调整容器大小

    }

    由于auto_ptrcopy constructor被显式地定义为接受non-const&,因此上述函数实现就需要将一个const T& x转换为non-const&,显然是违反静态类型安全规则的。STL容器不能使用强制类型转换来帮你达到此目的,否则它本身就不是类型安全的了。

    其它追加元素的方法如insert的某些版本也是一样的道理。

    上述auto_ptr通过采用非常规copy constructorcopy assignment operator使“企图将auto_ptr对象作为STL容器元素”的行为在编译阶段就被检测出来,从而避免了潜在的危险。

    然而如果auto_ptr采用常规copy constructorcopy assignment operator形式,编译器就无能为力了,因为它们不违反C++静态类型安全规则。P.J.Plauger版本(MS VC++采用的实现)的auto_ptr就是一个例子!

    P.J.Plauger版本的auto_ptr确实可以作为容器的元素,这并不是因为它没有修改拷贝源的拥有权,而是它的release函数虽然是const member function,却在修改拥有权时使用了const_cast强制类型转换。因为在一个const member function里面,编译器把当前对象看成是一个const对象(即this的类型为const auto_ptr<T> * const),调用copy constructor时通过强制类型转换就可以修改实参对象的拥有权属性,尽管它是const &传递。

    下面是拷贝构造函数和拷贝赋值函数采用const参数的一个实现版本:

    template<class T>

    class auto_ptr

    {

    private:

    T      *m_ptr;            // 原始指针

    public:

    explicit auto_ptr(T *p = 0) throw()        // explicit constructor

    : m_ptr(p){ }                          // *p必须是运行时创建的对象

     

    auto_ptr(const auto_ptr& other) throw()   // 常规copy constructor

    : m_ptr(other.release()){ }           // 转让拥有权,修改了实参对象

     

    #ifdef _SUPPORT_MEMBER_TEMPLATES_

    template<class U>

    auto_ptr(const auto_ptr<U>& other) throw()

    : m_ptr(other.release()){ }           // 转让拥有权,修改了实参对象

    #endif

        

    auto_ptr& operator=(const auto_ptr& other) throw() // 常规assignment

    {

    if (&other != this) {

    delete m_ptr;                     // 释放实值对象

    m_ptr = other.release();          // 交出拥有权,修改了实参对象

    }

    return (*this);

    }

        

    #ifdef _SUPPORT_MEMBER_TEMPLATES_

    template<class U>

    auto_ptr& operator=(const auto_ptr<U>& other) throw()

    {

    if (other.get() != this->get()) {

    delete m_ptr;                   // 释放实值对象

    m_ptr = other.release();        // 交出拥有权,修改了实参对象

    }

    return (*this);

    }

    #endif

     

                // 从析构函数看,m_ptr必须指向动态创建的对象

    ~auto_ptr(){ delete m_ptr; }           // destructor,“delete 0”没有任何问题!

    T& operator*() const throw(){ return *m_ptr; }

    T* operator->() const throw(){ return m_ptr; }

    T* get() const throw(){ return m_ptr; }

     

    T* release() const throw(){

    T *temp = m_ptr;

    ((auto_ptr<T>*)this)->m_ptr = 0;     // 必要!修改成员,释放拥有权

    return temp;

    }

    void reset(T *p = 0) throw(){

    if (p != m_ptr) {

    delete m_ptr;

    m_ptr = p;

    }

    }

    bool owns() const{ return (m_ptr != 0); }

    …      // 这里省略了一些无关紧要的东西

    };

    一旦如此实现,auto_ptr容器就可以顺利通过编译并可能正确执行。例如:

    int main()

    {

     typedef std::list<std::auto_ptr<int> >  IntPtrList;

    IntPtrList  la;  // or:std::vector<std::auto_ptr<int> >  va;

    std::auto_ptr<int> p1(new int(1));

    std::auto_ptr<int> p2(new int(2));

    std::auto_ptr<int> p3(new int(3));

    std::auto_ptr<int> p4(new int(4));

    std::auto_ptr<int> p5(new int(5));

    la.push_back(p1);                   // ok! 转交所有权

    la.push_back(p2);                   // ok! 转交所有权

    la.push_back(p3);                   // ok! 转交所有权

    la.push_back(p4);                   // ok! 转交所有权

    la.push_back(p5);                   // ok! 转交所有权

    // 不能再使用p1、p2、p3、p4、p5

    for (IntPtrList::const_iterator first = la.begin(),

          last = la.end(); first != last; ++first)

    std::cerr << **first << ‘\t’;

    // 不能再使用p1、p2、p3、p4、p5

    return 0;

    } // la析构的时候会自动调用每一个auto_ptr元素的析构函数,从而保证释放动态分配的内存

     

    输出:

    1   2   3   4   5

    但是把auto_ptr作为容器元素毕竟是一个危险的动作,而且这样的容器在使用时会受到很大的限制。如果上面的程序接着使用la,比如创建它的拷贝,调整它的大小,甚至对它排序,那么la就可能遭到破坏,它的所有元素会变成无效指针,或者里面夹杂了无效指针,甚至有可能丢失一些元素,而你却没有意识到。例如,假设为auto_ptr定义了泛型比较运算符:

    int main()

    {

     typedef std::vector<std::auto_ptr<int> >  IntPtrVector;

    IntPtrVector  va;

    std::auto_ptr<int> p1(new int(1));

    std::auto_ptr<int> p2(new int(2));

    std::auto_ptr<int> p3(new int(3));

    std::auto_ptr<int> p4(new int(4));

    std::auto_ptr<int> p5(new int(5));

    va.push_back(p1);

    va.push_back(p2);

    va.push_back(p3);

    va.push_back(p4);

    va.push_back(p5);

     

     (注意:以下操作并非放在一起进行,仅是示范)

     IntPtrVector vb = va;    // va丧失对所有实值对象的拥有权,元素成为NULL指针

     vb.resize(10);           // 新增的元素都为NULL指针

     std::sort(vb.begin(), vb.end());      // 可能会使其中某些元素成为NULL指针

     std::auto_ptr<int> t = vb.front();    // 改变了容器元素

     std::auto_ptr<int> r = vb[3];         // 改变了容器元素

     

     std::list<std::auto_ptr<int> >  la;

     std::copy(vb.begin(), vb.end(), std::back_inserter(la)); // copy改变了拷贝源

     

    return 0;

    }

    Scott Meyers在《Effective STL[4]Item 8中详细地分析了对auto_ptr容器进行排序时可能会导致的问题。但是在MSVC++环境下经测试,并没有出现书中所描述的悲惨结果,而是结果正确。主要的原因在于C++标准并没有要求std::sort等泛型算法的实现必须采用某一种方法,而是只规定了它们的接口、功能和应该达到的性能要求(容器也是如此)。因此,不同的STL实现可能采取不同的方法,比如有的sort实现采用快速排序法,而有的采用插入式排序法,等等。不同的排序方法在遭遇auto_ptr这样的容器时可能就会产生不同的结果。

    P.J.Plauger版本在这方面的防范能力确实不如SGI版本做得好!不过没关系,STL的源代码都是公开的,你可以比较不同的实现甚至修改它们,使之更安全、更适合你的应用。

    四、auto_ptr对象作为容器元素的危险性

    应该说,从应用的方便性和安全角度出发,容器应该要求其元素对象的拷贝与原对象相同或者等价,但auto_ptr显然不满足这一条。auto_ptr作为容器元素的危险性主要表现在如下几个方面:

    1)将auto_ptr对象插入容器中之后企图继续使用它,比如通过它调用实值对象的成员函数,然而此时它指向的实值对象已经交给容器中的某一个元素对象了;

    2)就象auto_ptr和它的拷贝并不相同一样,auto_ptr容器和它的拷贝也不一样;如果对某些成员函数的返回结果使用不当的话,可能无意中会产生不期望的结果。因此其应用受到很大限制,必须小心应付;

    3)某些算法将无法用于这样的容器。比如sort等会修改区间的算法,因为它们的实现调用元素对象的copy constructorcopy assignment operator,可能会释放掉某些元素对实值对象的拥有权;就连本来不会修改源区间的算法如copy,如果应用于auto_ptr容器,也可能修改源区间;还有的算法比如find等要求元素对象提供比较能力,如果auto_ptr不是可比的,那也不能用于auto_tr容器;

    4)不可移植。目前有些STL实现比如SGI版本可以在编译阶段阻止这种行为,但是某些STL实现仍然允许这样做。

    鉴于此,无论你使用的STL平台是否允许auto_ptr容器,你都不应该这样做。

    然而许多其它的功能强大的智能指针,比如使用了引用计数的智能指针,作为容器的元素时不会存在上述问题,但是auto_ptr不是这样的智能指针。关于智能指针的更详细阐述还可参考Andrei Alexandrescu的《Modern C++ Design[6]一书第7章。

    可见,智能指针“可以”还是“不可以”作为容器的元素并非绝对的,不仅与STL的实现有关,而且与STL容器的需求和安全性以及容器的语义有关。

    五、auto_ptr的正确用法

    既然auto_ptr在复制或赋值时会使原来的auto_ptr失效,那么我们只要防止其复制和赋值行为的发生就可以了。比如在传递auto_ptr对象时使用const &const *传递而不是值传递。例如:

    void func(const auto_ptr<int>& pInt)

    {

    cout << *pInt << endl;

    }

    int main()

    {

    auto_ptr<int> a(new int(100));

    func(a);

    }

    但即使这样,如果遇到象P.J.Plauger那样实现的auto_ptr,还是不能保证在函数内部不会出现对它的拷贝或者赋值。

    再就是不要用静态创建的对象来初始化auto_ptr。例如:

    int main()

    {

    int x(100);

    auto_ptr<int> a(&x);

    }// 这里调用delete删除本地对象,错误!

    由于auto_ptr是对象化的智能指针,具有自动释放资源的能力,因此它真正有价值的用途是在发生异常时避免资源泄漏。比如,如果不使用auto_ptr,则下列代码在发生异常的情况下不得不多次手工释放资源:

    class A{ … };

    void func()

    {

    A *pA = new A;

    try

    {

    …                // using *pA

    }

    catch(…)

    {

    delete pA;       // 发生异常时要显式释放

    throw;

    }

    delete pA;           // 函数退出时还要显式释放

    }

    现在有了auto_ptr,我们就可以这么做:

    class A{ … };

    void func()

    {

    auto_ptr<A> pA(new A);

    …                // using *pA

    }

    这是因为C++有一个保证:本地对象在函数退出时总是会被销毁,而不论函数以何种方式退出。也就是说,不管是在发生异常的情况下函数退出,还是函数的正常退出,堆栈都要展开,每一个本地对象的析构函数都会被依次调用。关于资源泄漏和智能指针的相关话题,请参考[3]的第9101128等条款,其中有极详细和精彩的论述。

    如果想防止无意中修改auto_ptr对实值对象的拥有权,可以使用const auto_ptr,这样的auto_ptr只能使用引用或指针传递,不能值传递,也不能赋值和拷贝构造。例如:

    class A{ … };

    void func()

    {

    const auto_ptr<A> p1(new A);

    …                               // using *pA

    auto_ptr<A> p2(p1);              // error!

    auto_ptr<A> p3;

    p3 = p1;                         // error!

    }

    关于auto_ptr的运用技巧可参考[5]的相关章节。

     

    【参考资料】

    [1]STL implementation, SGI co., 2000.

    [2]STL implementation, P.J.Plauger, 1995.

    [3]More Effective C++, Scott Meyers, 1998.

    [4]Effective STL, Scott Meyers, 2001.

    [5]The C++ Standard Library, Nicolai M.Josuttis, 1999.

    [6]Modern C++ Design, Andrei Alexandrescu, 2001.

    [7]Generic Programming and the STL, H.Austern, 1999.

    [8]Inside the C++ Object Model, Stanley B.Lippman, 1996.



    [1] 注意:C(C copy); 并非一个递归函数,它是非法的,因为它是一个悖论。

    更多相关内容
  • C++容器概述和序列式容器基本操作

    千次阅读 2018-02-02 18:40:31
    容器是一些特定类型对象的集合,容器类分为序列式容器和关联容器两种。容器基本操作 容器类的一些基本操作如下图:定义和初始化 每个容器都定义了一个默认构造函数。除array之外,其他容器的默认构造函数都会创建...

    容器是一些特定类型对象的集合,容器类分为序列式容器和关联容器两种。

    容器基本操作

     容器类的一些基本操作如下图:

    定义和初始化

        每个容器都定义了一个默认构造函数。除array之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。


    将一个容器初始化为另一个容器的拷贝

        将一个新容器创建为另一个容器的拷贝的方法有两种:可以直接拷贝整个容器,或者拷贝由一个迭代器对指定的元素范围。后者对array不适用。
        用第一种方式创建一个容器为另一个容器的拷贝时,两个容器的类型及其元素类型必须匹配。用第二种方式——传递迭代器参数,来拷贝时,就不要求容器类型是相同的了。而且新容器和原容器中的元素类型也可以不同,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可(例如可以将const char*元素转换为string)。

    序列式容器

            序列式容器为程序员提供了控制元素存储和顺序访问的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。
            C++的序列式容器有vector、deque、list、forward_list、array和string几种。它们都提供了快速顺序访问元素的操作。但它们在访问顺序、添加/删除元素的代价方面存在差异。
            string和vector都是可变大小的容器类,string专门用于保存字符,两者都将元素保存在连续的内存空间中。由于元素是连续存储的,由元素下标来计算其地址是非常快速的。但是,在这两种容器的中间位置添加/删除元素就会非常耗时:在一次插入/删除操作后,需要移动插入/删除之后的所有元素来保持连续存储。而且,添加一个元素有时可能还需要分配额外的存储空间,这种情况下每个元素都必须移动到新的存储空间中。
            list和forward_list两个容器支持容器任何位置的快速插入/删除操作,list是双向链表,forward_list是单向链表。作为代价,这两个容器不支持元素的随机访问,只能够顺序访问。而且,与vector和deque、array相比,这两个容器的额外内存开销较大。
            deque是一个双端队列,支持快速随机访问,但在除头尾以外的位置添加/删除元素的代价较大。但是,在deque的两端添加/删除元素的速度与list或forward_list的速度相当。

            array是最接近原始数组的一个容器类,其对象的大小是固定的。因此,array不支持添加和删除元素以及改变容器大小的操作。与内置数组相比,array是一种更安全、更容易使用的数组类型。

            序列式容器的特点决定我们应该在何种情况下使用何种容器。通常,使用vector是最好的选择,除非你有很好的理由选择其他容器。如果程序要求随机访问元素,不应使用list和forward_list。如果程序只需要在头尾位置插入或删除元素,则使用deque。一般来说,应用中占主导地位的操作决定了容器类型的选择。

    相关操作

    赋值和swap

    赋值运算符将其左边容器中的全部元素替换为右边容器中元素的拷贝:
    c1 = c2;
    c1 = {a, b, c};
            第一个赋值运算后,左边容器将与右边容器相等。如果两个容器原来大小不同,赋值运算后两者的大小都与右边容器的原大小相同。第二个赋值运算后,c1的size变为3,即花括号列表中值的数目。
            与内置数组不同,标准库array类型允许赋值。赋值号左右两边的运算对象必须具有相同的类型。并且,由于array类型大小固定,还要求赋值号左右两边的运算对象的大小相等。总之,所有试图改变原来array类型大小的操作都是不被允许的。
    使用assign(仅序列式容器)
            赋值运算符要求左边和右边的运算对象具有相同的类型。它将右边运算对象中所有元素拷贝到左边运算对象中。顺序容器(array除外)定义了一个assign成员函数,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。assign操作用参数所指定元素的拷贝替换左边容器中的所有元素。
    list<string> names;
    vector<const char*> oldstyle;
    names.assign(oldstyle.cbegin(), oldstyle.cend());

            上述代码将names中的元素替换为迭代器指定的范围中的元素的拷贝。assign的参数决定了容器中将有多少个元素以及值是什么。这一操作是不能用赋值运算符完成的,因为两个容器的类型不同。


    使用swap

            swap操作交换两个相同类型容器的内容。调用swap后,两个容器中的元素将会交换。而且,用swap操作来交换两个容器的速度会很快,因为元素本身并未交换,swap只是交换了两个容器的内部数据结构。也即swap不对元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。
            元素不会被移动的事实意味着,除string外,指向容器的迭代器、引用和指针在swap后都不会失效,它们仍然指向swap操作之前所指向的元素。但是,在swap操作后这些元素已经属于不同的容器了。
            swap操作有两个例外,一个是string,对string调用swap会导致迭代器、引用和指针失效。另一个是array,对array调用swap操作会真正交换它们的元素。因此,对于array,swap操作后指针、引用和迭代器所绑定的元素位置保持不变,但元素值已经与另一个array中对应元素的值进行了交换。
            上述对swap操作的描述都只针对非成员版本的swap,即std::swap

    容器大小操作

            出forward_list不支持size外,每个容器类型都有三个与大小相关的操作,这三个成员函数分别是:size、empty和max_size

    序列式容器操作

    添加元素

            从下表可以看到,虽然某些容器不支持push_front操作,但它们对于insert操作并无类似的限制(插入开始位置)。因此我们可以使用insert将元素插入到容器的开始位置,而不必担心容器是否支持push_front。但需要注意的是,虽然将元素插入到vector、deque和string中的任何位置都是合法的。然而,这样做可能很耗时。


    删除元素


    访问元素

    特殊的forward_list操作

            当添加或删除一个元素时,删除或添加的元素之前的那个元素的后继会发生改变。为了添加或删除一个元素,我们需要访问其前驱,以便改变前驱的链接。但是,forward_list是一个单向链表。在一个单向链表中,没有简单的方法来获取一个元素的前驱。出于这个原因,在一个forward_list中添加或删除元素的操作是通过改变给定元素之后的元素来完成的。这样,我们总是可以访问到被添加或删除操作所影响的元素。

            由于这些操作与其他容器上的操作的实现方式不同,forward_list并未定义insert、emplace和erase,而是定义了名为insert_after、emplace_after和erase_after的操作。


    改变容器的大小

        我们可以用resize来增大或缩小容器。如果当前大小大于所要求的大小,容器后部的元素会被删除;如果当前大小小于所要求的大小,会将新元素添加到容器后部。


    容器操作可能使迭代器失效

            向容器中添加元素和从容器中删除元素的操作可能会使指向容器元素的指针、引用或迭代器失效。一个失效的指针、引用或迭代器将不再表示任何元素。


            在向容器添加元素后:
    ·如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效。
    ·对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
    ·对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。
            从容器中删除元素后:
    ·所有容器中指向被删除元素的迭代器、指针和引用会失效。
    ·对于list和forward_list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍有效。
    ·对于deque,如果在首尾位置之外的任何位置删除元素,那么指向被删除元素外的其他元素的迭代器、引用或指针也会失效。如果是删除deque的尾元素,则尾后迭代器也会失效,但其他迭代器、引用和指针不受影响;如果是删除首元素,这些也不会受影响。
    ·对于vector和string,指向被删除元素之前的元素的迭代器、引用和指针仍有效。
            由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器。另外,最小化要求迭代器必须保持有效的程序片段也是一个便于管理迭代器的好方法。


            C++11开始的新标准库比旧版本快得多,原因是新标准库支持移动对象的操作,而旧标准库只能拷贝对象。因此新标准库容器的性能相较于旧标准库有比较大的提升。

    本文摘自《C++ Primer(中文版)第五版》


    展开全文
  • Spring 容器到底是个什么

    千次阅读 2021-02-23 13:11:06
    作为容器,是指存储东西的一种器具。 Spring 容器 相信刚接触Spring框架的时候,经常看到博文说Spring是一个容器,针对容器这个概念,可能会觉得很空洞。 在Java语言中,相对于保存物质的器具,如数组、集合等,都...

    容器

    生活中,容器多种多样。小到茶杯,大到水池。

    作为容器,是指存储东西的一种器具。

    Spring 容器

    相信刚接触Spring框架的时候,经常看到博文说Spring是一个容器,针对容器这个概念,可能会觉得很空洞。

    在Java语言中,相对于保存物质的器具,如数组、集合等,都能算一个容器。
    Spring 容器也就是保存与Spring框架相关数据的容器。通常用于保存各种各样的Bean。

    上一篇文章中已经说明了Spring Bean为Spring针对对象的一种实例化操作。

    单例Bean、多例Bean(scope)

    先说一个问题:

    Spring 容器中的单例Bean,针对同一个对象,并不是只有一个Bean

    1、单例Bean

    这种描述,有点上头,怎么理解上述结论,可以参考下面的Spring.xml配置:

    <bean id="userBean" class="cn.linkpower.test.User"></bean>
    <bean id="userBean1" class="cn.linkpower.test.User"></bean>
    

    如上所示,针对同一个cn.linkpower.test.User对象,配置了两个 Bean。查看测试结果:

    package cn.linkpower.test;
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Test {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    
            System.out.println(applicationContext.getBean("userBean",User.class));
            System.out.println(applicationContext.getBean("userBean",User.class));
    
            System.out.println(applicationContext.getBean("userBean1",User.class));
            System.out.println(applicationContext.getBean("userBean1",User.class));
        }
    }
    

    执行结果如下所示:
    在这里插入图片描述
    由上可见,针对同一个类,配置时指定不同的id,可以生成多个不同的bean

    2、多例Bean

    在xml的配置文件中,默认以单例bean的形式,构建bean对象,保存至Spring容器中:

    <bean id="userBean2" class="cn.linkpower.test.User" scope="singleton"></bean>
    

    如何生成多实例?

    只需要配置 scope 参数即可!

    <bean id="userBean2" class="cn.linkpower.test.User" scope="prototype"></bean>
    

    测试类编写:

    package cn.linkpower.test;
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class DuoLiTest {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            System.out.println(applicationContext.getBean("userBean2",User.class));
            System.out.println(applicationContext.getBean("userBean2",User.class));
        }
    }
    

    运行结果:
    在这里插入图片描述

    单例池

    以上的这些bean对象在Spring容器启动的时候,就开始进行生成操作,生成之后会保存于Spring容器的单例池中。

    单例池,本质上是一个 ConcurrentHashMap数据结构,按照key:value的形式保存各种bean信息,其中key为定义的id,value为对应类的实例化bean。
    在这里插入图片描述

    Spring 容器有哪些

    BeanFactory

    BeanFactory bean的实例化工厂,主要负责bean的解析、实现和保存化操作。
    如下所示:

    package cn.linkpower.beanFactory;
    
    import org.springframework.beans.factory.config.ConfigurableBeanFactory;
    import org.springframework.beans.factory.support.AbstractBeanDefinition;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.beans.factory.support.DefaultListableBeanFactory;
    
    class User {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    public class Test {
        public static void main(String[] args) {
            // spring的一种容器
            DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
    
            // 修饰一个bean出来(就像xml的封装一个bean出来)
            AbstractBeanDefinition abstractBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
            abstractBeanDefinition.setBeanClass(User.class);
            //abstractBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_SINGLETON);
    
            // 将修饰出来的bean放置于spring容器中
            defaultListableBeanFactory.registerBeanDefinition("userdemo",abstractBeanDefinition);
    
            System.out.println(defaultListableBeanFactory.getBean("userdemo"));
        }
    }
    
    

    ApplicationContext

    ApplicationContext源码中继承于BeanFactory
    在这里插入图片描述

    public interface ApplicationContext extends EnvironmentCapable, 
    ListableBeanFactory, HierarchicalBeanFactory, MessageSource, 
    ApplicationEventPublisher, ResourcePatternResolver {
    
    

    但实际功能相比BeanFactory而言,更加强大:

    1、org.springframework.beans.factory.HierarchicalBeanFactory 相比其父类 BeanFactory,多了获取父类bean工厂的功能。
    2、EnvironmentCapable 可以获取系统环境、JVM环境等。
    3、MessageSource 国际化操作。
    4、ApplicationEventPublisher 事件发布功能。
    5、ResourcePatternResolver 资源匹配解析功能。

    但是ApplicationContext 只是一个接口,其下包含很多子类,比如:AnnotationConfigApplicationContextClassPathXmlApplicationContextFileSystemXmlApplicationContext等。

    既然包含这么多的功能,以下就使用案例获取这些信息:

    package cn.linkpower.application;
    
    import org.springframework.beans.factory.support.AbstractBeanDefinition;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.core.io.Resource;
    
    class User {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    public class Test {
        public static void main(String[] args) {
            // 创建 applicationContext 容器
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
            applicationContext.refresh();
            // 修饰bean
            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
            beanDefinition.setBeanClass(User.class);
    
            // 将bean加载至容器中
            applicationContext.registerBeanDefinition("userbean",beanDefinition);
    
            // 获取bean
            System.out.println(applicationContext.getBean("userbean",User.class));
    
            // 获取系统参数信息
            System.out.println("系统信息-->"+applicationContext.getEnvironment().getSystemEnvironment());
    
            // 获取jvm参数信息
            System.out.println("JVN-->"+applicationContext.getEnvironment().getSystemProperties());
    
    
            // 发布事件(发布之后会触发监听)
            //applicationContext.publishEvent(new String("66666"));
    
            // 获取资源
            Resource resource = applicationContext.getResource("classpath:spring.xml");
            System.out.println("---->"+resource);
        }
    }
    

    前面说到了 AnnotationConfigApplicationContext是基于注解构建bean并放置于Spring容器中。

    但在ApplicationContext容器下还有ClassPathXmlApplicationContextFileSystemXmlApplicationContext两个字类,他们的区别是什么?

    看下面案例:

    package cn.linkpower.application;
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    
    /**
     * 比较 ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext 的区别
     */
    public class Test2 {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    
            System.out.println(applicationContext.getBean("user"));
    
            //FileSystemXmlApplicationContext fileSystemXmlApplicationContext = new FileSystemXmlApplicationContext("spring_container/src/main/resources/spring.xml");
            //System.out.println(fileSystemXmlApplicationContext.getBean("user"));
        }
    }
    

    运行上述代码,发现:

    可以获取到指定的配置文件,并能解析加载bean至applicationContext容器中,以至可以从容器中获取到bean对象实现操作。

    那么,ClassPathXmlApplicationContext是如何知道文件位置并解析加载的?
    1、项目启动时,控制台中出现的日志
    在这里插入图片描述
    2、其中的classpath包含如下

    -classpath D:\spring_test_idea\spring_container\target\classes;

    3、查看项目所在的文件目录
    在这里插入图片描述
    在这里插入图片描述
    由此可见,ClassPathXmlApplicationContext是以编译后的项目地址作为参考,采取相对路径模式获取配置文件,实现配置文件的获取、加载、解析操作。


    再来看FileSystemXmlApplicationContext这个类的操作方式:

    FileSystemXmlApplicationContext fileSystemXmlApplicationContext = new FileSystemXmlApplicationContext("spring_container/src/main/resources/spring.xml");
    System.out.println(fileSystemXmlApplicationContext.getBean("user"));
    

    运行程序代码,观察控制台日志输出:
    在这里插入图片描述
    FileSystemXmlApplicationContext采取的是项目源代码路径,进行相对定位,其次也能采取全局定位的方式获取配置文件信息。

    ClassPathXmlApplicationContext不支持绝对路径。

    三大容器总结

    基于xml文件或注解获取bean

    ClassPathXmlApplicationContextFileSystemXmlApplicationContext都能基于文件形式获取配置一个类的bean的信息。

    但是ClassPathXmlApplicationContext 只能根据 classpath(xx/target/classes/)相对路径获取对应的文件,且不支持配置全局路径。

    FileSystemXmlApplicationContext基于源码地址进行相对路径获取,或者采取全局路径获取指定的配置文件。


    AnnotationConfigApplicationContext则可以采取配置类加上@Bean或者@ComponentScan的形式获取bean。

    • 如下所示,基于@Bean获取:
    class User {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    class UserConfig {
        /**
         * 采取 @Bean 的方式,构建一个 bean对象;<br/>
         * 相当于 <bean id="getUser" class="cn.linkpower.beans.User" />
         * @return
         */
        @Bean
        public User getUser(){
            return new User();
        }
    }
    public class Test {
        public static void main(String[] args) {
            // 构建容器
            //AnnotationConfigApplicationContext annotationConfigApplicationContext = new  AnnotationConfigApplicationContext();
            // 加载、注册配置类
           // annotationConfigApplicationContext.register(UserConfig.class);
            //annotationConfigApplicationContext.refresh();
            AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(UserConfig.class);
            // 获取对象
            User getUser = annotationConfigApplicationContext.getBean("getUser", User.class);
    
            getUser.setName("banana");
    
            System.out.println(getUser.getName());
        }
    }
    
    • 基于@ComponentScan获取
    @Component
    class User {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    /**
     * @ComponentScan 注解,扫描的是带有 @Component 的类,将其解析加载至spring容器中
     */
    @ComponentScan("cn.linkpower.beanScan")
    class UserConfigScan {
    }
    public class Test {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(UserConfigScan.class);
            User user = applicationContext.getBean("user", User.class);
            System.out.println(user);
    
        }
    }
    

    基于能否刷新

    ClassPathXmlApplicationContextFileSystemXmlApplicationContext都能使用refresh()
    在这里插入图片描述
    在这里插入图片描述

    AnnotationConfigApplicationContext不支持refresh()

    refresh()做了什么

    ClassPathXmlApplicationContextFileSystemXmlApplicationContext都能使用refresh()。但是这个方法做了什么?

    看下列案例:

    package cn.linkpower.refresh;
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext 中容器的refresh()做了什么
     */
    public class Test {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-refresh.xml");
            System.out.println(applicationContext.getBean("user"));
            System.out.println(applicationContext.getBean("user"));
    
            // 执行一次 refresh()
            applicationContext.refresh();
            System.out.println(applicationContext.getBean("user"));
        }
    }
    

    查看控制台日志输出:
    在这里插入图片描述
    refresh()执行后,发现对象的内存地址信息发生了改变。相当于执行了refresh()后,Spring将容器重启了!

    代码案例

    参考:spring_container
    github 测试代码地址

    展开全文
  • 大学三年级,电子科技大学成都学院,云计算系期末考试AB卷带答案,还有课后复习题,给学弟学妹们复习使用
  • Spring IoC容器

    万次阅读 多人点赞 2019-05-31 11:40:01
    核心技术 发行版5.0.8 这一部分的指导文档覆盖了所有的完全集成到Spring框架中的...Spring框架拥有他自己的AOP框架,该技术在概念上是很容器理解的,并且成功的解决了在Java企业编程中的AOP需求的80%的点。 该框...

    核心技术

    发行版5.0.8


    这一部分的指导文档覆盖了所有的完全集成到Spring框架中的技术。

    这些技术中首要的便是Spring框架的控制反转(IoC)容器。Spring框架的IoC容器的全面的处理是与Spring的面向切面编程(AOP)的彻底实现紧密相关的。Spring框架拥有他自己的AOP框架,该技术在概念上是很容器理解的,并且成功的解决了在Java企业编程中的AOP需求的80%的点。

    该框架也提供了与AspectJ(当前最丰富的-就特征而言-并且是在Java企业空间中最成熟的AOP实现)的集成。

    1、IoC容器

    1.1、Spring IoC容器和beans的介绍

    这篇章节覆盖了IoC原则的Spring框架的的实现。IoC也被称为依赖注入(DI)。这是对象定义他们依赖关系的过程,也就是说,其他对象仅仅通过构造器参数,工厂方法参数,或者是在它被构造或从一个工厂方法中返回后设置到对象实例上的属性来工作。当他创建bean的时候,容器接着注入这些依赖。这个过程从根本上是相反的,因此,名称“控制反转”,bean它本身控制实例或者是通过使用类的直接构造器或一个机制,例如服务定位器模式来定位他的依赖。

    org.springframework.beansorg.springframework.context是Spring框架的IoC容器的基础。BeanFactory接口提供了一个先进的配置机制能够管理任何类型的对象。ApplicationContext是BeanFactory的一个子接口。它增加了与Spring AOP特征的更加简单的集成;消息资源处理(用于国际化中),事件发布和应用层特定的上下文例如用于Web应用中的WebApplicationContext。

    简而言之,BeanFactory提供了配置框架和基本的功能,并且ApplicationContext添加了更多的企业特定的功能。ApplicationContextBeanFactory的一个完备的超级,并且被唯一使用在描述Spring IoC容器的这一章节中。关于使用BeanFactory而不是ApplicationContext的更多信息,请查看1.16小节的BeanFactory的介绍

    在Spring中,那些组成你应用骨干的和那些被Spring IoC容器管理的对象被称为_beans_。一个Bean是一个被实例化的,组装的或其他被Spring IoC容器管理的对象。此外,简单的说,一个Bean就是你的应用程序中的对象中的一个。Beans和他们之间的依赖被映射到由容器使用的配置元数据中。

    1.2 容器概览

    接口org.springframework.context.ApplicationContext代表着Spring IoC容器并且负责实例化,配置和组装上述的beans。该容器使它的指令通过读取配置元数据作用于哪些对象去初始化,配置和组装。配置元数据表现在XML,Java注解或Java代码中。它允许您表达组成应用程度的对象以及这些对象之间的丰富的依赖性。

    很多ApplicationContext接口的实现为开箱即用的。在独立的应用程序中,创建一个ClassPathXmlApplicationContextFileSystemXmlApplicationContext的实例是通用的。虽然XML是定义配置元数据的原始格式,但是你可以通过提供少量的XML配置来指示容器使用Java注释或代码作为元数据格式,以声明性地支持这些附加的元数据格式。

    在大多数的应用程序场景中,不需要显式的用户代码来实例化Spring IoC容器的一个或多个实例。例如,在一个Web应用场景中,在应用程序的web.xml文件中,一个简单的样板Web描述符XML通常就足够了。如果你正在使用基于Eclipse开发环境的Spring工具套件,这个样板配置可以通过很少的鼠标点击或按键来简单的创建。

    下面的图表是Spring如何工作的高层视图。你的应用程序是与配置元数据绑定在一起的,所以在ApplicationContext被创建和初始化之后,你将会有一个完成的配置完成的和可执行的系统或应用程序。

    1.2.1 配置元数据

    正如前面的图标显示,Spring IoC容器消费了一种配置元数据的形式;这个配置元数据表示应用程序开发人员告诉Spring容器如何在应用程序中实例化,配置和组装对象。

    传统上,配置元数据是以简单直观的XML格式提供的,这篇章节主要使用它来传递Spring IoC容器的关键概念和特征。

    基于XML的元数据不是唯一允许的配置元数据的形式。Spring IoC容器本身与实际写入配置元数据的格式完全分离。这些天,很多开发者选择_基于Java的配置_用于他们的Spring应用程序。

    关于Spring容器中使用其他元数据格式的更多信息,请查看:

    • 基于注解的配置:Spring 2.5引入了基于注解的配置元数据的支持。
    • 基于Java的配置:从Spring 3.0开始,很多由Spring的Java配置项目提供的特征变成了核心Spring框架的一部分。因此,你可以在你的应用程序类之外通过使用Java而不是XML文件来定义beans。为了使用这些新的特征,请查看@Configuration@Bean@Import@DependsOn注解。

    Spring配置至少包含一个或超过一个容器必须要管理的bean定义。基于XML的配置元数据显示了这些beans在上层<beans/>元素中被配置成<bean/>元素。Java配置典型地在一个@Configuration类中使用@Bean注解方法。

    这些bean定义对应于组成你的应用程序的实际对象。典型地就是你定义服务层对象,数据访问对象(DAOs),表示对象,例如Struts Action实例,基础对象,例如HibernateSessionFactories,JMS Queues等。通常情况下,容器中不配置细粒度域对象,因为通常创建和加载域对象是DAOs和业务逻辑的责任。然而,你可以使用带有AspectJ的Spring集成来配置在IoC容器控制之外被创建的对象。请查看使用AspectJ来依赖注入Spring的域对象

    下列示例展示了基于XML配置元数据的基本结构:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="..." class="...">
            <!-- 在这里配置该bean的合作者和相关配置 -->
        </bean>
    
        <bean id="..." class="...">
            <!-- 在这里配置该bean的合作者和相关配置 -->
    
        </bean>
    
        <!-- 更多bean的配置 -->
    
    </beans>
    

    id属性是一个字符床,你可以使用该字符串来声明个人的bean定义。class属性定义bean的类型并且使用完全限定的类名。id属性的值指向协作对象。指向协作对象的XML没有在这个示例中展示出来;查看依赖获取更多信息。

    1.2.2 实例化一个容器

    实例化一个Spring IoC容器是直截了当的。位置路径或提供给AplicationContext构造器的路径实际是资源字符串,它允许容器从外部资源,如本地文件系统,从Java CLASSPATH等中加载配置元数据。

    ApplicationContext context = new ClassPathXmlApplicationContext("service.xml","daos.xml");
    

    在你学习Spring的IoC容器之后,你可能想要了解更多的关于Spring Resource的信息,正如在Resource中描述的,Resource提供了一个方便的机制用于从在URI语法中定义的位置中读取一个输入流。特别的,Resource路径被用于构建应用上下文,正如在Application contexts和Resouces paths中描述的。

    下面的示例展示了服务层对象(services.xml)配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- services -->
    
        <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
            <property name="accountDao" ref="accountDao"/>
            <property name="itemDao" ref="itemDao"/>
            <!-- additional collaborators and configuration for this bean go here -->
        </bean>
    
        <!-- more bean definitions for services go here -->
    
    </beans>
    

    下列示例展示了数据访问对象daos.xml文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="accountDao"
            class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
            <!-- additional collaborators and configuration for this bean go here -->
        </bean>
    
        <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
            <!-- additional collaborators and configuration for this bean go here -->
        </bean>
    
        <!-- more bean definitions for data access objects go here -->
    
    </beans>
    

    在前面的例子中,服务层包含类PetStoreServiceImpl,还有两个类型为JpaAccountDaoJpaItemDao(基于JPA的对象/关系映射标准)的数据访问对象。property name元素指向JavaBean属性的名称,ref指向另外bean定义的名称。idref的链接表示协作对象之间的依赖。想要了解配置一个对象依赖的更多细节,请查看依赖

    组合基于XML配置的元数据

    bean定义跨越多个XML文件是有用的。通常情况下,每一个独立的XML配置文件在你的架构中代表一个逻辑层或模块。

    你可以使用应用上下文构造器从这些XML片段中加载bean定义。这个构造器采用多个Resource位置,就像在上一小节中展示的那样。相应替代的,使用一个或多个<import/>元素从其他一个或多个文件中加载bean定义。例如:

    <beans>
        <import resource="services.xml"/>
        <import resource="resources/messageSource.xml"/>
        <import resource="/resources/themeSource.xml"/>
    
        <bean id="bean1" class="..."/>
        <bean id="bean2" class="..."/>
    </beans>
    

    在上面的示例中,额外的bean定义从三个文件中被加载上来:services.xml, messageSource.xmlthemeSource.xml。在做importing的时候,所有位置路径都是与定义文件相对的,所以在做importing的时候,service.xml必须在相同的目录或作为文件的类路径的位置,然而messageSource.xmlthemeSource.xml必须位于引入文件的位置下面的resource位置。正如你所看到的,主斜杠是被忽略的,但是考虑到这些路径都是相对的,不使用主斜杠是最好的形式。被引入文件的内容,包括顶层的<bean/>元素,根据Spring模式来说,都必须要是有效的XML的bean定义。

    使用相对路径"…/"在父目录中引入文件是可以的,但是不被推荐的。这样做在文件中也就是在当前应用外面创建了一个依赖。特别地,对于“classpath:”路径(例如,“classpath:…/service.xml”)来说这样的引用是不被推荐的,在这种情况下,运行时解决进程选择“最近的”类路径然后寻找他的父目录。类路径配置的改变可能会导致一个不同的,错误的目录的选择。

    你可以一直使用完全合格的资源位置而不是相对路径:例如,“file:C:/config/services.xml"或"classpath:/config/services.xml”。然而,要知道你正在连接你的应用的配置到特定的绝对路径位置。一般来说,对于这样绝对位置保持间接方向是更可取的,例如,通过在运行时对JVM系统属性进行解析的占位符"${…}"。

    导入指令是有beans名称空间本身提供的一个特征。除了普通的bean定义之外,更多的配置特征在由Spring,“context”和"util"名称空间提供的XML名称空间的选择中是可用的。

    Groovy Bean定义 DSL

    作为一个用于外部的配置元数据的例子,bean定义可以在Spring的Groovy Bean定义DSL中被表达,正如从Grails框架中所知道的那样。特别的,这样的配置将会和下面展示的一样作为一个结果存放在".groovy"文件中。

    beans {
        dataSource(BasicDataSource) {
            driverClassName = "org.hsqldb.jdbcDriver"
            url = "jdbc:hsqldb:mem:grailsDB"
            username = "sa"
            password = ""
            settings = [mynew:"setting"]
        }
        sessionFactory(SessionFactory) {
            dataSource = dataSource
        }
        myService(MyService) {
            nestedBean = { AnotherBean bean ->
                dataSource = dataSource
            }
        }
    }
    

    这个配置样式很大程度上与XML的bean定义相同,甚至支持Spring的XML配置命名空间。这也允许直接通过一个"importBeans"来导入XML bean定义文件。

    1.2.3. 使用容器

    ApplicationContext是能够维护不用bean及其依赖的注册表的高级工厂的接口。使用方法T getBean(String name,Class<T> requiredType),你可以检索你的bean的实例。

    // 创建和配置bean
    ApplicationContext context = new ClassPathXmlApplicationContext("services.xml","daos.xml");
    // 检索配置的实例
    PetStoreService service = context.getBean("petStore",PetStoreService.class);
    // 使用配置的实例
    List<String> userList = service.getUsernameList();
    

    使用Groovy配置,bootstrapping看上去很相似,仅仅是一个不同的上下文实现类,该实现类是Groovy认识的。(但是也理解XML的bean定义)。

    ApplicationContext context = new GenericGroovyApplicationContext("services.groovy","daos.groovy");
    

    最灵活的变量便是与读者代理(reader delegates),例如用于XML文件的XmlBeanDefinitionReader相结合的GenericApplicationContext。

    GenericApplicationContext context = new GenericApplicationContext();
    new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
    context.refresh();
    

    或者是用于Groovy文件的GroovyBeanDefinitionReader

    GenericApplicationContext context = new GenericApplicationContext();
    new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
    context.refresh();
    

    这样的阅读者代理可以混合的,并且可以在相同的应用上下文相匹配,如果希望的话,可以从不同的配置源读取bean定义。

    然后你可以使用getBean来取回你的bean的实例。ApplicationContext接口有一些其他的方法来取回beans,但是理想情况下你的应用代码中应该永远不使用他们。事实上,你的应用代码不应该有对getBean()方法的调用,因此,也不应该有对Spring API的依赖。例如,Spring伴随着web框架的集成为多个web框架组件,例如控制器和JSF管理beans,的依赖注入,允许你通过元数据在一特定的bean上声明一个依赖(例如autowiring注解)。

    1.3 Bean概览

    一个Spring IoC容器管理一个或多个beans。这些beans被你提供给容器的配置元数据创建(例如,XML中的<bean/>定义)。

    在容器本身,这些bean定义被代表成BeanDefinition对象,该对象包含下述元数据:

    • 包限定类名:特别的,被定义的bean的实际实现类。
    • Bean行为配置元素,这标记bean在容器内的行为(范围,声明周期回调等等)。
    • 该bean需要的其他bean的引用来做这个工作。这些引用也被称为协作者或依赖。
    • 在新的创建的对象中要去设置的其他配置设定 - 例如,池子的大小限制,或管理连接池的bean中要使用的连接数。

    这个元数据被翻译成一组属性来组成每一个bean定义。下面的表格描述了这些属性:

    属性在…中描述
    Class实例化Beans
    Name命名Beans
    ScopeBeans范围
    Constructor arguments依赖注入
    Properties依赖注入
    Autowiring modeAutowiring 协作者
    Lazy initialization mode懒实例化Beans
    Initialization method实例化回调
    Destruction method析构回调

    除了包含如何创建一个特定bean的bean定义之外,ApplicationContext实现也允许在容器外被创建的现存的对象的注册。这可以通过getBeanFactory()方法访问ApplicationContext的BeanFactory来实现,这返回BeanFactory的DefaultListableBeanFactory的实现。DefaultListableBeanFactory支持通过registerSingleton(..)registerBeanDefinition(..)来注册。然而,典型的应用程序只使用通过常规beans定义元数据定义的beans.

    Bean元数据和人工提供的单实例需要尽早被注册,以便容器在autowiring阶段和其他自动创建阶段正确的解释他们。然而,重载现存的元数据和现存的单一实例在一定程度上是被支持的,新的beans的注册在运行时(同时访问工厂)是不被官方支持的,并且可能会导致同时访问异常,在bean容器中的 不一致的状态或两者都会发生。

    1.3.1 命名Beans

    每一个bean都有一个或多个标识符。这些标识符在承载这个bean的容器中必须要是唯一的。一个bean通过仅有一个标识符。然而,如果他需要超过一个标识符,剩余的标识符被认为成别名。

    在基于XML的配置元数据中,你是用id属性,name属性或两者都使用来指定bean标识符。id属性允许你指定确切的一个id。按照惯例,这些名字是字母数字的(‘myBean’,'someService’等等),但是他们也可以包含特殊的字符。如果你想要为该bean引入其他别名,你也可以在name属性中指定他们,通过逗号(,),分号(;)或空格分隔。作为历史注释,在Spring 3.1之前的版本中,id属性本定义在xsd:id类型上,这约束了可能的字符。至于3.1,他被定义为xsd:string类型上。注意,bean id的唯一性依然被容器是强制的,虽然不再被XML解析器强制。

    你不需要必须为一个bean提供一个name或一个id属性。如果你不明显提供一个nameid属性,容器将会为该bean生成一个唯一的名字。然而,如果你想要通过name引用那个bean,通过使用ref元素或服务定位器搜寻,你必须要提供一个名称。不提供一个名称的动机与使用inner beansautowiring 协作者相关。

    Bean命名惯例:当命名bean的时候,惯例就是为实例使用标准的Java惯例。也就是说,bean名称以小写字母开始,并且以驼峰式命名。这样命名的例子包含accountManageraccountServiceuserDaologinController等等。

    一致的命名bean使您的配置更易于阅读和理解。如果你使用Spring AOP,当将建议应用于与名称相关的一组bean时,它有很大的帮助。

    随着在类路径中组件的搜索,Spring 为未命名的组件生成bean名称,遵循上述描述的规则:基本上,采用简单的类命名并且转换这些初始的字符为小写字母。然而,在特别情况下,当超过一个字符并且第一个和第二个字符都是大写情况下,原来的例子被保存下载。这些是与定义在java.beans.Introspector.decapitalize的规则是一样的。

    在Bean定义之外为一个Bean定义别名

    在一个bean定义本身,你可以为bean提供超过一个名称,通过使用由id属性和name属性中任何其他名称指定的最多一个名称的组合。这些名称相当于相同bean的别名,并且在一些情况下是有用的,例如,在一个应用中让一个组件通过使用指定到那个组件的bean名称来引用一个通用的依赖。

    然而,在bean实际定义的地方指定所有的别名并不总是足够的。有时需要为在别处定义的bean引入别名。在大系统中,这是常用的例子,在这样的系统中,配置被分离到每一个子系统中,每一个子系统都有他自己的对象定义组。在基于XML的配置元数据中,你可以使用<alias/>元素来完成。下面的例子展示了如何去做:

    <alias name="fromName" alias="toName" />
    

    在这个例子中,一个bean被命名为fromName,在使用这个别名定义之后,被引用为toName

    例如,用于子系统A的配置元数据通过名称subsystemA-dataSource来引用一个数据源。用于子系统B的配置元数据通过名称subsystemB-dataSource来引用一个数据源。当使用这两个子系统组成主应用的时候,该主应用程序通过名称myApp-dataSource引用数据源。为了让三个名称指向同一个对象,你可以添加下面的别名定义到配置元数据中:

    <alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
    <alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
    

    现在每一个组件和主程序可以通过唯一的名称引用数据源,并保证不会与任何其他定义(有效创建命名空间)冲突,因此他们指向相同的bean。

    Java配置:如果你是用Java配置,@Bean注解可以被使用来提供别名。查看使用@Bean注解了解详情。

    1.3.2 实例化Bean

    一个bean定义实际上是创建一个或多个对象的菜单。当容器被询问的时候,它搜索菜单找到被命名的bean ,然后使用被该bean定义封装的配置的元数据来创建或获取一个真实的对象。

    如果你使用基于XML的配置元数据,你指定<bean/>元素的class属性中要被实例化的对象的类型。这个class属性(该属性内部是位于BeanDefinition实例的Class属性)通常是强制的。(对于异常情况,查看通过使用一个实例工厂方法实例化Bean定义继承)你可以通过下述任一一个方法来使用Class属性。

    • 典型的,在容器本身直接创建bean的情况下,通过调用他的构造函数来创建bean类,与Java编码的new操作符是一样的,从而指定要被构造的bean类。

    • 指定包含static工厂方法的实际的类,该方法被调用来创建对象,在不太通常的情况下,容器调用类中的static工厂方法来创建bean。从static工厂方法中返回的对象类型可能完全是相同的类或是其他类。

    展开全文
  • 常见的Web容器

    千次阅读 2020-04-22 13:59:41
    开发人员可以将Jetty容器实例化成一个对象,可以迅速为一些独立运行(stand-alone)的Java应用提供网络和web连接。 特点:简单、可扩展性、高效、嵌入式、插入式 5. Nginx Nginx (engine x) 是一个高性能的HTTP和...
  • 例如,Traversable类 的map方法会返回另一个Traversable对象作为结果,但是这个结果类型在子类中被重写了。例如,在一个List上调用map会又生成一个List,在Set上调用会再生成一个Set,以此类推。 scala > List ( 1 ...
  • C++中STL各容器详解

    千次阅读 多人点赞 2019-03-09 17:10:57
    2)迭代器(Iterator),提供了访问容器对象的方法。例如,可以使用一对迭代器指定list或vector中的一定范围的对象。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。但是,迭代器也可以...
  • Python基础之:Python中的内部对象

    千次阅读 2021-04-06 09:04:32
    Python中内置了很多非常有用的对象,本文将会介绍Python中的内置函数,内置常量,内置类型和内置异常。
  • 因为vector容纳着“容纳着”其他对象,所以也常被称作容器。 2、想要使用vector必须包含适当的头文件。  #include &lt;vector&gt;  using std::vector;//其定义在命名空间std中 3、vector是一个类...
  • Servlet没有main方法,不能独立运行,它必须被部署到Servlet容器中,由容器来实例化和调用 Servlet的方法(如doGet()和doPost()),Servlet容器在Servlet的生命周期内包容和管理Servlet。在JSP技术 推出后,管理和...
  • 第十一章 关联容器 练习11.1 描述map和vector的不同。 解: map 是关联容器, vector 是顺序容器。 练习11.2 分别给出最适合使用list、vector、deque、map以及set的例子。 解: list:双向链表,适合频繁插入...
  • 下列关于JavaBean的说法正确的是: A:Java文件与Bean所定义的类名可以不同,但一定要注意区分字母的大小写 B:在JSP文件中引用Bean,其实就是用语句 C:被引用的Bean文件的文件名后缀为.java D:Bean文件放在...
  • java面试题32:Java网络程序设计中,下列正确的描述是() A:Java网络编程API建立在Socket基础之上 B:Java网络接口只支持tcP以及其上层协议 C:Java网络接口只支持UDP以及其上层协议 D:Java网络接口支持IP...
  • 本文代码实现基本按照《数据结构》课本目录顺序,外加大量的复杂算法实现,一篇文章足够。换你一个收藏了吧?
  • 渗透测试-Docker容器

    千次阅读 2020-06-11 16:12:37
    Docker是时下热门的容器技术,相信作为一名开发人员,你一定听说过或者使用过,很多人会把Docker理解为一个轻量级虚拟机,但其实Docker与虚拟机(VM)是两种不同的计算机虚拟化技术,也有很多人会觉得,有了虚拟机,...
  • Java基础——GUI——Swing中常用容器和组件

    万次阅读 多人点赞 2019-06-20 09:56:08
    1.swing中常用容器 (1)JFrame 常用方法: 1.构造方法: 2.设置窗体可见: 3.设置点击窗体的执行的操作:. 4.设置窗体的大小和位置 等价于上面两个方法 不管窗体多大,窗体运行起来都会出现在...
  • 判断题 1-1 可以通过下标随机访问向量vector中的元素。 T #include <iostream> #include <vector> using namespace std;...1-2 当向量对象的内存用完之后,就会产生越界错误。 F 1-3 ve.
  • java中Statement 对象

    千次阅读 2021-02-26 12:38:05
    1、创建Statement对象建立...Statement对象用 Connection 的方法 createStatement 创建,如下列代码段中所示:Connection con = DriverManager.getConnection(url, "sunny", "");Statement stmt = con.createStatem...
  • C++提高编程(四)—— STL函数对象

    千次阅读 2021-04-14 18:18:08
    1. 函数对象1.1 函数对象概念1.2 函数对象使用2. 谓词2.1 谓词概念2.2 一元谓词2.3 二元谓词3. 内建函数对象3.1 内建函数对象意义3.2 算术仿函数3.3 关系仿函数3.4 逻辑仿函数 1. 函数对象 1.1 函数对象概念   ...
  • 在JavaScript从一门只被用来编写零星的简单的表单验证代码的玩具语言变成日益流行的Web应用可取代的开发语言的过程中,脚本的作者们也逐渐学习和习惯了被视为软件开发正统的面向对象编程。等到盛极而衰,面向对象...
  • 容器作业及答案

    千次阅读 2020-04-05 15:38:16
    容器作业 一、填空题 1.Java集合框架提供了一套性能优良、使用方便的接口和类,包括Collection和Map两大类,它们都位于 java.util 包中 2.队列和堆栈有些相似,不同之处在于 。 3. 结构是一种由多个节点组成的线性...
  • Minio 搭建对象存储服务

    千次阅读 2020-08-04 15:19:58
    文章目录1 mino简介2 环境3 部署3.1 获取程序3.2 存储类别3.3 挂载硬盘3.4 单机部署3.4.1 部署及测试3.4.2 作为Linux Service启动3.5 分布式集群扩容方案3.5.1 部署及测试3.5.2 作为Linux Service启动3.6 多机部署,...
  • 【C++】顺序容器

    千次阅读 2016-11-08 23:44:21
    一个容器就是一些特定类型对象的集合。顺序容器为程序员提供了控制元素存储和访问顺序的能力。这种顺序依赖于元素的值,而是与元素加入容器时的位置相对应。顺序容器的概述下表列出了标准库中的顺序容器,所有顺序...
  • kubernetes容器编排系统介绍

    万次阅读 2016-10-18 11:12:26
    Kubernetes作为容器编排生态圈中重要一员,是Google大规模容器管理系统borg的开源版本实现,吸收借鉴了google过去十年间在生产环境上所学到的经验与教训。 Kubernetes提供应用部署、维护、 扩展机制等功能,利用...
  • 第1页《Python程序设计》期中考试卷一、...关于Python内存管理,下列说法错误的是()A、变量不必事先声明B、变量无须先创建和赋值而直接使用C、变量无须指定类型D、可以使用del释放资源3、下列哪种情况会导致Pyt...
  • 面向对象复习总结

    千次阅读 2016-12-07 21:11:27
    Ø 从问题域中客观存在的事物出发来构造软件系统,用对象作为对这些事物的抽象表示,并作为系统的基本构成单位。 (对象) Ø 用对象的 属性 表示事物的 静态特征 ;用对象的 服务 (操作)表示事物的 动态特征...
  • vector容器用法详解

    千次阅读 2018-09-11 11:23:36
    vector类称作向量类,它实现了动态数组,用于元素数量变化的对象数组。像数组一样,vector类也用从0开始的下标表示元素的位置;但和数组不同的是,当vector对象创建后,数组的元素个数会随着vector对象元素个数的...
  • 容器技术-Dockerfile详解

    千次阅读 2021-01-13 09:31:11
    我们可以把对容器的所有操作命令都记录到一个文件里,就像是写脚本程序。 之后用 docker build 命令以此文件为基础制作一个镜像,并会自动提交到本地仓库。 这样的话镜像的构建会变的透明化,对镜像的维护起来也更加...
  • 目录Java编程思想(一)第1~4章:概述Java编程思想(二)第5章:初始化和清理Java...内部类Java编程思想(八)第11章:持有对象Java编程思想(九)第12章:异常Java编程思想(十)第13章:字符串Java编程思想(十一...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 35,501
精华内容 14,200
关键字:

下列对象不能作为容器的是