撩课-Java面试题合辑100-150题
2018-12-05 本文已影响12人
码蚁Q
101.请解释下 ORM?
对象关系映射(Object Relational Mapping)模式
是一种为了解决面向对象与关系数据库
存在的互不匹配的现象的技术。
简单来说,
ORM是通过使用描述对象和数据库之间映射的元数据,
将程序中的对象自动持久化到关系数据库中。
传统如何实现持久化:
1、采用硬编码方式,
为每一种可能的数据库访问
操作提供单独的方法,
2、ORM提供了对数据库的映射,
不用sql直接编码,
能够像操作对象一样从数据库获得数据。
ORM核心原则:
1、简单:
以最基本的形式建模数据。
2、传达性:
数据库结构被任何人都能理解的语言文档化。
3、精确性:
基于数据库模型创建正确标准化的结构。
ORM技术特点:
1、提高开发效率。
由于可以自动对entity对象与数据库中的table
进行字段与属性的映射,
所以我们实际可能已经不需要一个专用的、
庞大的数据访问层。
ORM缺点:
使用ORM所生成的代码
一般不太可能写出很高效的算法,
性能会受影响。
102.Hibernate 与 JPA 区别在哪?
在讨论Hibernate与Jpa的关系是,
首先要明确Jpa的用途。
JPA全称为Java Persistence API ,
Java持久化API是Sun公司在Java EE 5规范中
提出的Java持久化接口。
JPA吸取了目前Java持久化技术的优点,
旨在规范、简化Java对象的持久化工作。
使用JPA持久化对象,
并不是依赖于某一个ORM框架。
与Jpa相关的就是这个ORM技术,
ORM 是Object-Relation-Mapping,
即对象关系影射技术,
是对象持久化的核心。O
RM是对JDBC的封装,
从而解决了JDBC的各种存在问题:
知道Jpa是一种规范,
而Hibernate是它的一种实现。
除了Hibernate,
还有EclipseLink(曾经的toplink),
OpenJPA等可供选择,
所以使用Jpa的一个好处是,
可以更换实现而不必改动太多代码。
103.什么是懒加载(Lazy Loading)?
1. 懒加载顾名思义就是延迟加载。
即当两个及以上表
使用Hibernate来管理级联关系时,
一个表被加载到JVM内存中,
而其他表会被暂存在数据库的缓存中,
当需要使用相关表数据的时候
再加载到JVM内存中。
因此通过懒加载机制可以
减少内存中不必要的开销,
以提高程序的性能。
2. 问题描述:当使用懒加载时,
如果将session关闭,
则断开了与数据库的连接,
这时如果要访问字表数据,
由于字表数据存放在数据库缓存中,
而连接已经关闭,
则访问字表数据时会抛出LazyInitializationException异常。
解决办法:
1. 不使用懒加载,将字表配置文件中的lazy属性改为false
2.使用过滤器,
把close session操作推迟到得到页面结果后执行,
这里可以采用Spring中的OpenSessionViewFilter过滤器来实现
3.手动将需要的字表数据
从数据库缓存加载到jvm内存中
104.什么是 N+1 难题?
一般而言说n+1意思是,
无论在一对多还是多对一当查询出n条数据之后,
每条数据会关联的查询1次他的关联对象,
这就叫做n+1。
本来所有信息可以一次性查询出来,
也就是简单的连表查询,
但是Hibernate会首先查询1次得到当前对象,
然后当前对象里面的n个关联对象
会再次访问数据库n次,
这就是1+n问题。
Hibernate给出了3中解决方案,
1.延迟加载,
当需要的时候才查询,不
需要就不查询,
但是感觉这种方式治标不治本,
尤其是在那种报表统计查询的时候更为明显。
2.fetch="join",
默认是fetch="select",
这个其实说白了就是一个做外连接,
允许外键为空的情况之下。
3.二级缓存,
第一次查询之后存在内存中,
后面的相同查询就快了。
但是有2个缺点:
二级缓存首先是有点浪费内存空间,
如果多了的话浪费还比较严重,
105.简介下 Hibernate Session 与 SessionFactory。
Session接口负责执行
被持久化对象的CRUD操作
CRUD的任务是完成与数据库的交流,
包含了很多常见的SQL语句。
需要注意的是,
Session对象是非线程安全的,
同时,
Hibernate的Session
不同于JSP应用中的HttpSession。
这里当使用Session这个术语时,
其实指的是Hibernate中的Session,
而以后会将HttpSession对象
称为用户Session。
SessionFactory接口负责初始化Hibernate。
它充当数据存储源的代理,
并负责创建Session对象。
这里用到了工厂模式。
需要注意的是,
SessionFactory并不是轻量级的,
因为一般情况下,
一个项目通常只需要
一个SessionFactory就够,
当需要操作多个数据库时,
可以为每个数据库指定
一个SessionFactory。
撩课Java+系统架构点击开始学习
106.什么是Hibernate的并发机制?怎么去处理并发问题?
a、Hibernate的Session对象是非线程安全的,
对于单个请求,单个会话,
单个的工作单元(即单个事务,单个线程),
它通常只使用一次, 然后就丢弃。
如果一个Session 实例允许共享的话,
那些支持并发运行的,
例如Http request,session beans
将会导致出现资源争用。
如果在Http Session中有hibernate的Session的话,
就可能会出现同步访问Http Session。
只要用户足够快的点击浏览器的“刷新”,
就会导致两个并发运行的线程
使用同一个Session。
b、多个事务并发访问同一块资源,
可能会引发第一类丢失
更新,
脏读,
幻读,
不可重复读,
第二类丢失更新一系列的问题。
解决方案:设置事务隔离级别。
Serializable:串行化。隔离级别最高
Repeatable Read:可重复读
Read Committed:已提交数据读
Read Uncommitted:未提交数据读。隔离级别最差
设置锁:乐观锁和悲观锁。
乐观锁:使用版本号或时间戳来检测更新丢失,
在的映射中设置 optimistic-lock=”all”
可以在没有版本或者时间戳属性
映射的情况下实现 版本检查,
此时Hibernate将比较一行记录的每个字段的状态
行级悲观锁:Hibernate总是使用数据库的锁定机制,
从不在内存中锁定对象!
只要为JDBC连接指定一下隔 离级别,
然后让数据库去搞定一切就够了。
类LockMode 定义了Hibernate所需的不同的锁定级别:
LockMode.UPGRADE,
LockMode.UPGRADE_NOWAIT,
LockMode.READ;
107.update和saveOrUpdate的区别?
update()和saveOrUpdate()
是用来对跨Session的PO进行状态管理的。
update()方法操作的对象必须是持久化了的对象。
也就是说,如果此对象在数据库中不存在的话,
就不能使用update()方法。
saveOrUpdate()方法
操作的对象既可以使持久化了的,
也可以使没有持久化的对象。
如果是持久化了的对象调用saveOrUpdate()
则会 更新数据库中的对象;
如果是未持久化的对象使用此方法,
则save到数据库中。
108.hibernate的三种状态之间如何转换
当对象由瞬时状态(Transient)一save()时,
就变成了持久化状态;
当我们在Session里存储对象的时候,
实际是在Session的Map里存了一份,
也就是它的缓存里放了一份,
然后,又到数据库里存了一份,
在缓存里这一份叫持久对象(Persistent)。
Session 一 Close()了,
它的缓存也都关闭了,
整个Session也就失效了,这个时候,
这个对象变成了游离状态(Detached),
但数据库中还是存在的。
当游离状态(Detached)update()时,
又变为了持久状态(Persistent)。
当持久状态(Persistent)delete()时,
又变为了瞬时状态(Transient), 此时,
数据库中没有与之对应的记录。
109.比较hibernate的三种检索策略优缺点?
1立即检索;
优点:
对应用程序完全透明,
不管对象处于持久化状态,
还是游离状态,
应用程序都可以方便的
从一个对象导航到与它关联的对象;
缺点:
1.select语句太多;
2.可能会加载应用程序
不需要访问的对象白白浪费许多内存空间;
2延迟检索:
优点:
由应用程序决定需要加载哪些对象,
可以避免可执行多余的select语句,
以及避免加载应用程序不需要访问的对象。
因此能提高检索性能,
并且能节省内存空间;
缺点:
应用程序如果希望访问游离状态代理类实例,
必须保证他在持久化状态时已经被初始化;
3 迫切左外连接检索
优点: 1对应用程序完全透明,
不管对象处于持久化状态,
还是游离状态,
应用程序都可以方便地冲
一个对象导航到与它关联的对象。
2使用了外连接,
select语句数目少;
缺点:
1 可能会加载应用程序不需要访问的对象,
白白浪费许多内存空间;
2复杂的数据库表连接也会影响检索性能;
110.Hibernate工作原理及为什么要用?
1.读取并解析配置文件
2.读取并解析映射信息,创建SessionFactory
3.打开Sesssion
4.创建事务Transation
5.持久化操作
6.提交事务
7.关闭Session
8.关闭SesstionFactory
为什么要用:
1. 对JDBC访问数据库的代码做了封装,
大大简化了数据访问层繁琐的重复性代码。
Hibernate是一个基于JDBC的主流持久化框架,
是一个优秀的ORM实现。
他很大程度的简化DAO层的编码工作
hibernate使用Java反射机制,
而不是字节码增强程序来实现透明性。
hibernate的性能非常好,
因为它是个轻量级框架。
映射的灵活性很出色。
它支持各种关系数据库,
从一对一到多对多的各种复杂关系。
撩课Java+系统架构点击开始学习
111.什么是乐观锁(Optimistic Locking)?
悲观锁,正如其名,
它指的是对数据被外界
包括本系统当前的其他事务,
以及来自外部系统的事务处理
修改持保守态度,
因此,在整个数据处理过程中,
将数据处于锁定状态。
悲观锁的实现,
往往依靠数据库提供的锁机制
也只有数据库层提供的锁机制才能真正保证数据访问的排他性,
否则,即使在本系统中实现了加锁机制,
也无法保证外部系统不会修改数据
乐观锁( Optimistic Locking )
相对悲观锁而言,
乐观锁假设认为数据
一般情况下不会造成冲突,
所以在数据进行提交更新的时候,
才会正式对数据的冲突与否进行检测,
如果发现冲突了,
则让返回用户错误的信息,
让用户决定如何去做
112.描述下Hibernate当中事务?
一、事务的并发问题
在实际应用中,
数据库是要被I多个用户共同访问的,
在多个事务同时使用相同的数据时,
可能会发生并发的问题,
具体为:
脏读:
一个事务读取到
另一个事务未提交的数据。
不可重复读:
一个事务对同一行数据
重复读取两次,
但是得到了不同的结果。
虚读:
一个事务连续两次在数据库进行同样条件的查询,
但是第二次查询结果包含了
第一次查询中未出现的数据。
(注意与不可重复读的区别)
更新丢失:
两个事物同时更新一行数据,
后提交(或撤销)的事务将前面事务
提交的数据都覆盖了。
第一类丢失更新:
是由于后面事务撤销将前面事务
修改的数据覆盖了
第二类丢失更新:
是由于后面事务更新将前面事务
修改的数据覆盖了
1、读未提交(1级,可防止更新丢失):
当一个事务进行写(更新)数据,
则另外一个事务不允许同时进行写(更新)数据,
但允许其他事务进行读数据,
即使是未提交的 (但这个不能防止脏读)
更新丢失是因为一个事务开始进行写事务,
另一个事务也可以进行写事务导致的。
2、读已提交(2级,可防止更新丢失、脏读):
读取数据的事务允许其他事务继续读取该数据,
但是未提交的写事务将会禁止其他事务访问改行。
脏读是由于读到未提及的数据
3、可重复读(4级,可防止不可重复读、脏读):
读取数据的事务禁止写事务,
写事务将禁止其他任何事务。
不可重复读是一个事务
对一行数据重复读取两次,
但是得到不同的结果;
脏读是读到未提交的数据
4、序列化(8级,防止一切并发问题):
提供严格的事务隔离。
事务只能一个接着一个执行,
不能并发执行
简单总结:
事务并发问题:
(1)脏读
(2)不可重复读
(3)虚读
(4)更新丢失
事务隔离级别:
(1)读未提交
(2)读已提交
(3)可重复读
(4)序列化
在hibernate.cfg.xml的标签配置:
使用本地事务还是全局事务,参数是:
hibernate.current_session_context_class
值:thread(本地事务)、jta(全局事务)
设置事务隔离级别,参数是:
hibernate.connection.isolation
当多个事务同时访问数据库中的相同数据时,
如果没有采取必要的隔离措施,
那么可以采用悲观锁或者是乐观锁对其进行控制。
悲观锁:
悲观地认为总是会有其他事务回来操作同一数据
因此在这个数据处理过程中,
将会把数据处于锁定状态。
比如读取数据时:
User user = (User)session.get(User.class, "111111", LockMode.UPGRADE)
乐观锁:
认为事务同时操作同一个数据的情况很少发生,
所以乐观锁不做数据库层次的锁定,
而是基于数据版本标识
实现应用程序级别上的锁定机制。
原理:数据版本标识,
是指通过为数据表增加一个“version”字段,
实现在读取数据时,
将版本号一同独处,
之后更新次数据时,
将此版本号加一;
在提交数据时,
将现有的版本号
与数据表对应的记录的版本号进行对比,
如果提交数据的版本号大于数据表中的版本号,
则允许更新数据,
否则禁止更新数据。
113.Hibernate 中的缓存分为几层。
hibernate的缓存包括
Session的缓存和SessionFactory的缓存,
其中SessionFactory的缓存又可以分为两类:
内置缓存和外置缓存。
Session的缓存是内置的,
不能被卸载,
也被称为Hibernate的第一级缓存。
SessionFactory的内置缓存
和Session的缓存在实现方式上比较相似,
前者是SessionFactory对象的一些集合属性包含的数据,
后者是指Session的一些集合属性包含的数据。
SessionFactory的内置缓存中存放了
映射元数据和预定义SQL语句,
映射元数据是映射文件中数据的拷贝,
而预定义SQL语句是
在Hibernate初始化阶段根据
映射元数据推导出来,
SessionFactory的内置缓存是只读的,
应用程序不能修改缓存中的映射元数据
和预定义SQL语句,
因此SessionFactory不需要进行内置缓存与映射文件的同步。
SessionFactory的外置缓存是一个可配置的插件。
在默认情况下,SessionFactory不会启用这个插件。
外置缓存的数据是数据库数据的拷贝,
外置缓存的介质可以是内存或者硬盘。
SessionFactory的外置缓存
也被称为Hibernate的第二级缓存
114.Entity Beans 的状态有哪些
瞬时(transient):
数据库中没有数据与之对应,
超过作用域会被JVM垃圾回收器回收,
一般是new出来且与session没有关联的对象。
持久(persistent):
数据库中有数据与之对应,
当前与session有关联,
并且相关联的session没有关闭,
事务没有提交;
持久对象状态发生改变,
在事务提交时会影响到数据库(hibernate能检测到)。
脱管(detached):
数据库中有数据与之对应,
但当前没有session与之关联;
托管对象状态发生改变,
hibernate不能检测到。
115.谈谈Hibernate中inverse的作用
Hibernate中的inverse
在表关系映射中经常应用
inverse的值有两种,“true”和“false”。
inverse="false"是默认的值
inverse的作用:
在hibernate中是通过inverse的设置
来决定是有谁来维护表和表之间的关系的。
我们说inverse设立不当会导致性能低下,
其实是说inverse设立不当,
会产生多余重复的SQL语句
甚至致使JDBC exception的throw。
这是我们在建立实体类关系时
必须需要关注的地方。
一般来说,inverse=true是推荐使用,
双向关联中双方都设置 inverse=false的话,
必会导致双方都重复更新同一个关系。
但是如果双方都设立inverse=true的话,
双方都不维护关系的更新,
这也是 不行的,
好在一对多中的一端:
many-to-one默认是inverse=false,
避免了这种错误的产生。
但是多对多就没有这个默认设置了,
所以很 多人经常在多对多的两端都使用inverse=true,
结果导致连接表的数据根本没有记录,
就是因为他们双分都没有责任维护关系。
所以说,双向关联中最 好的设置
是一端为inverse=true,
一端为inverse=false。
一般inverse=false会放在多的一端,
那么有人提问了, many-to-many两边都是多的,
inverse到底放在哪儿?
其实hibernate建立多对多关系
也是将他们分离成两个一对多关系,
中间连接一个连接表。
所以通用存在一对多的关系,
也可以这样说:
一对多是多对多的基本组成部分。
116.说下Struts的设计模式
MVC模式:
web应用程序启动时
就会加载并初始化ActionServler。
用户提交表单时,
一个配置好的ActionForm对象被创建,
并被填入表单相应的数据,
ActionServler根据Struts-config.xml文件
配置好的设置决定是否需要表单验证,
如果需要就调用ActionForm的Validate()
验证后选择将请求发送到哪个Action,
如果Action不存在,
ActionServlet会先创建这个对象,
然后调用Action的execute()方法。
Execute()从ActionForm对象中获取数据,
完成业务逻辑,
返回一个ActionForward对象,
ActionServlet再把客户请求
转发给ActionForward对象指定的jsp组件,
ActionForward对象指定的jsp生
成动态的网页,返回给客户。
117.拦截器和过滤器的区别?
1、拦截器是基于java反射机制的,
而过滤器是基于函数回调的。
2、过滤器依赖于servlet容器,
而拦截器不依赖于servlet容器。
3、拦截器只能对Action请求起作用,
而过滤器则可以对几乎所有请求起作用。
4、拦截器可以访问Action上下文、
值栈里的对象,而过滤器不能。
5、在Action的生命周期中,
拦截器可以多次调用,
而过滤器只能在容器初始化时被调用一次。
118.struts2框架的核心控制器是什么?它有什么作用?
1)Struts2框架的核心控制器是
StrutsPrepareAndExecuteFilter。
2)作用:
负责拦截由<url-pattern>/*</url-pattern>
指定的所有用户请求,
当用户请求到达时,
该Filter会过滤用户的请求。
默认情况下,
如果用户请求的路径
不带后缀或者后缀以.action结尾,
这时请求将被转入struts2框架处理,
否则struts2框架将略过该请求的处理。
可以通过常量"struts.action.extension"修改action的后缀,
如:
<constant name="struts.action.extension" value="do"/>
如果用户需要指定多个请求后缀,
则多个后缀之间以英文逗号(,)隔开。
<constant name="struts.action.extension" value="do,go"/>
119.struts2如何访问HttpServletRequest、HttpSession、ServletContext三个域对象?
方案一:
HttpServletRequest request =
ServletActionContext.getRequest();
HttpServletResponse response =
ServletActionContext.getResponse();
HttpSession session=
request.getSession();
ServletContext servletContext=
ServletActionContext.getServletContext();
方案二:
类 implements ServletRequestAware,ServletResponseAware,SessionAware,ServletContextAware
注意:框架自动传入对应的域对象
120.ActionContext、ServletContext、pageContext的区别?
1)ActionContext是当前的Action的上下文环境,
通过ActionContext可以
获取到request、session、ServletContext等
与Action有关的对象的引用;
2)ServletContext是域对象,
一个web应用中只有一个ServletContext,
生命周期伴随整个web应用;
3)pageContext是JSP中的最重要的一个内置对象,
可以通过pageContext获取其他域对象的应用,
同时它是一个域对象,
作用范围只针对当前页面,
当前页面结束时,
pageContext销毁,
生命周期是JSP四个域对象中最小的。
121.描述Struts2的工作原理
客户端发送请求--》
请求经过一系列过滤器->
FilterDispatcher通过
ActionMapper来决定这个Reques
t需要调用哪个Action ->
FilterDispatcher把请求的处理交给ActionProxy->
通过ConfigurationManager询问
Struts配置文件(Struts.xml)
找到需要调用的Action类->
ActionProxy创建一个ActionInvocation的实例 ->
调用Action->执行完毕,
返回结果
122.result的type属性中有哪几种结果类型?
一共10种:
dispatcher
struts默认的结果类型,
把控制权转发给应用程序里的
某个资源不能把控制权
转发给一个外部资源,
若需要把控制权重定向到
一个外部资源, 应该使用
redirect结果类型
redirect
把响应重定向到另一个资源(包括一个外部资源)
redirectAction
把响应重定向到另一个 Action
freemarker、
velocity、
chain、
httpheader、
xslt、
plainText、
stream
123.拦截器的生命周期与工作过程?
1)每个拦截器都是实现了I
nterceptor接口的 Java 类;
2)init(): 该方法将在拦截器被创建后立即被调用,
它在拦截器的生命周期内只被调用一次.
可以在该方法中对相关资源
进行必要的初始化;
3)intercept(ActionInvocation invocation):
每拦截一个动作请求,
该方法就会被调用一次;
4)destroy:
该方法将在拦截器被销毁之前被调用,
它在拦截器的生命周期内也只被调用一次;
5)struts2中有内置了18个拦截器。
124.struts2如何完成文件的上传?
1、JSP页面:
1)JSP页面的上传文件的组件:
<s: file name=”upload” />,
如果需要一次上传多个文件,
就必须使用多个 file 标签,
但它们的名字必须是相同的,
即: name=“xxx”的值必须一样;
2)必须把表单的enctype属性设置为:
multipart/form-data;
3)表单的方法必须为post,
因为post提交的数据在消息体中,
而无大小限制。
2、对应的action:
1)在 Action 中新添加 3 个
和文件上传相关的属性;
2)如果是上传单个文件,
uploadImage属性的类型就是
java.io.File, 它代表被上传的文件,
第二个和第三个属性的类型是 String,
它们分别代表上传文 件的文件名和文件类型,
定义方式是分别是:
jsp页面file组件的名称+ContentType,
jsp页面file组件的名称+FileName
3)如果上上传多个文件,
可以使用数组或 List
125.值栈ValueStack的原理与生命周期?
1)ValueStack贯穿整个 Action 的生命周期,
保存在request域中,
所以ValueStack和request的生命周期一样。
当Struts2接受一个请求时,
会迅速创建ActionContext,
ValueStack,action。
然后把action存放进ValueStack,
所以action的实例变量可以被OGNL访问。
请求来的时候,
action、ValueStack的生命开始,
请求结束,action、 ValueStack的生命结束;
2)action是多例的,和Servlet不一样,
Servelt是单例的;
3)每个action的都有一个对应的值栈,
值栈存放的数据类型是该action的实例,
以及该action中的实例变量,
Action对象默认保存在栈顶;
4)ValueStack本质上就是一个ArrayList;
5)关于ContextMap,
Struts 会把下面这些映射压入 ContextMap 中:
parameters :
该 Map 中包含当前请求的请求参数
request :
该 Map 中包含当前 request 对象中的
所有属性 session
:该 Map 中包含当前 session 对象中的所有属性
application
:该 Map 中包含当前
application 对象中的所有属性
attr:该 Map 按如下顺序来检索某个属性:
request, session, application
6)使用OGNL访问值栈的内容时,
不需要#号,
而访问request、session、application、attr时,
需要加#号;
7)注意: Struts2中,
OGNL表达式需要配合Struts标签才可以使用。
如:<s:property value="name"/>
8)在struts2配置文件中引用ognl表达式 ,
引用值栈的值 ,此时使用的"$",而不是#或者%;
126.Struts2中的拦截器有什么用?列举框架提供的拦截器名称?
1)拦截器是struts2核心组成部分,
它提供了一种机制,使得开发者
可以定义一个特定的功能模块,
这个模块会在Action执行之前或者之后执行
也可以在Action执行之前阻止Action执行。
2)常用的拦截器有:
chain:在不同请求之间将请求参数在不同名字件转换,
请求内容不变
fileUpload:提供文件上传。
i18n:记录用户选择的区域环境
logger:输出Action的名字
params:将请求中的参数设置到Action中去。
127.Struts2有哪些优点?
1)在软件设计上Struts2的应用
可以不依赖于Servlet API和struts API。
Struts2的这种设计属于无侵入式设计;
2)拦截器,
实现如参数拦截注入等功能;
3)类型转换器,
可以把特殊的请求参数转换成需要的类型;
4)多种表现层技术,
如:JSP、freeMarker、Velocity等;
5)Struts2的输入校验
可以对指定某个方法进行校验;
6)提供了全局范围、
包范围和Action范围的
国际化资源文件管理实现
7) 实现MVC模式,结构清晰,
使开发者只关注业务逻辑的实现。
有丰富的tag可以用,
大提高了开发效率。(简要)
128.ActionContext和ValueStack什么时候创建?是否是线程安全的?
明确:
动作类是多例的,
每次动作访问,
动作类都会实例化。
所以是线程安全的。
与Struts1的区别是,
struts1的动作类是单例的。
在每次动作执行前,
核心控制器StrutsPrepareAndExecuteFilter
都会创建一个ActionContext和ValueStack对象。
且每次动作访问都会创建。
这两个对象存储了整个动作
访问期间用到的数据。
并且把数据绑定到了线程局部变量
ThreadLocal上了。
所以是线程安全的。
129.一个请求在Struts2框架中的处理大概分为几个步骤?
1) 客户端初始化一个
指向Servlet容器(例如Tomcat)的请求
2 )这个请求经过一系列的过滤器Filter
这些过滤器中有一个叫做
ActionContextCleanUp的可选过滤器,
这个过滤器对于Struts2和
其他框架的集成很有帮助,
例如:SiteMesh Plugin)
3 )接着FilterDispatcher被调用,
FilterDispatcher询问ActionMapper
来决定这个请是否需要调用某个Action
4 )如果ActionMapper决定需要调用某个Action,
FilterDispatcher把请求的处理交给ActionProxy
5 )ActionProxy通过Configuration Manager
询问框架的配置文件,
找到需要调用的Action类
6 )ActionProxy创建一个ActionInvocation的实例。
7 )ActionInvocation实例使用命名模式来调用,
在调用Action的过程前后,
涉及到相关拦截器(Intercepter)的调用。
8 )一旦Action执行完毕,
ActionInvocation负责根据struts.xml中的配置
找到对应的返回结果。
返回结果通常是一个需要
被表示的JSP或者FreeMarker的模版。
在表示的过程中可以使用Struts2 框架中
继承的标签。
在这个过程中需要涉及到ActionMapper
130.介绍一下Struts的ActionServlet类
ActionServlet继承自
javax.servlet.http.HttpServlet类,
其在Struts framework中扮演的角色
是中心控制器。
它提供一个中心位置来处理全部的终端请求。
控制器ActionServlet主要
负责将HTTP的客户请求信息组装后,
根据配置文件的指定描述,
转发到适当的处理器。
按照Servelt的标准,
所有得Servlet必须在
web配置文件(web.xml)声明。
同样,ActoinServlet必须在
Web Application配置文件(web.xml)中描述
当用户向服务器端提交请求的时候,
实际上信息是首先发送到控制器ActionServlet,
一旦控制器获得了请求,
其就会将请求信息传交给一些辅助类(help classes)处理。
这些辅助类知道如何去处理与请求信息所对应的业务操作。
在Struts中,
这个辅助类就是org.apache.struts.action.Action。
通常开发者需要自己继承Aciton类,
从而实现自己的Action实例。
131.Spring是什么?
他解决的是业务逻辑层和其他各层的松耦合问题,
因此它将面向接口的编程思想
贯穿整个系统应用。
Spring是一个轻量级的IoC和AOP容器框架。
目的是解决企业应用开发的复杂性,
使用基本的JavaBean来完成
以前只可能由EJB完成的事情,
并提供了更多的企业应用功能,
Spring的用途不仅限于服务器端的开发,
从简单性、
可测试性和松耦合的角度而言,
任何Java应用都可以从Spring中受益。
132.说说Spring 的优点?
1.spring属于低侵入式设计,
代码的污染极低;
2.spring的DI机制降低了
业务对象替换的复杂性;
3.容器提供了AOP技术,
利用它很容易实现如权限拦截,
运行期监控等功能;
4.降低了组件之间的耦合性 ,
实现了软件各层之间的解耦;
5.容器提供单例模式支持;
6.可以使用容器提供的众多服务,
如事务管理,消息服务等;
7.容器提供了众多的辅助类,
能加快应用的开发;
8.spring对于主流的应用框架提供了集成支持,
如hibernate,JPA,Struts等
9.独立于各种应用服务器
10.Spring的高度开放性,
并不强制应用完全依赖于Spring,
开发者可以自由选择spring的部分或全部。
133.说说你对Spring的IoC与DI的理解
(1)IOC就是控制反转。
就是对象的创建权反转交给Spring,
由容器控制程序之间的依赖关系,
作用是实现了程序的解耦合,
而非传统实现中,
由程序代码直接操控。
(依赖)控制权由应用代码本身转到了外部容器,
由容器根据配置文件去创建实例
并管理各个实例之间的依赖关系,
控制权的转移,
是所谓反转,
并且由容器动态的将某种依赖关系注入到组件之中。
BeanFactory 是Spring IoC容器的具体实现与核心接口,
提供了一个先进的配置机制,
使得任何类型的对象的配置成为可能,
用来包装和管理各种bean。
2.最直观的表达就是,
IOC让对象的创建不用去new了,
可以由spring自动生产,
这里用的就是java的反射机制,
通过反射在运行时动态的去创建、
调用对象。
spring就是根据配置文件
在运行时动态的去创建对象,
并调用对象的方法的。
3.Spring的IOC有三种注入方式 :
第一是根据属性注入,也叫set方法注入;
第二种是根据构造方法进行注入;
第三种是根据注解进行注入。
详细的说:
IOC控制反转:
将对象交给容器管理,
你只需要在spring配置文件总配置相应的bean,
以及设置相关的属性,
让spring容器生成类的实例对象以及管理对象。
在spring容器启动的时候,
spring会把你在配置文件中配
置的bean都初始化以及装配好,
然后在你需要调用的时候,
就把它已经初始化好的那些bean
分配给你需要调用这些bean的类。
就是将对象的控制权反转给spring容器管理。
DI机制(Dependency Injection,依赖注入):
可以说是IoC的其中一个内容,
在容器实例化对象的时候
主动的将被调用者(或者说它的依赖对象)
注入给调用对象。
比如对象A需要操作数据库,
以前我们总是要在A中
自己编写代码来获得一个Connection对象,
有了 spring我们就只需要告诉spring,
A中需要一个Connection,
至于这个Connection怎么构造,
何时构造,
A不需要知道。
在系统运行时,
spring会在适当的时候制造一个Connection,
然后像打针一样,注射到A当中,
这样就完成了对各个对象之间关系的控制。
134.解释Spring支持的几种bean的作用域
Spring容器中的bean可以分为5个范围:
1.singleton:这种bean范围是默认的,
这种范围确保不管接受到多少个请求,
每个容器中只有一个bean的实例,
单例的模式由bean factory自身来维护。
2.prototype:原形范围与单例范围相反,
为每一个bean请求提供一个实例。
3.request:在请求bean范围内
会每一个来自客户端的网络请求创建一个实例,
在请求完成以后,
bean会失效并被垃圾回收器回收。
4.Session:与请求范围类似,
确保每个session中有一个bean的实例,
在session过期后,
bean会随之失效。
5.global-session:
global-session和Portlet应用相关。
当你的应用部署在Portlet容器中工作时,
它包含很多portlet。
如果你想要声明让所有的portlet
共用全局的存储变量的话,
那么这全局变量需要存储在global-session中。
全局作用域与Servlet中的session
作用域效果相同。
135.BeanFactory 接口和 ApplicationContext 接口有什么区别 ?
BeanFactory和ApplicationContext
是Spring的两大核心接口,
而其中ApplicationContext是BeanFactory的子接口。
它们都可以当做Spring的容器,
生成Bean实例的,
并管理容器中的Bean。
1.BeanFactory:
是Spring里面最底层的接口,
提供了最简单的容器的功能,
负责读取bean配置文档,
管理bean的加载与实例化,
维护bean之间的依赖关系,
负责bean的生命周期,
但是无法支持spring的aop功能和web应用。
2.ApplicationContext接口
作为BeanFactory的派生,
因而具有BeanFactory所有的功能。
而且ApplicationContext还在功能上做了扩展,
以一种更面向框架的方式工作以及对上下文进行分层和实现继承,
相较于BeanFactorty,
ApplicationContext还提供了以下的功能:
①默认初始化所有的Singleton,也可以通过配置取消预初始化。
②继承MessageSource,因此支持国际化。
③资源访问,比如访问URL和文件。
④事件机制。
⑤同时加载多个配置文件。
⑥以声明式方式启动并创建Spring容器。
⑦载入多个(有继承关系)上下文 ,
使得每一个上下文
都专注于一个特定的层次,
比如应用的web层。
BeanFactroy采用的是延迟加载形式
来注入Bean的,
即只有在使用到某个Bean时(调用getBean()),
才对该Bean进行加载实例化,
这样,我们就不能发现一些存在的Spring的配置问题。
如果Bean的某一个属性没有注入,B
eanFacotry加载后,
直至第一次使用调用getBean方法
才会抛出异常。
而ApplicationContext则相反,
它是在容器启动时,
一次性创建了所有的Bean。
这样,在容器启动时,
我们就可以发现Spring中存在的配置错误,
这样有利于检查所依赖属性是否注入。
ApplicationContext启动后
预载入所有的单实例Bean,
通过预载入单实例bean ,
确保当你需要的时候,
你就不用等待,
因为它们已经创建好了。
相对于基本的BeanFactory,
ApplicationContext 唯一的不足是
占用内存空间。
当应用程序配置Bean较多时,
程序启动较慢。
BeanFactory通常以编程的方式被创建,
ApplicationContext还能以声明的方式创建,
如使用ContextLoader。
BeanFactory和ApplicationContext
都支持BeanPostProcessor、
BeanFactoryPostProcessor的使用,
但两者之间的区别是:
BeanFactory需要手动注册,
而ApplicationContext则是自动注册。
136.请解释Spring Bean的生命周期?
首先说一下Servlet的生命周期:
实例化,
初始init,
接收请求service,
销毁destroy;
Spring上下文中的Bean生命周期也类似,
如下:
1.实例化一个Bean
也就是我们常说的new;
2.按照Spring上下文
对实例化的Bean进行配置
也就是IOC注入;
3.如果这个Bean已经实现了BeanNameAware接口,
会调用它实现的
setBeanName(String)方法,
此处传递的就是Spring配置文件中Bean的id值;
4.如果这个Bean已经实现了BeanFactoryAware接口,
会调用它实现的
setBeanFactory(setBeanFactory(BeanFactory)
传递的是Spring工厂自身
可以用这个方式来获取其它Bean,
只需在Spring配置文件中
配置一个普通的Bean就可以;
5.如果这个Bean已经实现了
ApplicationContextAware接口,
会调用setApplicationContext(ApplicationContext)方法,
传入Spring上下文
同样这个方式也可以实现步骤4的内容,
但比4更好,
因为ApplicationContext是BeanFactory的子接口,
有更多的实现方法
6.如果这个Bean关联了
BeanPostProcessor接口,
将会调用
postProcessBeforeInitialization(Object obj, String s)方法,
BeanPostProcessor
经常被用作是Bean内容的更改,
并且由于这个是在Bean初始化结束时
调用那个的方法,
也可以被应用于内存或缓存技术;
7.如果Bean在Spring配置文件中
配置了init-method属性
会自动调用其配置的初始化方法。
8.如果这个Bean关联了BeanPostProcessor接口,
将会调用
postProcessAfterInitialization(Object obj, String s)方法、;
注:以上工作完成以后就可以应用这个Bean了,
那这个Bean是一个Singleton的,
所以一般情况下
们调用同一个id的Bean会是
在内容地址相同的实例,
当然在Spring配置文件中
也可以配置非Singleton。
9.当Bean不再需要时,
会经过清理阶段,
如果Bean实现了DisposableBean这个接口,
会调用那个其实现的destroy()方法;
10.最后,
如果这个Bean的Spring配置中
配置了destroy-method属性,
会自动调用其配置的销毁方法。
另外我们这里描述的是应用Spring
上下文Bean的生命周期,
如果应用Spring的工厂
也就是BeanFactory的话去掉第5步就Ok了。
137.介绍下Spring的主要模块?
Spring AOP:
Spring的关键组件之一是AOP框架。
AOP在Spring中使用:
提供声明性的企业服务,
特别是作为EJB声明式服务的替代品。
最重要的服务是声明式事务管理,
它建立在Spring的事务抽象之上。
允许用户实现自定义的切面,
补充他们使用AOP的OOP的使用。
Spring ORM:
ORM包与数据库访问有关。
它为流行的对象关系映射api提供集成层,
包括JDO、Hibernate和iBatis。
Spring Web:
web应用程序开发堆栈,
其中包括Spring MVC。
Spring DAO:
Spring的DAO(Data Access Object)
支持主要用于使用JDBC、
Hibernate或JDO等
技术标准化数据访问工作。
Spring Context:
此包构建在bean包之上,
以增加对消息源的支持和观察者的设计模式支持,
以及应用程序对象使用一致的
API获得资源的能力。
Spring Web MVC:
这是为web应用程序
提供MVC实现的模块。
Spring Core:
核心包是Spring框架中最重要的组件。
该组件提供依赖性注入特性。
BeanFactory提供了一种工厂模式,
它将诸如初始化、
创造和访问对象
与实际程序逻辑的访问分离开来。
138.Spring事务的种类和各自的区别?
spring支持
编程式事务管理
声明式事务管理
两种方式:
1.编程式事务管理使用TransactionTemplate
或者直接使用底层的PlatformTransactionManager。
对于编程式事务管理,
spring推荐使用TransactionTemplate。
2.声明式事务管理建立在AOP之上的。
其本质是对方法前后进行拦截,
然后在目标方法开始之前创建或者加入一个事务,
在执行完目标方法之后
根据执行情况提交或者回滚事务。
声明式事务最大的优点
就是不需要通过编程的方式管理事务,
这样就不需要在业务逻辑代码中
掺杂事务管理的代码,
只需在配置文件中
做相关的事务规则声明
或通过基于@Transactional注解的方式
便可以将事务规则应用到业务逻辑中。
3.显然声明式事务管理要优于编程式事务管理,
这正是spring倡导的非侵入式的开发方式。
声明式事务管理使业务代码不受污染,
一个普通的POJO对象,
只要加上注解就可以获得完全的事务支持。
和编程式事务相比,
声明式事务唯一不足地方是,
后者的最细粒度只能作用到方法级别,
无法做到像编程式事务
那样可以作用到代码块级别。
139.说说spring的事务传播行为?
spring事务的传播行为
说的是当一个方法调用另一个方法时,
事务该如何操作。
1.PROPAGATION_REQUIRED:
如果当前没有事务,
就创建一个新事务,
如果当前存在事务,
就加入该事务,
该设置是最常用的设置。
2.PROPAGATION_SUPPORTS:
支持当前事务,
如果当前存在事务,
就加入该事务,
如果当前不存在事务,
就以非事务执行。‘
3.PROPAGATION_MANDATORY:
支持当前事务,
如果当前存在事务,
就加入该事务,
如果当前不存在事务,
就抛出异常。
4.PROPAGATION_REQUIRES_NEW:
创建新事务,
无论当前存不存在事务,
都创建新事务。
5.PROPAGATION_NOT_SUPPORTED:
以非事务方式执行操作,
如果当前存在事务,
就把当前事务挂起。
6.PROPAGATION_NEVER:
以非事务方式执行,
如果当前存在事务,
则抛出异常。
7.PROPAGATION_NESTED:
如果当前存在事务,
则在嵌套事务内执行。
如果当前没有事务,
则执行与PROPAGATION_REQUIRED类似的操作。
140.Spring事务的实现方式和实现原理
1.划分处理单元——IOC:
由于spring解决的问题是
对单个数据库进行局部事务处理的,
具体的实现首相用spring中的IOC
划分了事务处理单元。
并且将对事务的各种配置
放到了ioc容器中
设置事务管理器,
设置事务的传播特性及隔离机制。
2.AOP拦截需要进行事务处理的类:
Spring事务处理模块
是通过AOP功能来实现声明式事务处理的,
具体操作
比如事务实行的配置和读取,
事务对象的抽象,
用TransactionProxyFactoryBean接口
来使用AOP功能,
生成proxy代理对象,
通过TransactionInterceptor完成
对代理方法的拦截,
将事务处理的功能
编织到拦截的方法中。
读取ioc容器事务配置属性,
转化为spring事务处理
需要的内部数据结构
TransactionAttributeSourceAdvisor
转化为TransactionAttribute表示的数据对象。
3.对事物处理实现
事务的生成、提交、回滚、挂起:
spring委托给具体的事务处理器实现。
实现了一个抽象和适配。
适配的具体事务处理器:
DataSource数据源支持、
hibernate数据源事务处理支持、
JDO数据源事务处理支持,
JPA、JTA数据源事务处理支持。
这些支持都是通过设计PlatformTransactionManager、
AbstractPlatforTransaction
一系列事务处理的支持。
为常用数据源支持提供了
一系列的TransactionManager。
结合:
PlatformTransactionManager实现了
TransactionInterception接口,
让其与TransactionProxyFactoryBean结合起来,
形成一个Spring声明式事务处理的设计体系。
141.Spring AOP是什么?
AOP:面向切面编程
AOP技术利用一种称为“横切”的技术,
解剖封装的对象内部,
并将那些影响了多个类的公共行为
封装到一个可重用模块,
这样就能减少系统的重复代码,
降低模块间的耦合度,
并有利于未来的可操作性和可维护性。
AOP把软件系统分为两个部分:
核心关注点
和横切关注点。
业务处理的主要流程是核心关注点,
与之关系不大的部分是横切关注点。
横切关注点的一个特点是,
他们经常发生在核心关注点的多处,
而各处都基本相似。
比如权限认证、日志、事务处理。
142.说说AOP实现原理
AOP:
这里的AOP指的是面向切面编程思想,
而不是Spring AOP
主要的的实现技术主要有
Spring AOP和AspectJ。
1、AspectJ的底层技术。
AspectJ的底层技术是静态代理,
即用一种AspectJ支持的特定语言编写切面,
通过一个命令来编译,
生成一个新的代理类,
该代理类增强了业务类,
这是在编译时增强,
相对于下面说的运行时增强,
编译时增强的性能更好。
Spring AOP
Spring AOP采用的是动态代理,
在运行期间对业务方法进行增强,
所以不会生成新类,
对于动态代理技术,
Spring AOP提供了对
JDK动态代理的支持以及CGLib的支持。
JDK动态代理只能为接口创建动态代理实例,
而不能对类创建动态代理。
需要获得被目标类的接口信息(应用Java的反射技术),
生成一个实现了代理接口的动态代理类(字节码),
再通过反射机制获得动态代理类的构造函数,
利用构造函数生成动态代理类的实例对象,
在调用具体方法前调用invokeHandler方法来处理。
CGLib动态代理需要依赖asm包,
把被代理对象类的class文件加载进来,
修改其字节码生成子类。
但是Spring AOP基于注解配置的情况下,
需要依赖于AspectJ包的标准注解,
但是不需要额外的编译以及AspectJ的织入器,
而基于XML配置不需要。
143. 请描述JDK动态代理和CGLI代理的区别?
1.JDK动态代理
此时代理对象和目标对象实现了相同的接口,
目标对象作为代理对象的一个属性,
具体接口实现中,
可以在调用目标对象相应方法前后
加上其他业务处理逻辑。
代理模式在实际使用时
需要指定具体的目标对象,
如果为每个类都添加一个代理类的话,
会导致类很多,
同时如果不知道具体类的话,
怎样实现代理模式呢?
这就引出动态代理。
JDK动态代理只能
针对实现了接口的类生成代理。
2.CGLIB代理
CGLIB(CODE GENERLIZE LIBRARY)
代理是针对类实现代理,
主要是对指定的类生成一个子类,
覆盖其中的所有方法,
所以该类或方法不能声明称final的。
如果目标对象没有实现接口,
则默认会采用CGLIB代理;
如果目标对象实现了接口,
可以强制使用CGLIB实现代理
添加CGLIB库,
并在spring配置中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>)
144.怎样用注解的方式配置Spring?
Spring在2.5版本以后
开始支持用注解的方式来配置依赖注入。
可以用注解的方式
来替代XML方式的bean描述,
可以将bean描述转移到组件类的内部,
只需要在相关类上、
方法上或者字段声明上使用注解即可。
注解注入将会被容器在XML注入之前被处理,
所以后者会覆盖掉前者
对于同一个属性的处理结果。
注解装配在Spring中是默认关闭的。
所以需要在Spring文件中
配置一下才能使用基于注解的装配模式。
如果你想要在你的应用程序中
使用关于注解的方法的话,
参考如下的配置。
<beans>
<context:annotation-config/>
</beans>
在 <context:annotation-config/>
标签配置完成以后,
就可以用注解的方式在Spring中向属性、
方法和构造方法中自动装配变量。
下面是几种比较重要的注解类型:
@Required:该注解应用于设值方法。
@Autowired:该注解应用于有值设值方法、
非设值方法、
构造方法和变量。
@Qualifier:该注解和@Autowired注解搭配使用,
用于消除特定bean自动装配的歧义。
JSR-250 Annotations:
Spring支持基于JSR-250
注解的以下注解,
@Resource、
@PostConstruct
和 @PreDestroy。
145.如何在Spring中注入一个Java Collection?
• <list> :
该标签用来装配可重复的list值。
<!-- java.util.List -->
<property name="customList">
<list>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>UK</value>
</list>
</property>
• <set> :
该标签用来装配没有重复的set值。
<!-- java.util.Set -->
<property name="customSet">
<set>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>UK</value>
</set>
</property>
• <map>:
该标签可用来注入键和值可以为任何类型的键值对。
<!-- java.util.Map -->
<property name="customMap">
<map>
<entry key="1" value="INDIA"/>
<entry key="2" value="Pakistan"/>
<entry key="3" value="USA"/>
<entry key="4" value="UK"/>
</map>
</property>
• <props> :
该标签支持注入键和值都是字符串类型的键值对。
<!-- java.util.Properties -->
<property name="customProperies">
<props>
<prop key="admin">admin@nospam.com</prop>
<prop key="support">support@nospam.com</prop>
</props>
</property>
146、什么是Spring MVC ?简单介绍下你对springMVC的理解?
Spring MVC是一个基于MVC架构的
用来简化web应用程序开发的应用开发框架,
它是Spring的一个模块,
无需中间整合层来整合 ,
它和Struts2一样都属于表现层的框架。
在web模型中,
MVC是一种很流行的框架,
通过把Model,View,Controller分离,
把较为复杂的web应用分成逻辑清晰的几部分,
简化开发,减少出错,
方便组内开发人员之间的配合。
147、SpringMVC的流程?
(1)用户发送请求至前端控制器DispatcherServlet;
(2) DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;
(3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
(4)DispatcherServlet通过HandlerAdapter处理器适配器调用处理器;
(5)执行处理器(Handler,也叫后端控制器);
(6)Handler执行完成返回ModelAndView;
(7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
(8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
(9)ViewResolver解析后返回具体View;
(10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
(11)DispatcherServlet响应用户。
148.Springmvc的优点:
1.它是基于组件技术的。
全部的应用对象,无论控制器和视图,
还是业务对象之类的都是 java组件.
并且和Spring提供的其他基础结构紧密集成.
2.不依赖于Servlet API(目标虽是如此,
但是在实现的时候确实是依赖于Servlet的)
3.可以任意使用各种视图技术,
而不仅仅局限于JSP
4.支持各种请求资源的映射策略
5.它应是易于扩展的
149.Spring MVC的有哪些主要组键?
1.前端控制器 DispatcherServlet(不需要程序员开发)
作用:接收请求、响应结果 相当于转发器,
有了DispatcherServlet
就减少了其它组件之间的耦合度。
2.处理器映射器HandlerMapping(不需要程序员开发)
作用:根据请求的URL来查找Handler
3.处理器适配器HandlerAdapter
注意:在编写Handler的时候要按照
HandlerAdapter要求的规则去编写,
这样适配器HandlerAdapter
才可以正确的去执行Handler。
4.处理器Handler(需要程序员开发)
5.视图解析器 ViewResolver(不需要程序员开发)
作用:进行视图的解析 根据视图逻辑名解析成真正的视图(view)
6.视图View(需要程序员开发jsp)
View是一个接口,
它的实现类支持不同的视图类型
(jsp,freemarker,pdf等等)
150、springMVC和struts2的区别有哪些?
1.springmvc的入口是一个servlet即前端控制器(DispatchServlet),
而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。
2.springmvc是基于方法开发(一个url对应一个方法),
请求参数传递到方法的形参,
可以设计为单例或多例(建议单例),
struts2是基于类开发,
传递参数是通过类的属性,
只能设计为多例。
3.Struts采用值栈存储请求和响应的数据,
通过OGNL存取数据,
springmvc通过参数解析器是将request请求内容解析,
并给方法形参赋值,
将数据和视图封装成ModelAndView对象,
最后又将ModelAndView中的模型数据
通过reques域传输到页面。
Jsp视图解析器默认使用jstl。