]]>Flash Scope for Spring MVC(Without Spring Web Flow)http://www.tkk7.com/rain1102/archive/2010/05/28/322112.html周锐周锐Fri, 28 May 2010 02:19:00 GMThttp://www.tkk7.com/rain1102/archive/2010/05/28/322112.htmlhttp://www.tkk7.com/rain1102/comments/322112.htmlhttp://www.tkk7.com/rain1102/archive/2010/05/28/322112.html#Feedback0http://www.tkk7.com/rain1102/comments/commentRss/322112.htmlhttp://www.tkk7.com/rain1102/services/trackbacks/322112.htmlhttp://jira.springframework.org/browse/SPR-6464?page=com.atlassian.jira.plugin.system.issuetabpanels%3Aall-tabpanel#issue-tabs
]]>REST in Spring 3: @MVChttp://www.tkk7.com/rain1102/archive/2009/12/24/307201.html周锐周锐Thu, 24 Dec 2009 11:53:00 GMThttp://www.tkk7.com/rain1102/archive/2009/12/24/307201.htmlhttp://www.tkk7.com/rain1102/comments/307201.htmlhttp://www.tkk7.com/rain1102/archive/2009/12/24/307201.html#Feedback0http://www.tkk7.com/rain1102/comments/commentRss/307201.htmlhttp://www.tkk7.com/rain1102/services/trackbacks/307201.htmlIn the last couple of years, REST has emerged as a compelling alternative to SOAP/WSDL/WS-*-based distributed architectures. So when we started to plan our work on the next major release of Spring – version 3.0, it was quite clear to us that we had to focus on making the development of 'RESTful' Web services and applications easier. Now, what is and isn't 'RESTful' could be the topic of a whole new post all together; in this post I'll take a more practical approach, and focus on the features that we added to the @Controller model of Spring MVC.
A Bit of Background
Ok, I lied: there is some background first. If you really want to learn about the new features, feel free to skip to the next section.
For me, work on REST started about two years ago, shortly after reading the highly recommended book RESTful Web Services from O'Reilly, by Leonard Richardson and Sam Ruby. Initially, I was thinking about adding REST support to Spring Web Services, but after working a couple of weeks on a prototype, it became clear to me that this wasn't a very good fit. In particular, I found out that I had to copy most of the logic from the Spring-MVC DispatcherServlet over to Spring-WS. Clearly, this was not the way to go forward.
Around the same time we introduced the annotation-based model of Spring MVC. This model was clearly an improvement to the former, inheritance-based model. Another interesting development at that time was the development of the JAX-RS specification. My next attempt was to try and merge these two models: to try and combine the @MVC annotations with the JAX-RS annotations, and to be able to run JAX-RS applications within the DispatcherServlet. While I did get a working prototype out of this effort, the result was not satisfactory. There were a number of technical issues which I won't bore you with, but most importantly the approach felt 'clunky' and unnatural for a developer who was already used to Spring MVC 2.5.
Finally, we decided to add the RESTful functionality to features to Spring MVC itself. Obviously, that would mean that there would be some overlap with JAX-RS, but at least the programming model would be satisfactory for Spring MVC developers, both existing and new ones. Additionally, there are already three JAX-RS implementations offering Spring support (Jersey, RESTEasy, and Restlet). Adding a fourth to this list did not seem a good use of our valuable time.
Now, enough of the background, let's look at the features!
URI Templates
A URI template is a URI-like string, containing one or more variable names. When these variables are substituted for values, the template becomes a URI. For more information, see the proposed RFC.
In Spring 3.0 M1, we introduced the use of URI templates through the @PathVariable annotation. For instance:
1.@RequestMapping("/hotels/{hotelId}")
2.publicString getHotel(@PathVariableString hotelId, Model model) {
3.List<Hotel> hotels = hotelService.getHotels();
4.model.addAttribute("hotels", hotels);
5.return"hotels";
6.}
When a request comes in for /hotels/1, that 1 will be bound to the hotelId parameter. You can optionally specify the variable name the parameter is bound to, but when you compile your code with debugging enabled that is not necessary: we infer the path variable name from the parameter name.
You can also have more than one path variable, like so:
The above would match /hotels/1/dates/2008-12-18, for instance.
Content Negotiation
In version 2.5, Spring-MVC lets the @Controller decide which view to render for a given request, through its View, view name, and ViewResolver abstractions. In a RESTful scenario, it is common to let the client decide the acceptable representations, via the Accept HTTP header. The server responds with the delivered representation via the Content-Type header. This process is known as content negotiation.
One issue with the Accept header is that is impossible to change it in a web browser, in HTML. For instance, in Firefox, it's fixed to
So what if you want to link to a PDF version of a particular resource? Looking at the file extension is a good workaround. For example, http://example.com/hotels.pdf retrieves the PDF view of the hotel list, as does http://example.com/hotels with an Accept header of application/pdf.
This is what the ContentNegotiatingViewResolver does: it wraps one or more other ViewResolvers, looks at the Accept header or file extension, and resolves a view corresponding to these. In an upcoming blog post, Alef Arendsen will show you how to use the ContentNegotiatingViewResolver.
Views
We also added some new Views to Spring MVC, particularly:
the MarshallingView, which can be used to return an XML representation. This view is based on the Object/XML Mapping module, which has been copied from the Spring Web Services project. This module wraps XML marshalling technologies such as JAXB, Castor, JiBX, and more, and makes it easier to configure these within a Spring application context,
the JacksonJsonView, for JSON representations of objects in your model. This view is actually part of the Spring JavaScript project, which we'll talk about more in a future blog post.
Obviously, these work great in combination with the ContentNegotiatingViewResolver!
HTTP Method Conversion
Another key principle of REST is the use of the Uniform Interface. Basically, this means that all resources (URLs) can be manipulated using the same four HTTP method: GET, PUT, POST, and DELETE. For each of methods, the HTTP specification defines exact semantics. For instance, a GET should always be a safe operation, meaning that is has no side effects, and a PUT or DELETE should be idempotent, meaning that you can repeat these operations over and over again, but the end result should be the same.
While HTTP defines these four methods, HTML only supports two: GET and POST. Fortunately, there are two possible workarounds: you can either use JavaScript to do your PUT or DELETE, or simply do a POST with the 'real' method as an additional parameter (modeled as a hidden input field in an HTML form). This latter trick is what the HiddenHttpMethodFilter does. This filter was introduced in Spring 3.0 M1, and is a plain Servlet Filter. As such, it can be used in combination with any web framework (not just Spring MVC). Simply add this filter to your web.xml, and a POST with a hidden _method parameter will be converted into the corresponding HTTP method request.
As an extra bonus, we've also added support for method conversion in the Spring MVC form tags. For example, the following snippet taken from the updated Petclinic sample:
will actually perform an HTTP POST, with the 'real' DELETE method hidden behind a request parameter, to be picked up by the HiddenHttpMethodFilter. The corresponding @Controller method is therefore:
An ETag (entity tag) is an HTTP response header returned by an HTTP/1.1 compliant web server used to determine change in content at a given URL. It can be considered to be the more sophisticated successor to the Last-Modified header. When a server returns a representation with an ETag header, client can use this header in subsequent GETs, in a If-None-Match header. If the content has not changed, the server will return 304: Not Modified.
In Spring 3.0 M1, we introduced the ShallowEtagHeaderFilter. This is a plain Servlet Filter, and thus can be used in combination any web framework. As the name indicates, the filter creates so-called shallow ETags (as opposed to a deep ETags, more about that later). The way it works is quite simple: the filter simply caches the content of the rendered JSP (or other content), generates a MD5 hash over that, and returns that as a ETag header in the response. The next time a client sends a request for the same resource, it use that hash as the If-None-Match value. The filter notices this, renders the view again, and compares the two hashes. If they are equal, a 304 is returned. It is important to note that this filter will not save processing power, as the view is still rendered. The only thing it saves is bandwith, as the rendered response is not sent back over the wire.
Deep ETags are a bit more complicated. In this case, the ETag is based on the underlying domain objects, RDMBS tables, etc. Using this approach, no content is generated unless the underlying data has changed. Unfortunately, implementing this approach in a generic way is much more difficult than shallow ETags. We might add support for deep ETags in a later version of Spring, by relying on JPA's @Version annotation, or an AspectJ aspect for instance.
And more!
In a following post, I will conclude my RESTful journey, and talk about the RestTemplate, which was also introduced in Spring 3.0 M2. This class gives you client-side access to RESTful resources in a fashion similar to the JdbcTemplate, JmsTemplate, etc.
而测试代码承了AbstractTransactionalJUnit4SpringContextTestsQ代码如下:
@Test
public void testManyToMany() {
Role oneRole = new Role();
oneRole.setDescription("manager");
oneRole.setEnabled(true);
oneRole.setRoleName("manger");
Role twoRole = new Role();
twoRole.setDescription("waitress");
twoRole.setEnabled(true);
twoRole.setRoleName("waitress");
User user = new User();
user.setEnabled(true);
user.setPassword("jianghaiying");
user.setUsername("Jiang HaiYing");
user.addRole(oneRole);
user.addRole(twoRole);
userDAO.persist(user);
try {
userDAO.getConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
q样执行以后Q打印出的信息如下:
Hibernate: insert into user (enabled, password, username) values (?, ?, ?)
Hibernate: insert into role (description, enabled, name) values (?, ?, ?)
Hibernate: insert into role (description, enabled, name) values (?, ?, ?)
q时候问题出来了Qؓ什么没有往关系表中插入数据Q?br />
其实qƈ不是代码或者配|写错误了,在正式运行代码一切正常,而是AbstractTransactionalJUnit4SpringContextTests出的|事实上多对多兌关系是由Hibernated我们l护的,而AbstractTransactionalJUnit4SpringContextTestsZ保持数据的清z又会自动回滚。如何解册个问题呢Q?br />
ҎQ?br />
只需要在testҎ上添?span style="color: #008000">@Rollback(false)Q?/span>不让它回滚,一切正怺。这时候也可以Ltry语句了?br />
Hibernate: insert into user (enabled, password, username) values (?, ?, ?)
Hibernate: insert into role (description, enabled, name) values (?, ?, ?)
Hibernate: insert into role (description, enabled, name) values (?, ?, ?)
Hibernate: insert into user_role (user_id, role_id) values (?, ?)
Hibernate: insert into user_role (user_id, role_id) values (?, ?)
* Invoked by a BeanFactory after it has set all bean properties supplied * (and satisfied BeanFactoryAware and ApplicationContextAware). * <p>This method allows the bean instance to perform initialization only * possible when all bean properties have been set and to throw an * exception in the event of misconfiguration. * @throws Exception in the event of misconfiguration (such * as failure to set an essential property) or if initialization fails. */ void afterPropertiesSet() throws Exception;
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>public class ExampleBean { public void init() { // do some initialization work }}
同下面的完全一P
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>public class AnotherExampleBean implements InitializingBean { public void afterPropertiesSet() { // do some initialization work }}
/** * Invoked by a BeanFactory on destruction of a singleton. * @throws Exception in case of shutdown errors. * Exceptions will get logged but not rethrown to allow * other beans to release their resources too. */ void destroy() throws Exception;
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="destroy"/>public class ExampleBean { public void cleanup() { // do some destruction work (like closing connection) }}
同下面的完全一P
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>public class AnotherExampleBean implements DisposableBean { public void destroy() { // do some destruction work }}
]]>Bind And Validatehttp://www.tkk7.com/rain1102/archive/2009/01/09/250752.html周锐周锐Fri, 09 Jan 2009 15:05:00 GMThttp://www.tkk7.com/rain1102/archive/2009/01/09/250752.htmlhttp://www.tkk7.com/rain1102/comments/250752.htmlhttp://www.tkk7.com/rain1102/archive/2009/01/09/250752.html#Feedback0http://www.tkk7.com/rain1102/comments/commentRss/250752.htmlhttp://www.tkk7.com/rain1102/services/trackbacks/250752.html在Spring MVC体系里,已经提供了bind(request,command)函数q行Bind and Validate工作?
但因为默认的bind()函数是抛出Servlet异常Q而不是返回以数组形式保存错误信息的BindingResult对象供Controller处理 所以BaseController另外实现了一个bindObject函数Q?/p>
BindException bindObject(ServletRequest request, Object command)
1.Bind And Validate的完整用代码:
public BindingResult bindBook(HttpServletRequest request, Book book) throws Exception
{
Integer category_id = new Integer(request.getParameter("category.id"));
book.setCategory(bookManager.getCategory(category_id));
binder.setDisallowedFields(new String[]{"category"});
addValidator(new BookValiator());
return bindObject(request, book);
}
其中W?-3句是l定不能自动l定的Category对象Q(另外一个方案是实现Category的PropertityEditor,q注册,不过q太不实际了Qƈ命obinder忽略q些已被手工l定的field.
注意,如果不忽?binderl定时则很有可能出错?/p>
SimpleDateFormat dateFormat = new SimpleDateFormat(DateUtil.getDatePattern());
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
binder.registerCustomEditor(Integer.class, new CustomNumberEditor(Integer.class, true));
binder.registerCustomEditor(Double.class, new CustomNumberEditor(Double.class, true));
createBinder的另一个callBack函数是getCommandName()QgetCommandName用于在面?lt;spring:bind>l定错误信息时的标识QbaseController默认定ؓ首字母小写的cd?/p>
@Controller
@RequestMapping
public class HelloController{
@RequestMapping
public String world(){
String name = "Eric";
System.out.println("name is = " + name);
return "hello";
}
@RequestMapping
public String test() {
String name = "test";
System.out.println("name is = " + name);
return "test";
}
本节主要分析 CAS 的安全性,以及Z?CAS 被这栯计,带着许密码学的基础知识Q我希望有助于读者对 CAS 的协议有更深层次的理解?
从结构体pȝQ?CAS 包含两部分:
lCAS Server
CAS Server 负责完成对用L认证工作Q?CAS Server 需要独立部|Ԍ有不止一U?CAS Server 的实玎ͼ Yale CAS Server ?ESUP CAS Server 都是很不错的选择?
CAS Server 会处理用户名 / 密码{凭?(Credentials) Q它可能会到数据库检索一条用户帐号信息,也可能在 XML 文g中检索用户密码,对这U方式, CAS 均提供一U灵zM同一的接?/ 实现分离的方式, CAS I竟是用何种认证方式Q跟 CAS 协议是分ȝQ也是Q这个认证的实现l节可以自己定制和扩展?
lCAS Client
CAS Client 负责部v在客LQ注意,我是?Web 应用Q,原则上, CAS Client 的部|意味着Q当有对本地 Web 应用的受保护资源的访问请求,q且需要对h方进行n份认证, Web 应用不再接受M的用户名密码{类似的 Credentials Q而是重定向到 CAS Server q行认证?
如果没记错, CAS 协议应该是由 Drew Mazurek 负责可开发的Q从 CAS v1 到现在的 CAS v3 Q整个协议的基础思想都是Z Kerberos 的票据方式?
CAS v1 非常原始Q传送一个用户名居然?”yes\ndavid.turing” 的方式, CAS v2 开始用了 XML 规范Q大大增Z可扩展性, CAS v3 开始?AOP 技术,?Spring 爱好者可以轻N|?CAS Server 到现有的应用环境中?
CAS 是通过 TGT(Ticket Granting Ticket) 来获?ST(Service Ticket) Q通过 ST 来访问服务,?CAS 也有对应 TGT Q?ST 的实体,而且他们在保?TGT 的方法上虽然有所区别Q但是,最l都可以实现q样一个目的——免dơ登录的ȝ?
下面Q我们看?CAS 的基本协议框Ӟ
基础协议
CAS 基础模式
上图是一个最基础?CAS 协议Q?CAS Client ?Filter 方式保护 Web 应用的受保护资源Q过滤从客户端过来的每一?Web hQ同Ӟ CAS Client 会分?HTTP h中是否包h Service Ticket( 上图中的 Ticket) Q如果没有,则说明该用户是没有经q认证的Q于是, CAS Client 会重定向用户h?CAS Server Q?Step 2 Q?Step 3 是用戯证过E,如果用户提供了正的 Credentials Q?CAS Server 会生一个随机的 Service Ticket Q然后,~存?Ticket Qƈ且重定向用户?CAS Client Q附带刚才生的 Service Ticket Q, Service Ticket 是不可以伪造的Q最后, Step 5 ?Step6 ?CAS Client ?CAS Server 之间完成了一个对用户的n份核实,?Ticket 查到 Username Q因?Ticket ?CAS Server 产生的,因此Q所?CAS Server 的判断是毋庸|疑的?
该协议完成了一个很单的dQ就?User(david.turing) 打开 IE Q直接访?helloservice 应用Q它被立即重定向?CAS Server q行认证Q?User 可能感觉到浏览器?helloservcie ?casserver 之间重定向,?User 是看不到Q?CAS Client ?CAS Server 怺间的 Service Ticket 核实 (Validation) q程。当 CAS Server 告知 CAS Client 用户 Service Ticket 对应凿w䆾Q?CAS Client 才会对当?Request 的用戯行服务?
CAS 如何实现 SSO
当我们的 Web 时代q处于初U阶D늚时候, SSO 是通过׃n cookies 来实玎ͼ比如Q下面三个域名要?SSO Q?
CAS 可以很简单的实现跨域?SSO Q因为,单点被控制在 CAS Server Q用h有h值的 TGC-Cookie 只是?CAS Server 相关Q?CAS Server 只有一个,因此Q解决了 cookies 不能跨域的问题?
回到 CAS 的基协议图,?Step3 完成之后Q?CAS Server 会向 User 发送一?Ticket granting cookie (TGC) l?User 的浏览器Q这?Cookie q?Kerberos ?TGT Q下ơ当用户?Helloservice2 重定向到 CAS Server 的时候, CAS Server 会主?Get 到这?TGC cookie Q然后做下面的事情:
1Q?span style="font: 7pt 'Times New Roman'"> 如果 User 的持?TGC 且其q没失效Q那么就走基协议囄 Step4 Q达C SSO 的效果?
Service 收到Ticket后利用它?/span>KDC之间的密钥将Ticket中的信息解密出来Q从而获?/span>Session Key和用户名Q用户地址Q?/span>IPQ,服务名,有效期。然后再?/span>Session Key?/span>Authenticator解密从而获得用户名Q用户地址Q?/span>IPQ将其与之前Ticket中解密出来的用户名,用户地址Q?/span>IPQ做比较从而验?/span>Client的n份?/span>
public SpringTestCaseBase() {
// query the protected variables to implement denpendency injection automatically,
// so we don't need to write settor and gettor methods anymore.
this.setPopulateProtectedVariables(true);
sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getDefault());
}
protected String[] getConfigLocations() {
return new String[] { "file:WebRoot/WEB-INF/applicationContext*.xml"};
}
Caused by: org.springframework.aop.framework.AopConfigException: Couldn't generate CGLIB subclass of class [class com.xxxx.user.service.impl.UserManagerImpl]: Common causes of this problem include using a finalclass or a non-visible class; nested exception is java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
Caused by: java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
at net.sf.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:718)
<!-- This bean is optional; it isn't used by any other bean as it only listens and logs --> <bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/>
UserDetails user = userDAO.getUserByName(userName); if (user == null) { log.error("The user was not found:" + userName); throw new UsernameNotFoundException("The user was not found:" + userName); } return user; }