步步高电商Java面试题-1(高级)
博客专区 > 黄状 的博客 > 博客详情
步步高电商Java面试题-1(高级)
黄状 发表于3年前
步步高电商Java面试题-1(高级)
  • 发表于 3年前
  • 阅读 442
  • 收藏 13
  • 点赞 0
  • 评论 1

腾讯云 技术升级10大核心产品年终让利>>>   

摘要: 某电商网站Java面试题: (1)基本数据结构 (2)几种常用集合的特点,不同点(hashmap list set 链表) (3)解释Java多线程锁的机制 (4)常见分布式框架,比如dubbo,Zookeeper是否用过,用过哪些功能,不同框架有何优缺点 (5)常见设计模式,对哪些设计模式有深入理解? (6)spring底层代码,源代码看过没有,哪方面有深入了解?

某电商网站Java面试题:

(1)基本数据结构

1、栈和队列(引用来源:http://blog.csdn.net/baoyiming1991/article/details/6266339)

栈和队列是两种特殊的线性表,它们的逻辑结构和线性表相同,只是其运算规则较线性表有更多的限制,故又称它们为运算受限的线性表。

LinkedList数据结构是一种双向的链式结构,每一个对象除了数据本身外,还有两个引用,分别指向前一个元素和后一个元素,和数组的顺序存储结构(如:ArrayList)相比,插入和删除比较方便,但速度会慢一些。

栈的定义

 栈(Stack)是限制仅在表的一端进行插入和删除运算的线性表。

  (1)通常称插入、删除的这一端为栈顶(Top),另一端称为栈底(Bottom)。

  (2)当表中没有元素时称为空栈。

  (3)栈为后进先出(Last In First Out)的线性表,简称为LIFO表。

  栈的修改是按后进先出的原则进行。每次删除(退栈)的总是当前栈中"最新"的元素,即最后插入(进栈)的元素,而最先插入的是被放在栈的底部,要到最后才能删除。

栈的实现

package ds.linerlist;
/**
 * 栈的实现
 * @param <E> 
 * @author <a href="mailto:bao.yiming@live.cn" mce_href="mailto:bao.yiming@live.cn">Bao Yiming</a>
 */
public class Stack<E> {
    private Object[] data = null;
    private int capacity; // capacity: 栈的容量
    private int top; // top: 栈顶指针

    Stack() {
        this(10);
    }
    /**
     * 初始化栈,声明保存数据的数组大小。
     * @param initialSize  栈的初始化大小
     */
    Stack(int initialSize) {
        if (initialSize >= 0) {
            this.capacity = initialSize;
            data = new Object[initialSize];
            top = 0;
        } else {
            throw new RuntimeException("初始化大小不能小于0:" + initialSize);
        }
    }
    /**
     * 判断栈是否为空
     * @return
     */
    boolean empty() {
        return top == 0 ? true : false;
    }
    /**
     * 获取栈顶元素的内容,但是不弹出
     * @return
     */
    E peek() {
        return (E) data[top - 1];
    }
    /**
     * 弹出栈顶元素
     * @return
     */
    E pop() {
        E e = (E) data[top - 1];
        --top;
        return e;
    }
    /**
     * 在栈顶插入元素
     * @param e 待插入的元素
     * @return
     */
    boolean push(E e) {
        ensureCapacity();
        data[top] = e;
        ++top;
        return true;
    }
    /**
     * 检查存储数据的数组容量,如果数组已经满,则扩充容量;否则不操作。
     */
    private void ensureCapacity() {
        int index;
        if (top == capacity) {
            capacity *= 2;
            Object[] newData = new Object[capacity];
            for (index = 0; index < top; ++index) {
                newData[index] = data[index];
            }
            data = newData;
        }
    }
    @Override
    public String toString() {
        String str = "";
        for (int index = 0; index <= top - 1; ++index) {
            str += (data[index] + " ");
        }
        return str;
    }
}


队列定义

   队列(Queue)是只允许在一端进行插入,而在另一端进行删除的运算受限的线性表

  (1)允许删除的一端称为队头(Front)。

  (2)允许插入的一端称为队尾(Rear)。

  (3)当队列中没有元素时称为空队列。

  (4)队列亦称作先进先出(First In First Out)的线性表,简称为FIFO表。

//使用removeFirst()方法,返回队列中第一个数据,然后将它从队列中删除

队列的实现

package ds.linerlist;
/**
 * 队列的实现
 * @param <E> 
 * @author <a href="mailto:bao.yiming@live.cn" mce_href="mailto:bao.yiming@live.cn">Bao Yiming</a>
 */
public class Queue<E> {
    Object[] data = null;
    private int capacity; // capacity: 队的容量
    private int tail; // tail: 队尾指针
    /**
     * 初始化为声明大小,则设置为10。
     */
    Queue() {
        this(10);
    }
    /**
     * 初始化队列,声明保存数据的数组大小。
     * @param initialSize 队列的初始化大小
     */
    Queue(int initialSize) {
        if (initialSize >= 0) {
            this.capacity = initialSize;
            data = new Object[initialSize];
            tail = 0;
        } else {
            throw new RuntimeException("初始化大小不能小于0:" + initialSize);
        }
    }
    /**
     * 判断队列是否为空
     * @return
     */
    public boolean empty() {
        return tail == 0 ? true : false;
    }
    /**
     * 在队尾插入元素
     * @param e 待插入的元素
     * @return
     */
    public boolean add(E e) {
        ensureCapacity();
        data[tail] = e;
        ++tail;
        return true;
    }
    /**
     * 获取队首的元素内容,但不将该元素出队。
     * @return
     */
    public E peek() {
        return (E) data[0];
    }
    /**
     * 将队首元素出队。
     * @return
     */
    public E poll() {
        E e = (E) data[0];
        for (int index = 1; index < tail; ++index) {
            data[index - 1] = data[index];
        }
        data[tail - 1] = null;
        --tail;
        return e;
    }
    /**
     * 检查存储数据的数组容量,如果数组已经满,则扩充容量;否则不操作。
     */
    private void ensureCapacity() {
        int index;
        if (tail == capacity) {
            capacity *= 2;
            Object[] newData = new Object[capacity];
            for (index = 0; index < tail; ++index) {
                newData[index] = data[index];
            }
            data = newData;
        }
    }
    @Override
    public String toString() {
        String str = "";
        for (int index = 0; index < tail; ++index) {
            if (data[index] != null) {
                str += (data[index] + " ");
            }
        }
        return str;
    }
}



2、链表(引用来源:http://www.cnblogs.com/zhoufanking/archive/2012/03/20/2408212.html)

    按链表的组织形式分有ArrayListLinkList两种。ArrayList内部其实是用数组的形式实现链表,比较适合链表大小确定或较少对链表进行增删操作的情况,同时对每个链表节点的访问时间都是constant;而LinkList内部以一个List实现链表,比较适合需要频繁对链表进行操作的情况,对链表节点的访问时间与链表长度有关O(N)
    另外,根据实现形式可以分为直接式(想不出什么合适的名字,姑且这样吧)和使用Iterator(迭代模式)两种方法。直接式的实现方法和C/C++中的写法差不多;而使用Iterator时,需要实现java.lan中的Iterable接口(或者也可以自己在链表内部定义自己的Iterator method
    关于直接实现链表好,还是使用迭代模式好这个问题,有两个帖子可以看看:
     1.  http://topic.csdn.net/t/20050722/17/4162226.html这篇帖子,虽然短小,但也说出了一些使用迭代模式设计链表的好处;
     2.后来又看到了http://www.java63.com/design_pattern/iterator_pattern.html这篇帖子,觉得说的比较清楚了。
    我这里再捡主要的说一下:
        使用迭代模式的优点:
           1,实现功能分离,简化容器接口。让容器只实现本身的基本功能,把迭代功能委让给外部类实现,符合类的设计原则。
           2,隐藏容器的实现细节。
           3,为容器或其子容器提供了一个统一接口,一方面方便调用;另一方面使得调用者不必关注迭代器的实现细节。
           4,可以为容器或其子容器实现不同的迭代方法或多个迭代方法。
    我觉得第4点说的很好,对于一堆数据而言,不同的人(或业务逻辑)使用它的方式也不尽相同,定义一个theIterator继承Iterator,不仅提供nexthasNext 以及remove这个最小的操作集合,同时也可以提供更多的其它方法。在theIterator的实现类中,具体实现不同的迭代方法,比如顺序、逆序或根据其它语义进行遍历等,再通过类厂的方式将一个theIterator实现的对象交给用户使用。
下面我给出两个例子:
    首先是直接式实现链表的例子,这个例子只提供了几种链表操作的基本方法,仅用于示意:

public class MyList<AnyType>  
{
    private int theSize;

    private Node<AnyType> Header;

    private Node<AnyType> Tail;

    public MyList(){}

    public void add(AnyType item){}

    public boolean isEmpty(){}

    public int size(){}

    public AnyType get( int idx){}

    public void print(){}
    
    private class Node<AnyType>
    {
    
        public  Node<AnyType> pre;
    
        public  Node<AnyType> next;
    
        public  AnyType      data;
    
        public Node(AnyType d, Node<AnyType>p, Node<AnyType> n){}
    
        public Node(){}
    
    }

}


Node<AnyType>类定义了双向链表中节点的结构,它是一个私有类,而其属性和构造函数都是公有的,这样,其父类可以直接访问其属性,而外部类根本不知道Node类的存在。Data是节点中的数据与,pre指向前一个Node节点,next指向后一个Node节点。其构造函数的实现如下,不解释:

public Node(AnyType d, Node<AnyType>p, Node<AnyType> n){

this.data = d;

this.pre = p;

this.next = n;

}

public Node(){

this.data = null;

this.pre = null;

this.next = null;

}

下面我们看一下链表的构造函数实现:

public MyList(){

theSize = 0;

Header = new Node<AnyType>(null,null,null);

Tail   =  new Node<AnyType>(null,Header,null);

Header.next = Tail;

}

  我们构造了一个带有头、尾节点的双向链表,头节点的Next指向尾节点,为节点的pre指向头节点。链表长度起始为0

继续贴上链表类其它方法的实现,不解释了,应该比较清楚:

public void add(AnyType item){

     Node<AnyType> aNode = new Node<AnyType>(item,null,null);

     Tail.pre.next = aNode;

     aNode.pre = Tail.pre;

     aNode.next = Tail;
   
     Tail.pre = aNode;

     theSize++;

}

public boolean isEmpty(){

     return ( theSize == 0);

}

public int size(){

     return theSize;

}

public AnyType get( int idx){

     if(idx > theSize-1 || idx < 0)

           throw new IndexOutOfBoundsException();

 

     Node<AnyType> current = new Node<AnyType>(null,Header,null);

 

    for(int i = 0; i<idx; i++)

       current = current.next;

   return current.data;

}

public void print(){

    Node<AnyType> current = Header.next;

while(current.next != null){
   //如果AnyType是你自己定义 //的数据类型,那么请务必提供
   //一个toString方法,要么就不
   //要在链表里实现print方法。
  System.out.println(current.data.toString()); 
  
  current = current.next;

}

}


 第二个例子是用迭代方法实现链表的例子,下面是类定义:

public class MyListItr<Type> implements Iterable<Type> {

private class Node<Type>{

public Type data;

public Node<Type> pre;

public Node<Type> next;

public Node(){}

public Node(Type d, Node<Type> p, Node<Type> n){}

}


private Node<Type> Header;

private Node<Type> Tail;

private int theSize;

public MyListItr(){}

public void add(Type item){}

public void print(){}


public int size(){}

@Override

public Iterator<Type> iterator() {}


private class MyListIterator implements Iterator<Type>{

@Override

public boolean hasNext() {}

@Override

public Type next() {}

@Override

public void remove() {}

}

}


    这里主要说一下与前面例子不同的地方:MyListItr类实现了java.lan.Itrable接口,因此我们必须实现其public Iterator<Type> iterator() {}方法,它的返回值是一个迭代器对象,那么我就定义一个内部私有类——MyListIterator,这个类实现了iterator接口。在public Iterator<Type> iterator() 方法中,我就构造这么一个MyListIterator的对象并返回。

    Iterator接口有三个方法是我们必须实现的,hasNextnextremove,这是迭代操作的最小集合了。对于不同的迭代器执行方式,我们可以定义多个类似MyListIterator这样的实现类,只要在链表类中的iterator() 方法中把具体实现类的对象返回给用户就OK了。

   罗嗦两句:我感觉如果我们定义链表类时,如果不继承Iterable接口,而是直接在类内部提供 public theIterator Iterator() 方法,theIterator是一个自定义接口,继承自Iterator接口,这样我们可以提供遵循theIterator接口的各种迭代方式的不同实现类,并通过一个类厂方法在链表的public theIterator Iterator() 方法中把具体迭代实现类的对象交给用户,以此就可以根据不同的需求,提供链表的多种迭代方式。我觉得这种方法是可行的,但并没有具体实验。

   下面只贴出链表iterator()方法和迭代实现类的MyListIterator代码,其它方法就不重复贴了:

@Override

 Iterator<Type> iterator() {

  MyListIterator();

}

  MyListIterator  Iterator<Type>{

Node<Type> current = Header.next;

@Override

  hasNext() {

 (current != Tail);

}

@Override

 Type next() {

(!hasNext())

  IndexOutOfBoundsException();  

  Type item = current.data;

  current = current.next;

 item;
}

@Override

  remove() {

( !hasNext())

  NoSuchElementException();

  current.pre.next = current.next;
  current.next.pre = current.pre;
  current = current.next;

  theSize--;
}

}


测试代码:

   其中 ItemInfo的定义如下:

public class ItemInfo {

 
String name;

String score;

public ItemInfo(String n, String s){

name = n; score = s;

}

public ItemInfo(){

name = null; score = null;

}

public String toString(){

String str = name +"   "+ score;

return str;

}

}

    至此,Java实现链表的方法基本上也就清楚了


(2)几种常用集合的特点,不同点(hashmap list set 链表)

1.Map接口

  请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。

2.List接口

List是有序的Collection,用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。

  和下面要提到的Set不同,List允许有相同的元素。

3.Collection接口

  两个标准的构造函数:无参数的构造函数用于创建一个空的Collection;有一个Collection参数的构造函数用于创建一个新的Collection

  如何遍历:

Iterator it = collection.iterator(); //获得一个迭代子
while(it.hasNext()) {
     Object obj = it.next();//得到下一个元素
}


  由Collection接口派生的两个接口是List和Set。


4.ArrayList类

      ArrayList实现了可变大小的数组。

  它允许所有元素,包括null。

       ArrayList没有同步。

5.Hashtable类

Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。

  添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。

使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是“one”,“two”,“three”:

Hashtable numbers = new Hashtable();
numbers.put(“one”, new Integer(1));
numbers.put(“two”, new Integer(2));
numbers.put(“three”, new Integer(3));


  要取出一个数,比如2,用相应的key:

Integer n = (Integer)numbers.get(“two”);

System.out.println(“two =”+ n);

  由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)==true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。

  如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。

Hashtable是同步的。

   6.Stack类

Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

7.Set接口

Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)==false,Set最多有一个null元素。

  很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。

  请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)==true将导致一些问题。

8.WeakHashMap类

WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。

9.Vector类

Vector非常类似ArrayList,但是Vector是同步的。

10.HashMap类

HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。

11.LinkedList类

  允许null元素。

  此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。

  注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:

List list = Collections.synchronizedList(new LinkedList(...));

(3)解释Java多线程同步和锁

同步和锁定

 1、锁的原理

Java中每个对象都有一个内置锁

当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。

 

    当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。

    一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。

    释放锁是指持锁线程退出了synchronized同步方法或代码块。

关于锁和同步,有一下几个要点:

1)、只能同步方法,而不能同步变量和类;

2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?

3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。

4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。

5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。

6)、线程睡眠时,它所持的任何锁都不会释放。

7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。

8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。

9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:

    public int fix(int y) {
        synchronized (this) {
            x = x - y;
        }
        return x;
    }

当然,同步方法也可以改写为非同步方法,但功能完全一样的,例如:

    public synchronized int getX() {
        return x++;
    }

    public int getX() {
        synchronized (this) {
            return x;
        }
    }

效果是完全一样的。

(4)常见分布式框架,比如dubbo,Zookeeper是否用过,用过哪些功能,不同框架有何优缺点

(引用来源:http://www.cnblogs.com/Javame/p/3632473.html)

1. Dubbo是什么?

    Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上是个服务调用的东东,说白了就是个远程服务调用的分布式框架(告别Web Service模式中的WSdl,以服务者与消费者的方式在dubbo上注册)

其核心部分包含:

1. 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。

2. 集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。

3. 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

2. Dubbo能做什么?

1.透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。      

2.软负载均衡及容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点。

3. 服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。

Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。

之前使用Web Service,我想测试接口可以通过模拟消息的方式通过soapui或LR进行功能测试或性能测试。但现在使用Dubbo,接口之间不能直接交互,我尝试通过模拟消费者地址测试,结果不堪入目,再而使用jmeter通过junit进行测试,但还是需要往dubbo上去注册,如果再不给提供源代码的前提下,这个测试用例不好写啊....

3. dubbo的架构

dubbo架构图如下所示:

节点角色说明:

       Provider: 暴露服务的服务提供方。

       Consumer: 调用远程服务的服务消费方。

       Registry: 服务注册与发现的注册中心。

       Monitor: 统计服务的调用次调和调用时间的监控中心。

       Container: 服务运行容器。

这点我觉得非常好,角色分明,可以根据每个节点角色的状态来确定该服务是否正常。

调用关系说明:

0 服务容器负责启动,加载,运行服务提供者。

1. 服务提供者在启动时,向注册中心注册自己提供的服务。

2. 服务消费者在启动时,向注册中心订阅自己所需的服务。

3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

dubbo的容错性显而易见,性能方面还没有还得及测,我们系统某页面需要掉5次接口,本来想建议做个缓存,但业务关系不能采纳,还需要研究下dubbo的性能调优问题...

4. dubbo使用方法。

Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。如果不想使用Spring配置,而希望通过API的方式进行调用(不推荐)

下面我们就来看看spring配置方式的写法:

服务提供者:

1. 下载zookeeper注册中心,下载地址:http://www.apache.org/dyn/closer.cgi/zookeeper/  下载后解压即可,进入D:\apach-zookeeper-3.4.5\bin,

双击zkServer.cmd启动注册中心服务。

2. 定义服务接口: (该接口需单独打包,在服务提供方和消费方共享)

 下面这个例子不错,写的很详细可以做个model.

package com.unj.dubbotest.provider;  
  
import java.util.List;  
  
public interface DemoService {  
  
    String sayHello(String name);  
  
    public List getUsers();  
  
}


在服务提供方实现接口:(对服务消费方隐藏实现)

package com.unj.dubbotest.provider;  
  
import java.util.ArrayList;  
import java.util.LinkedList;  
import java.util.List;  
  
  
public class DemoServiceImpl implements DemoService{  
      
     public String sayHello(String name) {  
            return "Hello " + name;  
     }  
     public List getUsers() {  
         List list = new ArrayList();  
         User u1 = new User();  
         u1.setName("jack");  
         u1.setAge(20);  
         u1.setSex("男");  
           
         User u2 = new User();  
         u2.setName("tom");  
         u2.setAge(21);  
         u2.setSex("女");  
           
         User u3 = new User();  
         u3.setName("rose");  
         u3.setAge(19);  
         u3.setSex("女");  
           
         list.add(u1);  
         list.add(u2);  
         list.add(u3);  
         return list;  
     }  
}

用Spring配置声明暴露服务:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
        http://www.springframework.org/schema/beans/spring-beans.xsd  
        http://code.alibabatech.com/schema/dubbo  
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd  
        ">  
   
    <!-- 具体的实现bean -->  
    <bean id="demoService" class="com.unj.dubbotest.provider.DemoServiceImpl" />  
      
    <!-- 提供方应用信息,用于计算依赖关系 -->  
    <dubbo:application name="xixi_provider"  />  
   
    <!-- 使用multicast广播注册中心暴露服务地址   
    <dubbo:registry address="multicast://224.5.6.7:1234" />-->  
    
    <!-- 使用zookeeper注册中心暴露服务地址 -->  
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />   
    
    <!-- 用dubbo协议在20880端口暴露服务 -->  
    <dubbo:protocol name="dubbo" port="20880" />  
   
    <!-- 声明需要暴露的服务接口 -->  
    <dubbo:service interface="com.unj.dubbotest.provider.DemoService" ref="demoService" />  
      
</beans>

 

加载Spring配置,启动服务:

package com.unj.dubbotest.provider;  
  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class Provider {  
   
    public static void main(String[] args) throws Exception {  
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"applicationContext.xml"});  
        context.start();  
   
        System.in.read(); // 为保证服务一直开着,利用输入流的阻塞来模拟  
    }  
}

服务消费者:

 applicationContext-dubbo.xml 中注册自己需要调用的接口,我刚开始测试的时候需要的接口很多,所以把这个文件写的满满的,后来熟悉了把接口按业务类型分开,写了N多个 applicationContext-dubbo-***.xml 简练多了 》。 


1.通过Spring配置引用远程服务:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
        http://www.springframework.org/schema/beans/spring-beans.xsd  
        http://code.alibabatech.com/schema/dubbo  
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd  
        ">  
  
    <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->  
    <dubbo:application name="hehe_consumer" />  
  
    <!-- 使用zookeeper注册中心暴露服务地址 -->  
    <!-- <dubbo:registry address="multicast://224.5.6.7:1234" /> -->  
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />  
  
    <!-- 生成远程服务代理,可以像使用本地bean一样使用demoService -->  
    <dubbo:reference id="demoService"  
        interface="com.unj.dubbotest.provider.DemoService" />  
  
</beans>

2.加载Spring配置,并调用远程服务:

package com.alibaba.dubbo.demo.pp;  
  
import java.util.List;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
import com.unj.dubbotest.provider.DemoService;  
  
public class Consumer {  
  
    public static void main(String[] args) throws Exception {  
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(  
                new String[] { "applicationContext.xml" });  
        context.start();  
  
        DemoService demoService = (DemoService) context.getBean("demoService"); //  
        String hello = demoService.sayHello("tom"); // ִ  
        System.out.println(hello); //   
  
        //   
        List list = demoService.getUsers();  
        if (list != null && list.size() > 0) {  
            for (int i = 0; i < list.size(); i++) {  
                System.out.println(list.get(i));  
            }  
        }  
        // System.out.println(demoService.hehe());  
        System.in.read();  
    }  
  
}

Zookeeper(引用来源:http://www.biaodianfu.com/zookeeper.html)

Zookeeper是一个高性能,分布式的,开源分布式应用协调服务。它提供了简单原始的功能,分布式应用可以基于它实现更高级的服务,比如同步,配置管理,集群管理,名空间。它被设计为易于编程,使用文件系统目录树作为数据模型。服务端跑在java上,提供java和C的客户端API。Zookeeper是Google的Chubby一个开源的实现,是高有效和可靠的协同工作系统,Zookeeper能够用来leader选举,配置信息维护等,在一个分布式的环境中,需要一个Master实例或存储一些配置信息,确保文件写入的一致性等。

Zookeeper总体结构

Zookeeper服务自身组成一个集群(2n+1个服务允许n个失效)。Zookeeper服务有两个角色,一个是leader,负责写服务和数据同步,剩下的是follower,提供读服务,leader失效后会在follower中重新选举新的leader。

(5)常见设计模式,对哪些设计模式有深入理解?

装饰器模式:

package silenceburn;

abstract class Componet{
	abstract public void disp();
}

class Wood extends Componet{

	@Override
	public void disp() {
		// TODO Auto-generated method stub
		System.out.print(" wood ");
	}
}

abstract class Decorator extends Componet{
	Componet cp;
	
	public Decorator(Componet cp){
		this.cp = cp;
	}
	
	public void disp(){
 		this.decoratorDisp();
		cp.disp();
	}
	abstract protected void decoratorDisp();
}

class RedDecorator extends Decorator{

	public RedDecorator(Componet cp) {
		super(cp);
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void decoratorDisp() {
		// TODO Auto-generated method stub
		System.out.print(" red ");
	}
	
}


class SmallDecorator extends Decorator{

	public SmallDecorator(Componet cp) {
		super(cp);
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void decoratorDisp() {
		// TODO Auto-generated method stub
		System.out.print(" Small ");
	}
	
}
public class ConcreteMode {
	
	public static void main(String args[]){
		Wood w  = new Wood();
		RedDecorator r = new RedDecorator(w);
		SmallDecorator s = new SmallDecorator(r);
		s.disp();
	}
}

说明:可以给Wood加上任意多的装饰者实现,比如此例子中实现了两个装饰者,

一个是红色RED装饰,一个是小SMALL装饰,最终得到的是 红色的 小的 木头。

适配器模式:

package silenceburn;

class M1911{
	public void singleFire(){
		System.out.print("da! ");
	}
}

class M16{
	public void threeFire(){
		System.out.println("da da da ");
	}
}

class M16Adapter extends M16{
	M1911 m;
	M16Adapter(M1911 m){this.m = m;}
	
	public void threeFire() {
		
		m.singleFire();m.singleFire();m.singleFire();
		
	};
}


public class AdapterMode {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		M1911 m = new M1911();
		M16 m16 = new M16Adapter(m);
		
		m16.threeFire();
		
	}

}

说明:M1911是手枪,只能单发“DA!”,M16是突击步枪,可以三连发,“DA DA DA”

通过一个M16适配器,可以让手枪表现的和M16相似,好像是一把M16一样。


代理模式:

package silenceburn;

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

interface Read{
	public void print();
}

class ReadProxy implements Read{
	
	Read realRead;
	
	ReadProxy(Read a){this.realRead = a;}
	
	@Override
	public void print() {
		// TODO Auto-generated method stub
		int reading = 5; 
		while(reading -- >0) System.out.println("wait " + reading);
		this.realRead.print();
	}	
}

class DynProxy implements InvocationHandler{

	Read real;
	
	DynProxy(Read real){this.real = real;}
	
	@Override
	public Object invoke(Object arg0, Method arg1, Object[] arg2)
			throws Throwable {
		// TODO Auto-generated method stub
		int reading = 5; 
		while(reading -- >0) System.out.println("wait " + reading);
		
		arg1.invoke(real, arg2);
		
		return null;
	}
	
}

class ReadReal implements Read{
	@Override
	public void print() {
		// TODO Auto-generated method stub
		System.out.println("this is real content");
	}	
}


public class ProxyMode {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//java Dynmanic Proxy
		Read r = new ReadReal();
		Read rp = (Read) Proxy.newProxyInstance(r.getClass().getClassLoader(), r.getClass().getInterfaces(), new DynProxy(r));
		rp.print();
		
		// my define Proxy
		Read r2  = new ReadReal();
		Read r2p = new ReadProxy(r2);
		r2p.print();
		
	}

}


说明:实现了两个代理,功能都是在真正的打印前,打印倒数4,3,2,1,0。

         第一个代理自己写代码实现。第二个代理是用JAVA的动态代理功能实现。

        其余还有策略模式,观察者模式,状态模式,工厂模式的代码,比较常见。

(6)spring底层代码,源代码看过没有,哪方面有深入了解?

(引用来源:面试——Spring特性: http://blog.csdn.net/competerh_programing/article/details/7262442

Spring高级特性: http://sueshanxiao.blog.163.com/blog/static/2165940592013521958380/

关于spring的事务传播特性: http://blog.csdn.net/dyllove98/article/details/8587484

1、spring的基本特性

一、Spring的IoC(Inversion of Control)。

这是Spring中得有特点的一部份。IoC又被翻译成“控制反转”,也不知道是谁翻译得这么别扭,感觉很深奥的词。其实,原理很简单,用一句通俗的话来说:就是用XML来定义生成的对象。IoC其实是一种设计模式,Spring只是实现了这种设计模式。

这种设计模式是怎么来的呢?是实践中逐渐形成的。

第一阶段:用普通的无模式来写Java程序。一般初学者都要经过这个阶段。

第二阶段:频繁的开始使用接口,这时,接口一般都会伴随着使用工厂模式。

第三阶段:使用IoC模式。工厂模式还不够好:(1)因为的类的生成代码写死在程序里,如果你要换一个子类,就要修改工厂方法。(2)一个接口常常意味着一个生成工厂,会多出很多工厂类。

    可以把IoC模式看做是工厂模式的升华,可以把IoC看作是一个大工厂,只不过这个大工厂里要生成的对象都是在XML文件中给出定义的,然后利用Java的“反射”编程,根据XML中给出的类名生成相应的对象。从实现来看,IoC是把以前在工厂方法里写死的对象生成代码,改变为由XML文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。

    IoC中最基本的Java技术就是“反射”编程。反射又是一个生涩的名词,通俗的说反射就是根据给出的类名(字符串)来生成对象。这种编程方式可以让对象在生成时才决定要生成哪一种对象。我在最近的一个项目也用到了反射,当时是给出一个.properties文本文件,里面写了一些全类名(包名+类名),然后,要根据这些全类名在程序中生成它们的对象。反射的应用是很广泛的,象Hibernate、String中都是用“反射”做为最基本的技术手段。

    在过去,反射编程方式相对于正常的对象生成方式要慢10几倍,这也许也是当时为什么反射技术没有普通应用开来的原因。但经SUN改良优化后,反射方式生成对象和通常对象生成方式,速度已经相差不大了(但依然有一倍以上的差距)。

    所以要理解IoC,你必须先了解工厂模式和反射编程,否则对它产生的前因后果和实现原理都是无法理解透彻的。只要你理解了这一点,你自己也完全可以自己在程序中实现一个IoC框架,只不是这还要涉及到XML解析等其他知识,稍微麻烦一些。

    IoC最大的好处是什么?因为把对象生成放在了XML里定义,所以当我们需要换一个实现子类将会变成很简单(一般这样的对象都是现实于某种接口的),只要修改XML就可以了,这样我们甚至可以实现对象的热插拨(有点象USB接口和SCIS硬盘了)。

    IoC最大的缺点是什么?(1)生成一个对象的步骤变复杂了(其实上操作上还是挺简单的),对于不习惯这种方式的人,会觉得有些别扭和不直观。(2)对象生成因为是使用反射编程,在效率上有些损耗。但相对于IoC提高的维护性和灵活性来说,这点损耗是微不足道的,除非某对象的生成对效率要求特别高。(3)缺少IDE重构操作的支持,如果在Eclipse要对类改名,那么你还需要去XML文件里手工去改了,这似乎是所有XML方式的缺憾所在。

    总的来说IoC无论原理和实现都还算是很简单的。一些人曾认为IoC没什么实际作用,这种说法是可以理解的,因为如果你在编程中很少使用接口,或很少使用工厂模式,那么你根本就没有使用IoC的强烈需要,也不会体会到IoC可贵之处。有些人也说要消除工厂模式、单例模式,但是都语焉不详、人云亦云。但如果你看到IoC模式和用上Spring,那么工厂模式和单例模式的确基本上可以不用了。但它消失了吗?没有!Spring的IoC实现本身就是一个大工厂,其中也包含了单例对象生成方式,只要用一个设置就可以让对象生成由普通方式变单一实例方式,非常之简单。

   总结:

   (1)IoC原理很简单,作用的针对性也很强,不要把它看得很玄乎。

   (2)要理解IoC,首先要了解“工厂、接口、反射”这些概念。

二、Spring的MVC

如果你已经熟悉Struts,那么不必把MVC做为重点学习内容。基本上我认为Spring  MVC是一个鸡肋,它的技术上很先进,但易用性上没有Struts好。而且Struts有这么多年的基础了,Spring很难取代Struts的地位。这就是先入为主的优秀,一个项目经理选用一种框架,不能单纯的从它的技术上考虑,还有开发效率,人员配置等都是考虑因素。但做为研究性的学习,Spring的MVC部份还是蛮有价值的。

三、数据库层的模板

Spring主要是提供了一些数据库模板(模板也是一种Java设计模式),让数据部分的代码更简洁,那些try...catch都可以不见了。

四、AOP

AOP又称面向方面编程,它的实现原理还是用了反射:通过对某一个种类的方法名做监控来实现统一处理。比如:监控以“insert”字符串开头的方法名,在这种方法执行的前后进行某种处理(数据库事务等)。

2.Java的动态代理:

Spring的面向切面编程(AOP)底层实现原理是动态代理,因此在学习面向切面编程之前必须先了解动态代理。

Java中动态代理应用非常广泛,动态代理是23中设计模式中非常常用的经典设计模式之一。动态代理的原理是,当要调用一个目标对象或者其方法时,系统并不是直接返回目标对象,而是返回一个代理对象,通过这个代理对象去访问目标对象或者目标对象的方法。

动态代理的简单原理如下:

客户端调用者——>代理对象——>被调用的目标对象。

当客户端调用代理对象时,代理对象委派目标对象调用其业务方法。

动态代理分为两种,针对接口的动态代理和针对普通类的动态代理,java中的动态代理是真的接口的动态代理,cglib是针对普通类的动态代理,目标javaEE的依赖包和Spring的jar包中已经包含了cglib相关jar包,因此即可以对代理也可以对普通类进行动态代理。

(1).java的针对接口动态代理:

Java中的动态代理只能针对接口进行动态代理,因此,目标对象必须实现接口,代理对象要实现目标对象的所有接口。工作流程如下:

a.      动态代理类编写:

注意:动态代理必须实现InvocationHandler接口,同时实现以下方法:

Object invoke(Objectm代理实例,Method代理实例上调用的接口方法的Method 实例,Object[] 传入代理实例上方法调用的参数值的对象数组);  

安装JDK的文档说明,该方法作用是传递代理实例、识别调用方法的 java.lang.reflect.Method 对象以及包含参数的 Object 类型的数组。调用处理程序以适当的方式处理编码的方法调用,并且它返回的结果将作为代理实例上方法调用的结果返回。

b.      创建代理对象:

Proxy.newProxyInstance(类加载器, Class<?>[]接口数组,回调代理对象(一般是this))  

 当调用目标对象方法时,通过该方法创建目标对象的代理对象,代理对象会自动调用其invoke方法调用目标对象,并将调用结果返回。

(2).cglib针对普通java类动态代理:

cglib创建动态代理时,不要求目标类必须实现接口,其工作流程如下:

a.动态代理类编写:

Enhancer  enhancer = new Enhancer();  
//设置目标类的父类为其本身  
enhancer.setSuperclass(目标类对象.getClass());  
//设置回调对象为动态代理对象本身  
enhancer.setCallback(this);

b.实现MethodInterceptor接口:

实现以下方法:

Object intercept(Objectm代理实例,Method代理实例上调用的接口方法的Method 实例,Object[] 传入代理实例上方法调用的参数值的对象数组,MethodProxy 方法代理实例);

注意:cglib不但可以针对类动态代理,还可以针对方法动态代理。

3.面向切面编程(AOP)的基础概念:

以一个普通的java方法来举例

public 返回类型 方法名(参数列表){ ——>环绕通知  
       方法前处理代码    ——>     前置通知  
	try{  
       		方法具体实现(方法体)…….  
       		方法后处理代码    ——>     后置通知  
	}Catch(异常类型 e){  
       		异常处理……       ——>     例外通知  
	}finally{  
      		最后处理代理……       ——>     最终通知  
	}  
}

a.      横切关注点:如上面5个通知的位置,在java对象中,可以这些具有类似共同处理逻辑的位置加入如权限验证、事物处理、日志记录等处理逻辑的对象称为横切关注点,面向对象编程(OOP)的关注点是纵向将现实世界的事物抽象成编程的对象模型。而面向切面编程(AOP)的关注点是横向的,它将编程对象模型中拥有类似处理逻辑的地方抽象出来形成切面,而编程对象中的处理逻辑就是横切关注点。

b.      切面(Aspect):将横切关注点抽象就形成切面,与类类似,二者关注点不同,类是事物特性的抽象,切面是横切关注点的抽象。

c.      连接点(Joinpoint):被拦截到的点,在Spring中指方法,因为spring只支持方法类型的连接点,即被拦截的方法。如上面例子的方法。

d.      切入点(Pointcut):指对连接点进行拦截的定义,是连接点的集合,即一系列被拦截方法的集合。

e.      通知(Advice):指拦截到连接点之后要做的事情,即拦截之后的逻辑处理。通常的权限验证、事物处理、日志记录等操作就是在通知中定义和完成的。

f.       目标对象(Target):代理的目标对象,即被拦截的对象。如上面例子中方法所在的对象。

g.      织入(Weave):指将切面应用到目标对象,并导致代理对象创建的过程。

h.      引入(Introduction):在不修改代码的前提下,引入可以在运行期为类动态的添加一些方法和字段。

1.      Spring中支持面向切面编程(AOP)的依赖包:

Spring解压后目录中的如下3个包:

lib/aspectj/aspectjweaver.jar

lib/aspectj/aspectjrt.jar

lib/cglib/cglib-nodep-2.1-3.jar

2.      在spring中使用面向切面编程(AOP)时,需要在spring配置文件中引入aop的命名空间,即添加如下的配置:

xmlns:aop=”http://www.springframework.org/schema/aop”  
“http://www.springframework.org/schema/aop  
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”

 

注意:Spring2.5以后提供两种AOP方法,即基于xml配置文件方式和基于java注解方式。

若要使用注解方式的aop,需要在spring配置文件中添加如下的对象注解方式aop的支持:

[xhtml] view plaincopy
<aop:aspectj-autoProxy/>

4.关于spring的事务传播特性

我们都知道事务的概念,那么事务的传播特性是什么呢?(此处着重介绍传播特性的概念,关于传播特性的相关配置就不介绍了,可以查看spring的官方文档) 

在我们用SSH开发项目的时候,我们一般都是将事务设置在Service层 那么当我们调用Service层的一个方法的时候它能够保证我们的这个方法中执行的所有的对数据库的更新操作保持在一个事务中,在事务层里面调用的这些方法要么全部成功,要么全部失败。那么事务的传播特性也是从这里说起的。 

如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法,那么在调用其他的 Service方法的时候,这个事务是怎么规定的呢,我必须保证我在我方法里掉用的这个方法与我本身的方法处在同一个事务中,否则如果保证事物的一致性。事务的传播特性就是解决这个问题的,“事务是会传播的”在Spring中有针对传播特性的多种配置我们大多数情况下只用其中的一种:PROPGATION_REQUIRED:这个配置项的意思是说当我调用service层的方法的时候开启一个事务(具体调用那一层的方法开始创建事务,要看你的aop的配置),那么在调用这个service层里面的其他的方法的时候,如果当前方法产生了事务就用当前方法产生的事务,否则就创建一个新的事务。这个工作使由Spring来帮助我们完成的。 

以前没有Spring帮助我们完成事务的时候我们必须自己手动的控制事务,例如当我们项目中仅仅使用hibernate,而没有集成进 spring的时候,我们在一个service层中调用其他的业务逻辑方法,为了保证事物必须也要把当前的hibernate session传递到下一个方法中,或者采用ThreadLocal的方法,将session传递给下一个方法,其实都是一个目的。现在这个工作由 spring来帮助我们完成,就可以让我们更加的专注于我们的业务逻辑。而不用去关心事务的问题。 

默认情况下当发生RuntimeException的情况下,事务才会回滚,所以要注意一下 如果你在程序发生错误的情况下,有自己的异常处理机制定义自己的Exception,必须从RuntimeException类继承 这样事务才会回滚!

Spring事务传播特性总结:

1.只要定义为spring的bean就可以对里面的方法使用@Transactional注解。 

2.Spring的事务传播是Spring特有的。不是对底层jdbc的代理。

3.使用spring声明式事务,spring使用AOP来支持声明式事务,会根据事务属性,自动在[方法调用之前决定是否开启一个事务],并在[方法执行之后]决定事务提交或回滚事务。 

4.Spring支持的PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:

PROPAGATION_REQUIRES_NEW:二个事务没有信赖关系,不会存在A事务的成功取决于B事务的情况。有可能存在A提交B失败。A失败(比如执行到doSomeThingB的时候抛出异常)B提交,AB都提交,AB都失败的可能。

PROPAGATION_NESTED:与PROPAGATION_REQUIRES_NEW不同的是,内嵌事务B会信赖A。即存在A失败B失败。A成功,B失败。A成功,B成功。而不存在A失败,B成功。

5. 特别注意PROPAGATION_NESTED的使用条件:使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;而 nestedTransactionAllowed属性值默认为false;

6.特别注意PROPAGATION_REQUIRES_NEW的使用条件:JtaTransactionManager作为事务管理器

的AOP简介与事务传播特性总结2009-10-26 16:56srping用到的另外一项技术就是AOP(Aspect-Oriented Programming, 面向切面编程),它是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程)的补充。AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点。在应用 AOP 编程时, 仍然需要在定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里。每个事物逻辑位于一个位置, 代码不分散,便于维护和升级,业务模块更简洁, 只包含核心业务代码。 

现实中使用spring最多的就是声明式事务配置功能。下面就来了解其aop在事务上应用。首先要了解的就是AOP中的一些概念: 

Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面是横切性关注点的抽象。 

joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器)。 

Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义。 

Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知。 

Target(目标对象):代理的目标对象。 

Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入。 

Introduction(引入):在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field。 

所谓AOP,我的理解就是应该是这样一个过程,首先需要定义一个切面,这个切面是一个类,里面的方法就是关注点(也是通知),或者说里面的方法就是用来在执行目标对象方法时需要执行的前置通知,后置通知,异常通知,最终通知,环绕通知等等。有了切面和通知,要应用到目标对象,就需要定义这些通知的切入点,换句话说就是需要对哪些方法进行拦截,而这些被拦截的方法就是连接点,所谓连接点也就是在动态执行过程,被织入切面的方法(至少在spring中只能对方法进行拦截)。因此,在动态过程中通知的执行就属于织入过程,而被织入这些通知的对象就是目标对象了。 

通常应用中,被织入的都是事务处理,对事务的织入不是普通简单的织入,它有着事务特有的特性—— 

事务的传播特性: 

1. PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务。如果没有事务则开启新的事物。 

2. PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。 

3. PROPAGATION_MANDATORY: 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。 

4. PROPAGATION_REQUIRES_NEW: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。 

5. PROPAGATION_NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务。 

6. PROPAGATION_NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常 

7.(spring)PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。 

这些都是事务特有的特性,比如前面分析的,如果两个在代码上不相关的操作,需要放在同一个事务中,这就需要利用到传播特性了,这时后调用的方法的传播特性的值就应该是PROPAGATION_REQUIRED。在spring中只需要进行这样的配置,就实现了生命式的事物处理。

化成表格的事务的传播特性

事务属性 事务1 事务2
Required

事务1

事务2

事务1

RequiredNew

事务1

事务2

事务2

Support

事务1

事务1

Mandatory

事务1

抛异常

事务1

NOSupport

事务1

Never

事务1

抛异常

最后一点需要提及的就是Spring事务的隔离级别: 

1. ISOLATION_DEFAULT:这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。 

2. ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。 

3. ISOLATION_READ_COMMITTED:保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 

4. ISOLATION_REPEATABLE_READ:这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。 

5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。 

除了第一个是spring特有的,另外四个与JDBC的隔离级别相对应。第二种隔离级别会产生脏读,不可重复读和幻像读,特别是脏读,一般情况下是不允许的,所以这种隔离级别是很少用到的。大多说数据库的默认格里基本是第三种。它能消除脏读,但是可重复读保证不了。第四种隔离级别也有一些数据库作为默认的隔离级别,比如MySQL。最后一种用的地方不多,除非是多数据访问的要求特别高,否则轻易不要用它,因为它会严重影响数据库的性能。

共有 人打赏支持
粉丝 12
博文 7
码字总数 52096
评论 (1)
programtic
待遇估计跟tw差不多吧?
×
黄状
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: