Struts2_ognl表达式&操作值栈

Struts2_OGNL表达式

一、初步使用ongl表达式

OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,是一个使用简单、功能强大的、开源的表达式语言(框架),可以方便地操作任何的对象属性、方法等。

Struts2框架使用OGNL作为默认的表达式语言,主要用于页面的取值。它类似于EL表达式语言,但比EL语法强大很多。

EL Expression Language 表达式语言, 主要用来获取 JSP页面四个域范围数据 (page、 request、 session、 application )

使用ongl表达式

1. 导入的jar包:
    ognl-xxx.jar : ognl的核心环境
    javassist-xxx.jar : ognl的依赖,提供字节码修改能力。

2. 使用ognl表达式的jsp页面需要导入struts2的标签库
    <%@ taglib prefix="s" uri="/struts-tags"%>

3. 几种基本的使用方式:
    1. Java对象的直接访问 :
        <s:property value="'name'"/><br/>
    
    2. 实例方法调用
        <s:property value="'name'.length()"/><br/>      

    3. 赋值操作或表达式串联
        <s:property value="1+1" /><br/>

    4. 静态方法调用(类的静态方法或静态变量的访问) 不推荐
        语法:@[类全名(包括包路径)]@[方法名|值名]
        例如:<s:property value="@java.lang.Math@max(10,20)"/><br/>

        但是!!!在使用静态方法之前必须在struts.xml中开启静态方法调用功能
        <!-- 开启ognl表达式静态方法调用 -->
        <constant name="struts.ognl.allowStaticMethodAccess" value="true"></constant>


    5. OGNL上下文(OGNL Context)和ActionContext的访问。(值栈相关访问-重要)
    6. 集合对象的操作

二、值栈

值栈的概念

值栈(ValueStack),是Struts2的数据中转站,其中自动保存了当前Action对象和其他相关对象(包括常用的Web对象的引用,如request等),也可以手动保存自己的数据对象,同时也可以随时随地将对象从值栈取出或操作(通过OGNL表达式)

了解:值栈的生命周期,和request一样或action,生命周期都一样

获取值栈对象

在Action动作类中获取值栈对象有两种方式:

@Override
public String execute() throws Exception {

    //目标 :获取值栈对象
    //方式一 : 通过request对象直接获取
    ValueStack valueStack = (ValueStack) ServletActionContext.getRequest().getAttribute("struts.valueStack");
    
    //方式二 : 通过struts2提供的api来获取(底层还是1方式)
    ValueStack valueStack2 = ActionContext.getContext().getValueStack();
    
    //测试是否是同一个对象
    System.out.println(valueStack == valueStack2);
    return NONE;
}

值栈的数据存储结构

在值栈的内部有两个逻辑部分:

  • ObjectStack(对象栈):保存了Action的相关对象和动作,数据存储结构是List。
  • ContextMap(上下文栈):保存了各种映射关系,如常用的web对象的引用,数据存储结构是Map。

    值栈ValueStack(OgnlValueStack实现类)中有两块区域 :
    一块是CompoundRoot对象栈(也称为Root栈),它实质上是使用List集合来存放对象,存入对象压栈从栈顶往下压。

    一块是OgnlContext上下文栈(也称为Map栈)。
        Map栈中有两个区域,_root用于存放对象栈(Root栈)的引用
        _values实质上使用Map集合键值对的形式存放对象
img12.png

值栈是在请求对象引用了一块存储空间。

值栈包括两部分: 对象栈(CompoundRoot,继承List接口)和OGNL上下文栈(OgnlContext,实现Map接口)
而OGNL上下文栈内部又分为两部分:对象栈的引用和一个HashMap,这个HashMap会引用常用web对象和其他映射关系。

三、 操作值栈

栈的概念:
栈是一种数据结构,它按照先进后出的原则存储数据,即先进入的数据被压入栈底,
最后进入的数据在栈顶,需要读取数据的时候,从栈顶开始弹出数据(即最后一个数据被第一个读出来)。

栈也被成为先进后出表,可进行插入和删除操作,插入称之为进栈(压栈)(push),
删除称之为退栈(pop),允许操作的两端分别称为栈顶(top)和栈底(bottom)。栈底固定,而栈顶浮动。

对于栈就只能每次访问它的栈顶元素,从而可以达到保护栈顶元素以下的其他元素的目的。

值栈数据的保存:向root栈和map栈放入数据

@Override
public String execute() throws Exception {

    // 目标 :向root栈和map栈中分别存入数据
    String msg = "我爱java";
    
    // 获取值栈对象
    ValueStack valueStack = ActionContext.getContext().getValueStack();
    
    //向root栈中存值(压栈),匿名,
    valueStack.push(msg);
    //root栈中存值(压栈),有名字
    valueStack.set("msg2", "我爱C++");
    //向Root栈中存值(键值对)
    ActionContext.getContext().put("msg3", "我爱Python");

    return SUCCESS;
}

通过ognl表达式取出存储在值栈中的数据显示在页面

struts.xml 配置跳转页面 :
    <action name="valueStack2" class="com.itdream.a_valueStack.ValueStack2Action">
            <result>/a_ognl/result.jsp</result>
    </action>


result.jsp : 

<%@ taglib prefix="s" uri="/struts-tags"%>

<!-- 取出存在值栈中的值 -->
<!-- push方法,匿名压入Root栈的数据,第一个被压入 -->
<s:property value="[1].top"/><br/>

<!-- set方法,Struts2帮我们创建一个map集合存储数据压入Root栈的数据,map的key就是数据的名字,通过key取值 -->
<s:property value="msg2"/><br/>

<!-- put方法,放入Map栈中,通过加#号定向到Map栈中找 -->
<!-- 也可以不加#号使用默认查找机制,但这样先从Root栈先找,再到Map栈找key,如果在Root栈找到就找不到Map栈的数据了 -->
<s:property value="#msg3"/>|<s:property value="msg3"/><br/>

<!-- 用来查看值栈的内容 -->
<s:debug/>
img14.png

点击Debug查看值栈的内容:

页面中直接查看值栈的元素的方法。
struts2为我们提供了一个标签:<s :debug/>,只要将其加在页面中即可。

Root栈:

数据从栈顶往下压,push方法直接压入String对象,此时它是栈顶,索引为0
然后使用set方法,创建了一个HashMap存储数据的值,作为对象存入Root栈,此时栈顶是这个HashMap对象,String对象的索引变为1
img15.png

Map栈:

put方法,以键值对形式存储数据
img16.png

在这里介绍值栈的创建过程:

1. 每次请求都会创建一个ActionContext对象和一个OnglValueStack值栈对象,这时在Root栈中放入了一个textProvider.

2. 紧接着,Struts2将与Servket有关的Response,Request,Session等对象放入到Map栈中,此时Map栈中有Servlet的对象引用.

3. 之后就准备执行Action动作类 ,创建Action动作的代理对象。Action是在执行的时候才进行初始化,初始化完成后,
将Action对象压入Root栈栈顶,并在Map栈的_root中放入一个引用:key - action

到这里,Root栈中有一个textProvider与一个Action对象,Map栈中有Servlet相关的数据以及Action对象的引用,
两个Action是同一个

4. 初始化完成,ActionProxy代理对象在执行时,会先执行拦截器,再执行Action,
这个时候,如果有Action实现了ModelDriven接口,模型驱动拦截器就将模型对象压入栈顶。


了解原理的目的:

知道值栈的初始化时机,访问action的时候,才创建(这个过程中会创建actionProxy对象,并且同时创建值栈,并且,将一些对象放入值栈。)

值栈初始化之后,里面主要默认有:root栈(textProvider、action类对象、model(user)),map栈(servlet相关对象、action对象一个引用)

哪些对象在栈顶!因为后面我们从值栈取值,都从栈顶往下取。所以,一定要知道哪个对象在栈上面。
img13.png

值栈的默认搜索机制

测试代码:

    
    public class ValueStack3Action extends ActionSupport {
        
        // 目标 : 测试值栈的默认查找机制
        // 提供Action的属性,Action初始化完成时,会将Action的对象属性压入Root栈
        private String username = "林志玲";
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        @Override
        public String execute() throws Exception {
    
            ValueStack valueStack = ActionContext.getContext().getValueStack();
            //在创建一个有名字的对象压入Root栈
            valueStack.set("username", "唐嫣");
            
            //往Map栈存入数据
            ActionContext.getContext().put("username", "高圆圆");
            
            return SUCCESS;
        }
    }


    jsp页面:

    <!-- 取出存在值栈中的值 -->
    <s:property value="username"/>
    
    <!-- 用来查看值栈的内容 -->
    <s:debug/>


    此时运行取出username的值是 唐嫣 。

    为什么呢?这是由于值栈的默认查找机制。
    当我们使用 对象名称 直接查找时,底层使用的API是valuestack.find(ognl)
    1. 他会优先从Root栈中开始查找(从上而下),因为Action属性存入值栈的是在执行前压入的,因此取的时候,它在值栈的下方,
    因此首先找到上面的执行时 set 方法放入的 username。

    2. 如果Root栈找不到,才会找Map栈中的key.如果想直接获取Map栈中的数据,加个#号即可
        <s:property value="#username"/>
    

OGNL操作值栈的几个符号:

直接使用对象名 , # , % , $

1. 直接使用对象名,使用值栈的默认查找机制

2. #+key 从Map栈中查找对象的key,从而找到该数据
    1. 通过#request等可以获取存储在Servlet域对象中的数据

3. % , 控制解析或者不解析

    添加%{'对象名'}或者'对象名'让其变成普通字符串而不解析 。
    <!-- %强制不转换 -->
    <s:property value="username"/> <!-- 唐嫣 -->
    <s:property value="'username'"/><!-- username -->
    <s:property value="%{'username'}"/><!-- username -->

    使用%{对象名}强制解析
    <!-- 这里s:textfield标签类似与input的text标签 -->
    <s:textfield value="username"/>
    <!-- textfield标签的value中直接写对象名称不会进行解析,需要%强制解析 -->
    <s:textfield value="%{username}"/>

4. $用于在配置文件中获取值栈的值
    
    1.URL请求重定向时,携带(动态传递)参数 –struts.xml中使用
        result type="redirect">/c_expr/ognl2.jsp?name=${myname}</result>

值栈的存取汇总小结

值栈的主要作用就是数据的保存和获取(可以在任何地方获取)。

1.如何向值栈保存数据
  • ValueStack.put(obj):保存数据到Root栈(栈顶),(对象本身)匿名,通过索引取出
  • ValueStack.set(key,value):保存数据到Root栈(栈顶),数据被自动封装成一个Map集合存储,栈顶保存的是一个Map集合,Map的value就是对象,这种存储方式有名字.ognl通过名称获取数据的值
  • ActionContext.getContext().put(key,value): 保存数据到Map栈中
  • 提供Action成员变量,提供getter方法(Action就在root栈中,Action属性可以被搜索)

模型驱动封装参数优先于属性驱动的原理:


    第四种方式在初始化完成时压入Root栈,而ModelDriven过滤器待封装的参数,在初始化完成后,过滤器中,将其压入栈顶。
    而封装的参数从Root栈中取值是自上而下取值的,因此模型驱动优先于属性驱动。
2. ognl表达式如何获取值栈的数据
  • JSP页面获取
    • <s :property value= “对象名”/> 先搜索root栈对象属性(getter方法: getXxx-->xxx),再搜索map的key
    • <s :property value= “#对象名”/>搜索map栈的key
    • 通过[index].top指定访问Root栈的某个对象,例如[0].top访问栈顶对象
  • Action代码获取
    • ValueStack.findValue(ognl表达式) ; 获取值栈数据
3. 值栈的生命周期
* 贯穿整个Action的生命周期,每个Action类的对象实例都拥有一个ValueStack对象。
* Struts2框架将ValueStack对象保存在名为“struts.valueStack”的请求(request)属性中,
即值栈是request中的一个对象,一个请求对应一个Action实例和一个值栈对象。

因此:值栈的生命周期,就是request生命周期。值栈、action、request生命周期一样,不存在线程问题!
重定向跳转页面时,目标页面无法获取到Action中放入值栈的值。
4. 补充,通过EL表达式获取值栈的值
EL 表达式原理:
 在page、request、session、application 四个范围,调用getAttribute 获取数据。为什么也可以获取值栈的值呢?

原因:Struts2提供StrutsRequestWrapper包装类,对request 的 getAttribute 方法增强 。
它优先使用 request.getAttribute取值,如果取不到,执行 valueStack的findValue方法使用值栈的默认查找机制。

问题思考 : 
后台代码:
request.setAttribute(“name“, ”aaa“ ) ;//存入request域中
valueStack.set(“name“,”bbb“ )//存入值栈
页面代码:
<s:property name=”name” />   -----  值栈默认查找机制,bbb
${name} ------ 优先从request中查找,aaa 

四、CRM案例:添加,显示,修改客户

1. 搭建环境

1.1 搭建Struts2环境
  • 拷贝前端页面
  • 导入Struts2的jar包
  • 配置web.xml的前端控制器
  • 配置struts.xml文件
  • Struts2环境搭建完成
1.2 搭建Hibernate环境
  • 导入Hibernate的jar包

    • required包下的所有jar包
    • c3p0的jar包
    • 另,log4j-1.x的jar包
  • 删除Struts2与Hibernate冲突的jar包

  • R : 数据库表的编写

  • O : 持久化类的编写

  • M : 映射文件的编写

  • 导入日志log4l.properties配置文件,创建核心配置文件hibernate.cfg.xml

  • 准备工具类HibernateUtils

  • 创建包结构,测试Hibernate是否搭建成功

  • Hibernate搭建完成

2. 代码实现功能

2.1 修改menu.jsp
新增客户 : 跳转到增加新客户的页面
    href="${pageContext.request.contextPath }/jsp/customer/add.jsp"

客户列表 : 查询所有客户
    href="${pageContext.request.contextPath }/customer_list.action"
2.2 修改add.jsp
form 表单提交路径 : 
    action="${pageContext.request.contextPath }/customer_save.action"
2.3 struts.xml请求与Action的配置关系
<struts>
    <constant name="struts.devMode" value="true" />
    <!-- 简单样式 -->
    <constant name="struts.ui.theme" value="simple"/>

    <package name="default" namespace="/" extends="struts-default">
        <action name="customer_*" class="com.itdream.crm.web.action.CustomerAction" method="{1}">
            <result name="flushListCustomer" type="redirectAction">customer_list.action</result>
            <result name="listCustomer">/jsp/customer/list.jsp</result>
            <!-- 默认转发,传递customer数据 -->
            <result name="editjsp">/jsp/customer/edit.jsp</result>
        </action>
    </package>
</struts>
2.4 Action动作类处理请求
1. 提供模型驱动,用以封装参数。
2. 查询到的数据,存入值栈转发。
3. 下面是三个功能:新增客户,查询所有客户,修改客户

public class CustomerAction extends ActionSupport implements ModelDriven<Customer>{

    //提供模型对象
    private Customer customer = new Customer();
    
    @Override
    //Struts2获取模型对象封装
    public Customer getModel() {
        return customer;
    }
    
    //添加客户
    public String save() {
        //获取参数,模型驱动自动封装
        //调用业务层
        CustomerService service = new CustomerServiceImpl();
        service.addCustomer(customer);
        
        return "flushListCustomer";
    }
    
    //查询所有客户
    public String list() {
        //调用业务层
        CustomerService service = new CustomerServiceImpl();
        List<Customer> listCustomer = service.findListCustomer();
        
        //将所有Customer的数据存入值栈,以set方式存入Root栈
        ValueStack valueStack = ActionContext.getContext().getValueStack();
        valueStack.set("list", listCustomer);;
        //跳转页面
        return "listCustomer";
    }
    
    //查询要修改的客户,并跳转到修改页面
    public String showEdit() {
        //获取参数
        Long cust_id = customer.getCust_id();
        //调用Service层
        CustomerService service = new CustomerServiceImpl();
        Customer customer2 = service.findCustomerById(cust_id);
        //将查询到的客户数据压入值栈
        ActionContext.getContext().getValueStack().set("customer", customer2);
        //跳转页面
        return "editjsp";
    }

    //修改客户信息
    public String update() {
        //获取参数(模型驱动)
        //调用业务层
        System.out.println("hehe");
        CustomerService service = new CustomerServiceImpl();
         service.updateCustomer(customer);
        //跳转
        return "flushListCustomer";
    }
}
2.5 Service层处理业务逻辑
public class CustomerServiceImpl implements CustomerService {

    @Override
    // 添加客户
    public void addCustomer(Customer customer) {
        // 获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        // 开启事务
        Transaction transaction = session.beginTransaction();
        // 业务逻辑
        try {
            CustomerDAO dao = new CustomerDAOImpl();
            dao.addCustomer(customer);
        } catch (Exception e) {
            // struts2默认异常回滚事务,可以不写
            transaction.rollback();
            e.printStackTrace();
        } finally {
            // 无论是否异常都提交事务结束事务
            transaction.commit();
        }
    }

    @Override
    // 查询所有客户
    public List<Customer> findListCustomer() {
        // 获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        // 开启事务
        Transaction transaction = session.beginTransaction();
        // 业务逻辑
        List<Customer> listCustomer = null;
        try {
            CustomerDAO dao = new CustomerDAOImpl();
            listCustomer = dao.findListCustomer();
        } catch (Exception e) {
            // struts2默认异常回滚事务,可以不写
            transaction.rollback();
            e.printStackTrace();
        } finally {
            // 无论是否异常都提交事务结束事务
            transaction.commit();
        }
        return listCustomer;
    }

    @Override
    // 通过id查找客户
    public Customer findCustomerById(Long cust_id) {
        // 获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        // 开启事务
        Transaction transaction = session.beginTransaction();

        // 业务逻辑
        Customer customer = null;
        try {
            CustomerDAO dao = new CustomerDAOImpl();
            customer = dao.findCustomerById(cust_id);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            transaction.commit();
        }
        return customer;
    }

    @Override
    // 更新客户信息
    public void updateCustomer(Customer customer) {
        // 获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        // 开启事务
        Transaction transaction = session.beginTransaction();
        // 业务逻辑
        try {
            CustomerDAO dao = new CustomerDAOImpl();
            dao.updateCustomer(customer);
        } catch (Exception e) {
            // Struts2默认异常回滚
            transaction.rollback();
            e.printStackTrace();
        } finally {
            // 提交事务
            transaction.commit();
        }
    }
}
2.6 dao层操作数据库
1. 添加客户需要先找到其关联的联系人建立关系
2. 持久态的使用

public class CustomerDAOImpl implements CustomerDAO {

    @Override
    //添加客户
    public void addCustomer(Customer customer) {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //首先找到客户的联系人
        //创建Criteria查询对象
        Criteria criteria = session.createCriteria(Linkman.class);
        //创建限制条件
        Criterion criterion = Restrictions.eq("lkm_name", customer.getCust_linkman());
        //添加限制条件
        criteria.add(criterion);
        //查询联系人
        Linkman linkman = (Linkman) criteria.uniqueResult();
        if(linkman == null) {
            linkman = new Linkman();
            linkman.setLkm_name(customer.getCust_linkman());
        }
        
        //保存客户到数据库
        //建立关系:客户表达与联系人的关系:一个客户有多个联系人
        customer.getLinkmans().add(linkman);
        //建立关系:联系人表达与客户的关系:一个联系人对应一个客户
        linkman.setCustomer(customer);
        
        //将customer变为持久态,配置了级联,会自动级联将linkman也变为持久态
        session.save(customer);
    }

    @Override
    //查询所有客户
    public List<Customer> findListCustomer() {
        //单表查询使用Criteria查询更方便
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //创建Criteria查询对象
        Criteria criteria = session.createCriteria(Customer.class);
        //执行查询
        return criteria.list();
    }

    @Override
    //通过ID查询客户
    public Customer findCustomerById(Long cust_id) {
        //获取Session
        Session session = HibernateUtils.getCurrentSession();
        //get方法获取的Customer已经是持久态无需保存
        return session.get(Customer.class, cust_id);
    }

    @Override
    //更新客户信息
    public void updateCustomer(Customer customer) {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //修改客户
        session.update(customer);
    }
}
2.7 显示所有客户list.jsp获取值栈数据
<!-- 神奇的request先从request中找,找不到就走值栈的默认查找机制,因此也能查找到存入值栈的list -->
<c:forEach items="${list }" var="customer">
    <TR style="FONT-WEIGHT: normal; FONT-STYLE: normal; BACKGROUND-COLOR: white; TEXT-DECORATION: none">
    <TD>${customer.cust_name }</TD>
    <TD>${customer.cust_level }</TD>
    <TD>${customer.cust_source }</TD>
    <TD>${customer.cust_linkman }</TD>
    <TD>${customer.cust_phone }</TD>
    <TD>${customer.cust_mobile }</TD>
    <TD>
    <a href="${pageContext.request.contextPath }/customer_showEdit.action?cust_id=${customer.cust_id}">修改</a>
    </TD>
2.8 edit.jsp获取存入值栈的客户信息,提交修改
form表单action请求地址:
    action="${pageContext.request.contextPath }/customer_update.action"

//隐藏域提交客户id,从而找到该客户进行数据更新
<s:textfield name="cust_id" value="%{customer.cust_id}" type="hidden" />

客户名称:<s:textfield name="cust_name" value="%{customer.cust_name}" />
客户级别:<s:textfield name="cust_level" value="%{customer.cust_level}" />
信息来源:<s:textfield name="cust_source" value="%{customer.cust_source}" />
联系人:<s:textfield name="cust_linkman" value="%{customer.cust_linkman}" />
固定电话 :<s:textfield name="cust_phone" value="%{customer.cust_phone}" />
移动电话 :<s:textfield name="cust_mobile" value="%{customer.cust_mobile}" />
2.9 功能完成,测试

结果图:

img17.png
img18.png
img19.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容

  • 概述 什么是Struts2的框架Struts2是Struts1的下一代产品,是在 struts1和WebWork的...
    inke阅读 2,228评论 0 50
  • 本文包括:1、OGNL 表达式概述(了解)2、值栈概述3、值栈的存值与取值4、EL 表达式也会获取到值栈中的数据5...
    廖少少阅读 1,238评论 0 14
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,514评论 18 399
  • (一)Struts、Spring、Hibernate、Mybatis框技术 1.Struts2.0有几种标签库 【...
    独云阅读 3,208评论 0 62
  • 人生的最终目的不一定是钱,但是没有钱却是万万不行。我们的主业给我们带来了稳定的现金流,因此,我们一定要珍惜自己的主...
    看世间涛走云飞阅读 188评论 0 0