有关Spring注解@xxx的零碎知识



在Java的Spring开发中经常使用一些注解,例如 @XXX 等等,在网上看到收集整理碎片知识,便于懒人计划^=^...

过去,Spring使用的Java Bean对象必须在配置文件[一般为application.xml] 中进行配置,然后才能使用,但Spring2.5版之后,引入了配置注解功能,操作更简单,但是不了解的就抽象了,所以有必要了解一下一些注解的知识;

一,首选注意,注解,注入需要的JAR包,即用common-annotations.jar 包的支持;

二,要使用注解,注入功能需在Spring配置文件[一般为application.xml]进行必要的配置才能使用注解,注入功能,例如下面;
参见 http://gtgt1988.iteye.com/blog/1670030

  1. <beans xmlns="...">  
  2.     <!-- 添加注解驱动 -->    
  3.     <context:annotation-config/>   
  4.   
  5.     <!-- 默认扫描的包路径 -->    
  6.     <context:component-scan base-package="cn.org.xxx" />    
  7.   
  8.     <!--指定默认扫描的包路径,同时指定不扫描的包,如默认扫描包下的Service不扫描-->  
  9.     <!--  
  10.     <context:component-scan base-package="xx.xxx.yyy"/>  
  11.     <context:exclude-filter type="annotation" expression="xx.xxx.yyy.Service"/>    
  12.     </context:component-scan>  
  13.     -->  
  14.   
  15.     <!-- Spring MVC 必须的配置 -->  
  16.     <mvc:annotation-driven />    
  17.   
  18.     <!-- 配置js,css等静态文件直接映射到对应的文件夹,不被DispatcherServlet处理 -->  
  19.     <mvc:resources location="/resources/" mapping="/resources/**" />  
  20.    
  21.     <!-- 定义一些视图控制器,完成访问路径到返回视图的映射关系 -->  
  22.     <mvc:view-controller path="/" view-name="forward:/logon"/>   
  23.     <mvc:view-controller path="/permission/login" view-name="permission/login"/>  
  24.     <mvc:view-controller path="/permission/logout" view-name="permission/login"/>  
  25.   
  26.     <!-- ...其他Bean的配置... -->      
  27. </beans>  
<beans xmlns="...">
    <!-- 添加注解驱动 -->  
    <context:annotation-config/> 

    <!-- 默认扫描的包路径 -->  
    <context:component-scan base-package="cn.org.xxx" />  

    <!--指定默认扫描的包路径,同时指定不扫描的包,如默认扫描包下的Service不扫描-->
    <!--
    <context:component-scan base-package="xx.xxx.yyy"/>
    <context:exclude-filter type="annotation" expression="xx.xxx.yyy.Service"/>  
    </context:component-scan>
    -->

    <!-- Spring MVC 必须的配置 -->
    <mvc:annotation-driven />  

    <!-- 配置js,css等静态文件直接映射到对应的文件夹,不被DispatcherServlet处理 -->
    <mvc:resources location="/resources/" mapping="/resources/**" />
 
    <!-- 定义一些视图控制器,完成访问路径到返回视图的映射关系 -->
    <mvc:view-controller path="/" view-name="forward:/logon"/> 
    <mvc:view-controller path="/permission/login" view-name="permission/login"/>
    <mvc:view-controller path="/permission/logout" view-name="permission/login"/>

    <!-- ...其他Bean的配置... -->    
</beans>
其中 <context:annotation-config/> 的作用是隐式地向 Spring 容器注册如下四个Bean,这是注解,注入功能的驱动:
AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,
PersistenceAnnotationBeanPostProcessor,RequiredAnnotationBeanPostProcessor 
具体解释例如:
.如果想使用@Resource 、@PostConstruct、@PreDestroy等注解就必须声明CommonAnnotationBeanPostProcessor。  
.如果想使用@PersistenceContext注解,就必须声明PersistenceAnnotationBeanPostProcessor的Bean。  
.如果你想使用@Autowired注解,那么就必须事先在 Spring 容器中声明 AutowiredAnnotationBeanPostProcessor Bean。
传统声明方式如下:  
< bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor "/>   
.如果想使用 @Required的注解,就必须声明RequiredAnnotationBeanPostProcessor的Bean。同样,传统的声明方式如下:  
< bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/> 

其中<context:component-scan base-package="xx.xxx.xxxx" /> 的作用是扫描指定的包,即寻找指定包内的类class文件
类似于Spring配置文件中Bean的定义,如:<bean id="..." class="..."> ,
也可在该元素其中增加<context:exclude-filter type="annotation" expression="xx.yy"/>指定不扫描的包;

其中<mvc:annotation-driven /> 的作用是自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 这两个bean, 是spring MVC为@Controllers分发请求所必须的。是一种简写形式,完全可以手动配置替代这种简写形式,简写形式可以让初学快速应用默认配置方案,并提供了数据绑定支持,@NumberFormatannotation支持,@DateTimeFormat支持,@Valid支持,读写XML的支持(JAXB),读写JSON的支持(Jackson)。

三,在annotaion配置注解中用@Component来表示一个通用注释,用于说明一个类是一个spring容器管理的类,也即就是该类已经被拉入到spring框架的管理中了。而@Controller, @Service, @Repository等等是@Component的细化,这三个注解比@Component带有更多的语义,它们分别对应了控制层、服务层、持久层的类,下面逐步了解一下部分注解;

1,@Component
把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>,即类的声明;

@Component和<context:annotation-config/>和<context:component-scan base-package="com.xxx"/>
三者配合实现无XML配置,只通过注解配置即可将类放入Spring资源容器中。 

如果用注入方式的话就需要在Spring配置文件application.xml中引入component的扫描组件, 
< context:annotation-config/>和<context:component-scan base-package="com.xxx">   
其中base-package为需要扫描的包(含所有子包)

2,@Resource 
作用是在Spring容器里面找相应的资源,资源必须先通过Spring配置文件application.xml等方式预先加载到Spring框架容器中;
http://www.2cto.com/kf/201206/137806.html

可通过name属性指定查找的资源名称,有时name属性可省,可注解到field或setter方法上面,例如:

  1. public class UserAction {   
  2.     private UserService userService;   
  3.        
  4.     @Resource(name="userService")   //或@Resource("userService")  
  5.     public void setUserService(UserService userService){   
  6.         this.userService = userService;   
  7.     }   
  8.     public void addUser(){   
  9.         userService.HelloWorld();   
  10.     }   
  11. }   
public class UserAction { 
    private UserService userService; 
     
    @Resource(name="userService")   //或@Resource("userService")
    public void setUserService(UserService userService){ 
        this.userService = userService; 
    } 
    public void addUser(){ 
        userService.HelloWorld(); 
    } 
} 
3,@Autowired 和 @Resource
两者都用于注入对象功能,
@Autowired 按 byType 自动注入,@Resource 的作用相当于 @Autowired,但@Resource 默认按 byName 自动注入罢了,
@Resource 有两个属性是比较重要的,分别是 name 和 type,Spring 将 @Resource 注释的 name 属性解析为 Bean 的名字,
而 type 属性则解析为 Bean 的类型。所以如果使用 name 属性,则使用 byName 的自动注入策略,
而使用 type 属性时则使用 byType 自动注入策略。如果既不指定 name 也不指定 type 属性,这时将通过反射机制使用 byName 自动注入策略。

Resource 注释类位于 Spring 发布包的 lib/j2ee/common-annotations.jar 类包中;

4,@Repository
该注解是用来给持久层的类定义一个名字,让Spring根据这个名字关联到这个类。
例如:

  1. @Repository("userDao")   
  2. public class UserDaoImpl  implements UserDao{   
  3.    //...  
  4. }   
@Repository("userDao") 
public class UserDaoImpl  implements UserDao{ 
   //...
} 
声明了UserDaoImpl ,它在Spring容器中叫userDao这个名字; 另外标签:@Autowired 用来注入,例如: 

  1. @Autowired   
  2. private UserDao userDao;  
@Autowired 
private UserDao userDao;
这样就注入到Spring容器中进去了,相当于我们new了这个实现类并且加入到Spring框架的容器中,我们就无需写setter方法了。 

5,@Controller, 
使用 @Controller 注解定义一个 Controller 控制器。使用@Controller 标记的类就是一个SpringMVC Controller 对象;但还不能使用,需要把控制器类加入到Spring框架容器中才能使用;

  1. @Controller    
  2. public class MyController {    
  3.     @RequestMapping ( "/showView" )    
  4.     public ModelAndView showView() {    
  5.        ModelAndView modelAndView = new ModelAndView();    
  6.        modelAndView.setViewName( "viewName" );    
  7.        modelAndView.addObject( "需要放到model中的属性名称" , "对应的属性值,它是一个对象");    
  8.        return modelAndView;    
  9.     }      
  10. }   
@Controller  
public class MyController {  
    @RequestMapping ( "/showView" )  
    public ModelAndView showView() {  
       ModelAndView modelAndView = new ModelAndView();  
       modelAndView.setViewName( "viewName" );  
       modelAndView.addObject( "需要放到model中的属性名称" , "对应的属性值,它是一个对象");  
       return modelAndView;  
    }    
} 
控制器类加入到Spring框架容器中有两种方法:
(1)在SpringMVC 的配置文件[application.xml]中定义MyController的bean对象,如下:
< bean class="com.host.app.web.controller.MyController"/>
(2)在SpringMVC 的配置文件[application.xml]中告诉Spring 该到哪里去找标记为@Controller 的Controller控制器。
<context:component-scan base-package = "com.host.app.web.controller" >  
   <context:exclude-filter type = "annotation" expression = "org.springframework.stereotype.Service" />  
< /context:component-scan >   
注:上面 context:exclude-filter 标注的是不扫描 @Service 标注的类

6,@Service, 
@Service用于标注业务层组件,相当于定义一个bean然后添加到Spring容器中;如:@Service("名称"),如果未指定名称则自动根据Java Bean的类名生成一个首字母小写跟bean类名称同名的名称,

  1. //(指定在Spring容器中的名称),相当于在Spring容器中通过myUserService名称即可找到UserServiceImpl类的实例  
  2. @Service("myUserService")   
  3. public class UserServiceImpl implements userService {    
  4.     //...code...  
  5. }  
  6.   
  7. //如果不指定名称则生成一个跟类名相同,但首字母小写的该类实例,并加到Spring容器中,即在Spring容器中通过logServiceImpl可找到该类实例  
  8. @Service    
  9. public class LogServiceImpl implements LogService {    
  10.     //...code...  
  11. }  
//(指定在Spring容器中的名称),相当于在Spring容器中通过myUserService名称即可找到UserServiceImpl类的实例
@Service("myUserService") 
public class UserServiceImpl implements userService {  
	//...code...
}

//如果不指定名称则生成一个跟类名相同,但首字母小写的该类实例,并加到Spring容器中,即在Spring容器中通过logServiceImpl可找到该类实例
@Service  
public class LogServiceImpl implements LogService {  
	//...code...
}
7,@RequestMapping
使用 @RequestMapping 来完成 Request 请求到处理器或处理器方法的映射。
使用@RequestMapping 可把URL映射到控制器类,或者控制器类的某个处理方法上,当@RequestMapping 标记在Controller 类上时,
表明该控制器类所有方法处理的请求都基于前面的URL,控制器类里面的方法如果再使用@RequestMapping时,请求的路径是相对于
类上面的请求路径,也即基于前面的请求路径;如果控制器类前没有@RequestMapping 标记时,控制器类里面方法的@RequestMapping则
相对host根路径,例如:
  1. @Controller    
  2. @RequestMapping ("/user")  //markA  
  3. public class MyController {    
  4.     @RequestMapping ( "/userInfo" ) //markB  
  5.     public ModelAndView showView() {    
  6.        ModelAndView modelAndView = new ModelAndView();    
  7.        modelAndView.setViewName( "viewName" );    
  8.        modelAndView.addObject( "需要放到model中的属性名称" , "对应的属性值,它是一个对象");    
  9.        return modelAndView;    
  10.     }      
  11. }   
@Controller  
@RequestMapping ("/user")  //markA
public class MyController {  
    @RequestMapping ( "/userInfo" ) //markB
    public ModelAndView showView() {  
       ModelAndView modelAndView = new ModelAndView();  
       modelAndView.setViewName( "viewName" );  
       modelAndView.addObject( "需要放到model中的属性名称" , "对应的属性值,它是一个对象");  
       return modelAndView;  
    }    
} 
上面的访问路径为http://host:port/user/userInfo.do
如果注释掉标注markA行的代码,访问路径为:http://host:port/userInfo.do

@RequestMapping 中还支持通配符“*”或“?”等,例如如果控制器类中markB行改成 @RequestMapping ( "/*Info" ),那访问可以是
http://host:port/user/userInfo.do 或 http://host:port/user/getUserInfo.do
8,@PathVariable
URI 模板就是在URI 中给定一个变量,然后在映射的时候动态的给该变量赋值,即非常方便的实现URL的RestFul 风格,在SpringMVC 中,我们可以使用@PathVariable 来标记Controller里面的处理方法参数,表示该参数的值将使用 URI 模板中对应的变量的值来赋值。

  1. @Controller    
  2. @RequestMapping ( "/test/{variable1}" )    
  3. public class MyController {      
  4.     @RequestMapping ( "/showView/{variable2}" )    
  5.     public ModelAndView showView( @PathVariable String variable1, @PathVariable ("variable2"int variable2) {    
  6.        ModelAndView modelAndView = new ModelAndView();    
  7.        modelAndView.setViewName( "viewName" );    
  8.        modelAndView.addObject( "需要放到model中的属性名称" , "对应的属性值,它是一个对象");    
  9.        return modelAndView;    
  10.     }    
  11. }   
@Controller  
@RequestMapping ( "/test/{variable1}" )  
public class MyController {    
    @RequestMapping ( "/showView/{variable2}" )  
    public ModelAndView showView( @PathVariable String variable1, @PathVariable ("variable2") int variable2) {  
       ModelAndView modelAndView = new ModelAndView();  
       modelAndView.setViewName( "viewName" );  
       modelAndView.addObject( "需要放到model中的属性名称" , "对应的属性值,它是一个对象");  
       return modelAndView;  
    }  
} 
处理时,路径中{variable1}的值替换showView方法参数variable1,路径中{variable2}的值替换variable2,
如:有请求 /test/hello/showView/2.do 到控制器时,hello赋给showView方法的variable1,2赋给showView方法的variable2;

当你没有明确指定从路径中取哪个参数时,就默认去URI 模板中找跟参数名相同的变量,但是这种情况只有在使用debug 模式进行编译的时候才可以,如果不是debug 编译的就会报错;当不是使用debug 模式进行编译,或者是所需要使用的变量名跟参数名不相同的时候,就要明确指出使用的是URI 模板中的哪个变量
9,@RequestParam
使用 @RequestParam 可完成 HttpServletRequest 的请求参数到控制器方法参数的绑定,同时还可增加一些参数的选项。

  1. //例如:  
  2. @RequestMapping ( "/test" )    
  3. public String userInfo( @RequestParam(value="name",required=true) String name, @RequestParam (value="age",required=falseint age) {    
  4.    return "requestParam" ;    
  5. }   
//例如:
@RequestMapping ( "/test" )  
public String userInfo( @RequestParam(value="name",required=true) String name, @RequestParam (value="age",required=false) int age) {  
   return "requestParam" ;  
} 
上面会把HttpServletRequest请求中的参数name绑定到控制器方法userInfo的参数name,同时指定name参数为必选参数,
把请求中的参数age(可选)绑定到控制器方法userInfo的参数age,即@RequestParam完成请求URL中的
  1. @RequestMapping (value= "testMethod", method={RequestMethod.GET, RequestMethod.DELETE })    
  2. public String testMethod() {    
  3.    System. out .println( "test Method..........." );    
  4.    return "method" ;    
  5. }    
@RequestMapping (value= "testMethod", method={RequestMethod.GET, RequestMethod.DELETE })  
public String testMethod() {  
   System. out .println( "test Method..........." );  
   return "method" ;  
}  
参数到控制器中方法的参数的绑定;
当有请求:/test.do?name=xiaoming&age=13 发送到控制的userInfo方法时,请求中的name参数值xiaoming赋给控制器的方法userInfo的name参数,
请求中的age参数值13赋给控制器的方法userInfo的age参数;

值得注意的是和@PathVariable 一样,当你没有明确指定从请求中取哪个参数时,Spring在代码是debug编译的情况下会默认取更方法参数同名的参数,如果不是debug 编译的就会报错。
@RequestMapping 的一些高级应用
在RequestMapping 中除了指定请求路径value 属性外,还有其他的属性可以指定,如params 、method 和headers 。这样属性都可以用于缩小请求的映射范围。

(1)params属性
params 属性用于指定请求参数的

  1. @RequestMapping (value= "testParams", params={"param1=value1""param2""!param3" })    
  2. public String testParams() {    
  3.    System. out .println( "test Params..........." );    
  4.    return "testParams" ;    
  5. }    
@RequestMapping (value= "testParams", params={"param1=value1", "param2", "!param3" })  
public String testParams() {  
   System. out .println( "test Params..........." );  
   return "testParams" ;  
}  
用@RequestMapping 的params 属性指定了三个参数,这些参数都是针对请求参数而言的,它们分别表示参数param1 的值必须等于value1 ,参数param2 必须存在,值无所谓,参数param3 必须不存在,只有当请求是/testParams.do并且满足指定的三个参数条件的时候才能访问到testParams方法;故请求/testParams.do?param1=value1&param2=value2 能够正确访问到该testParams 方法;
请求/testParams.do?param1=value1&param2=value2&param3=value3 不能够正常的访问到该方法,因含param3参数,与规定不符合;
(2)method属性
method 属性主要是用于限制能够访问方法的请求类型。

  1. @RequestMapping (value= "testMethod", method={RequestMethod.GET, RequestMethod.DELETE })    
  2. public String testMethod() {    
  3.    System. out .println( "test Method..........." );    
  4.    return "method" ;    
  5. }    
@RequestMapping (value= "testMethod", method={RequestMethod.GET, RequestMethod.DELETE })  
public String testMethod() {  
   System. out .println( "test Method..........." );  
   return "method" ;  
}  
在上面的代码中限制请求为/testMethod.do,并且使用GET或DELETE的请求方式,才能访问到该控制器的testMethod方法。
(3)headers属性
使用headers 属性可以通过请求头信息来缩小@RequestMapping 的映射范围。

  1. @RequestMapping (value= "testHeaders" , headers={"host=localhost""Accept"})    
  2. public String testHeaders() {    
  3.    System.out.println( "test Headers..........." );    
  4.    return "headers" ;    
  5. }  
@RequestMapping (value= "testHeaders" , headers={"host=localhost", "Accept"})  
public String testHeaders() {  
   System.out.println( "test Headers..........." );  
   return "headers" ;  
}
headers属性的用法和功能与params属性相似。上面代码中当请求为/testHeaders.do并且只有当请求头包含Accept信息,且请求的host为localhost时才能正确访问到testHeaders方法。
10,@CookieValue
使用 @CookieValue 可将cookie的值绑定到Controller方法的参数上。

  1. @RequestMapping ( "test" )    
  2. public String getCookieValue( @CookieValue ("hello") String cookieValue, @CookieValue String hello) {    
  3.    System.out.println(cookieValue + "-----------" + hello);    
  4.    return "cookieValue" ;    
  5. }   
@RequestMapping ( "test" )  
public String getCookieValue( @CookieValue ("hello") String cookieValue, @CookieValue String hello) {  
   System.out.println(cookieValue + "-----------" + hello);  
   return "cookieValue" ;  
} 
使用@CookieValue完成了把名为hello的cookie的值绑定控制器方法getCookieValue的参数cookieValue上;后一个没有指定名称时,默认自动查找同名的cookie的值绑定到getCookieValue的参数hello上;

注意,才没有指定名称时,在debug 编译模式下将自动获取跟方法参数名同名的cookie 值,非debug编译环境出错;
11,@RequestHeader
使用 @RequestHeader 注解可绑定 HttpServletRequest 请求的某个头信息到 Controller 方法的参数;

  1. @RequestMapping ( "/test" )    
  2. public String getRequestHeader( @RequestHeader("Host") String hostAddr, @RequestHeader String Host, @RequestHeader String host ) {    
  3.     System. out .println(hostAddr + "-----" + Host + "-----" + host );    
  4.     return "requestHeader" ;    
  5. }   
@RequestMapping ( "/test" )  
public String getRequestHeader( @RequestHeader("Host") String hostAddr, @RequestHeader String Host, @RequestHeader String host ) {  
    System. out .println(hostAddr + "-----" + Host + "-----" + host );  
    return "requestHeader" ;  
} 
上面把请求的头信息中,把头信息Host的值绑定到控制器中的方法getRequestHeader的Host参数;@RequestHeader指定了头信息名称,则取指定名称的
头信息值[推荐];(在debug编译模式下)如果没有指定头信息名称则绑定请求头信息中跟控制器参数同名的头信息(非debug可能出错);
注意:在使用 @RequestHeader 的时候是大小写不敏感的;但在@PathVariable、@RequestParam和@CookieValue中都是大小写敏感的。
12,@ModelAttribute 
SpringMVC 支持使用 @ModelAttribute 和 @SessionAttributes 在不同的模型和控制器之间共享数据。
首先看@ModelAttribute,主要有两种使用方式,一种是标注在方法名称上,一种是标注在 Controller 方法的参数上。
当@ModelAttribute标记在控制器的某个方法上时,则该方法将在控制器所有其它方法执行之前被执行,然后把返回的对象存放在模型属性中;
属性名称可以使用@ModelAttribute("attributeName")在标记方法的时候指定,若未指定,则使用返回类型的类名称(首字母小写)作为属性名称。

  1. @Controller    
  2. @RequestMapping ("/myTest")    
  3. public class MyController {    
  4.     
  5.     @ModelAttribute ("hello")    
  6.     public String getModel() {    
  7.        System. out .println( "----------Hello---------" );    
  8.        return "world" ;    
  9.     }    
  10.     
  11.     @ModelAttribute ("intValue")    
  12.     public int getInteger() {    
  13.        System. out .println( "---------intValue---------" );    
  14.        return 10;    
  15.     }    
  16.     
  17.     @RequestMapping ("sayHello")    
  18.     public void sayHello( @ModelAttribute ( "hello" ) String hello,   
  19.             @ModelAttribute ( "intValue" ) int num, @ModelAttribute ( "user2" ) User user,   
  20.         Writer writer, HttpSession session) throws IOException {    
  21.        writer.write( "Hello " + hello + " , Hello " + user.getUsername() + num);    
  22.        writer.write("\r");    
  23.        Enumeration enume = session.getAttributeNames();    
  24.        while (enume.hasMoreElements())    
  25.            writer.write(enume.nextElement() + "\r" );    
  26.     }    
  27.     
  28.     @ModelAttribute ( "user2" )    
  29.     public User getUser() {  //User类需要另外定义  
  30.        System. out .println( "---------getUser---------" );    
  31.        return new User(3"user2" );    
  32.     }   
  33. }  
@Controller  
@RequestMapping ("/myTest")  
public class MyController {  
  
    @ModelAttribute ("hello")  
    public String getModel() {  
       System. out .println( "----------Hello---------" );  
       return "world" ;  
    }  
  
    @ModelAttribute ("intValue")  
    public int getInteger() {  
       System. out .println( "---------intValue---------" );  
       return 10;  
    }  
  
    @RequestMapping ("sayHello")  
    public void sayHello( @ModelAttribute ( "hello" ) String hello, 
    		@ModelAttribute ( "intValue" ) int num, @ModelAttribute ( "user2" ) User user, 
		Writer writer, HttpSession session) throws IOException {  
       writer.write( "Hello " + hello + " , Hello " + user.getUsername() + num);  
       writer.write("\r");  
       Enumeration enume = session.getAttributeNames();  
       while (enume.hasMoreElements())  
           writer.write(enume.nextElement() + "\r" );  
    }  
  
    @ModelAttribute ( "user2" )  
    public User getUser() {  //User类需要另外定义
       System. out .println( "---------getUser---------" );  
       return new User(3, "user2" );  
    } 
}
当请求/myTest/sayHello.do时,使用@ModelAttribute标记的方法[getModel,getInteger,getUser]会先执行,然后把它们返回的对象存放到模型中,最终访问到 sayHello 方法的时候,使用 @ModelAttribute 标记的方法参数都能被正确的注入值。
方法执行结果:Hello world,Hello user210

当 @ModelAttribute 标记在控制器的方法的参数上时,表示该参数的值将从模型或者Session中取对应名称的属性值,该名称可以通过 @ModelAttribute("attributeName") 来指定,若未指定,则使用参数类型的类名称(首字母小写)作为属性名称。
13,@SessionAttributes
SpringMVC 支持使用@ModelAttribute和@SessionAttributes在不同的模型和控制器之间共享数据。
下面讲用于标记需要在Session中使用到的数据,包括从Session 中取数据和存数据。
@SessionAttributes一般是标记在Controller类前面,可以通过指定名称、类型或者名称加类型的形式来指定哪些属性是需要存放在session中。
名称、类型分别对应@SessionAttributes注解的value和types属性;
当使用名称时放在大括号里多个属性名称间用逗号分隔,如:@SessionAttributes(value={"user1","blog1"}),当仅有一个名称时可省value,简写为@SessionAttributes("user1"),
当使用的是types属性时,那么使用的Session属性名称将会是对应类型的名称(首字母小写),如:@SessionAttributes(types={User.class, Blog.class}),那么
Session中使用的名称是user和blog,也即Session.getAttribute("user"), Session.getAttribute("blog");
当同时使用名称和类型时,如:@SessionAttributes(value={"user1", "blog1"}, types={User.class,Blog.class}) ,这时候取的是它们的并集,即Session中有
属性名为user1,blog1,user,blog四个属性对应的值,其中user,blog属性对应的值分别为User.class,Blog.class类型对象;

  1. //例如  
  2. @Controller    
  3. @RequestMapping ("/myTest")    
  4. @SessionAttributes(value={"user1""blog1"}, types={User.class,Blog.class})    
  5. public class MyController {        
  6.     @RequestMapping ("setSessionAttribute")    
  7.     public void setSessionAttribute(Map<String, Object> map, Writer writer) throws IOException {    
  8.        User user = new User(1"user" );  //User类需事先定义好  
  9.        User user1 = new User(2"user1" );    
  10.        Blog blog = new Blog(1"blog" );    
  11.        Blog blog1 = new Blog(2"blog1" );    
  12.        map.put("user" , user);    
  13.        map.put("user1" , user1);    
  14.        map.put("blog" , blog);    
  15.        map.put("blog1" , blog1);    
  16.        writer.write( "---------set value over---------" );    
  17.     }    
  18.          
  19.     @RequestMapping ("useSessionAttribute")    
  20.     public void useSessionAttribute(Writer writer, @ModelAttribute("user1") User user1, @ModelAttribute("blog1") Blog blog1)   
  21.     throws IOException {    
  22.        writer.write(user1.getId() + "--------" + user1.getUsername());    
  23.        writer.write( "\r" );    
  24.        writer.write(blog1.getId() + "--------" + blog1.getTitle());    
  25.     }    
  26.     
  27.     @RequestMapping ("useSessionAttribute2")    
  28.     public void useSessionAttribute(Writer writer, @ModelAttribute("user1") User user1, @ModelAttribute("blog1") Blog blog1,   
  29.     @ModelAttribute User user, HttpSession session) throws IOException {    
  30.        writer.write(user1.getId() + "--------" + user1.getUsername());    
  31.        writer.write( "\r" );    
  32.        writer.write(blog1.getId() + "--------" + blog1.getTitle());    
  33.        writer.write( "\r" );    
  34.        writer.write(user.getId() + "---------" + user.getUsername());    
  35.        writer.write( "\r" );    
  36.        Enumeration enume = session.getAttributeNames();    
  37.        while (enume.hasMoreElements())    
  38.            writer.write(enume.nextElement() + " \r" );    
  39.     }    
  40.     
  41.     @RequestMapping ("useSessionAttribute3")    
  42.     public void useSessionAttribute(@ModelAttribute("user2") User user){    
  43.     
  44.     }    
  45. }   
//例如
@Controller  
@RequestMapping ("/myTest")  
@SessionAttributes(value={"user1", "blog1"}, types={User.class,Blog.class})  
public class MyController {      
    @RequestMapping ("setSessionAttribute")  
    public void setSessionAttribute(Map<String, Object> map, Writer writer) throws IOException {  
       User user = new User(1, "user" );  //User类需事先定义好
       User user1 = new User(2, "user1" );  
       Blog blog = new Blog(1, "blog" );  
       Blog blog1 = new Blog(2, "blog1" );  
       map.put("user" , user);  
       map.put("user1" , user1);  
       map.put("blog" , blog);  
       map.put("blog1" , blog1);  
       writer.write( "---------set value over---------" );  
    }  
       
    @RequestMapping ("useSessionAttribute")  
    public void useSessionAttribute(Writer writer, @ModelAttribute("user1") User user1, @ModelAttribute("blog1") Blog blog1) 
	throws IOException {  
       writer.write(user1.getId() + "--------" + user1.getUsername());  
       writer.write( "\r" );  
       writer.write(blog1.getId() + "--------" + blog1.getTitle());  
    }  
  
    @RequestMapping ("useSessionAttribute2")  
    public void useSessionAttribute(Writer writer, @ModelAttribute("user1") User user1, @ModelAttribute("blog1") Blog blog1, 
	@ModelAttribute User user, HttpSession session) throws IOException {  
       writer.write(user1.getId() + "--------" + user1.getUsername());  
       writer.write( "\r" );  
       writer.write(blog1.getId() + "--------" + blog1.getTitle());  
       writer.write( "\r" );  
       writer.write(user.getId() + "---------" + user.getUsername());  
       writer.write( "\r" );  
       Enumeration enume = session.getAttributeNames();  
       while (enume.hasMoreElements())  
           writer.write(enume.nextElement() + " \r" );  
    }  
  
    @RequestMapping ("useSessionAttribute3")  
    public void useSessionAttribute(@ModelAttribute("user2") User user){  
  
    }  
} 
首先访问/myTest/setSessionAttribute.do 调用MyController的setSessionAttribute 方法,完成往模型里面添加了user 、user1 、blog 和blog1 四个属性,因控制器前面有@SessionAttributes定义的需要存到session中的属性名称相同或类型相同,所以这四个属性同时被添加到Session中;
再访问/myTest/useSessionAttribute.do时,方法参数中用@ModelAttribute指定了参数user1和参数blog1是绑定到session或模型中的同名属性,
因前一步请求已经完成给session对应属性名赋值(注意:如第一步访问没有,即没有完成设置值,那后面的访问将找不到值,但不报错),故执行结果为:
2------user1
2------blog1
再访问/myTest/useSessionAttribute2.do,方法参数user、user1和blog1用@ModelAttribute声明了需要session或模型的属性值注入,因前面请求已经完成给session和模型对应属性名赋值,
故指定结果有值,如下:
2------user1
2------blog1
1------user
blog
user
user1
blog1

再访问/myTest/useSessionAttribute3.do,因方法中user用@ModelAttribute("user2")进行标记,说明(绑定到)使用模型或session中属性名为user2的属性值,
因其之前都没有定义,即不存在,故报错;

14,@RequestBody 
参见http://snowolf.iteye.com/blog/1628861

将HTTP请求正文转换为适合的HttpMessageConverter对象。

HttpMessageConverter接口,需要在Spring配置文件中开启<mvc:annotation-driven />。

15,@ResponseBody
参见http://snowolf.iteye.com/blog/1628861
将内容或对象作为 HTTP 响应正文返回,并调用适合HttpMessageConverter的Adapter转换对象,写入输出流。

HttpMessageConverter接口,需要在Spring配置文件中开启<mvc:annotation-driven />。

16,@Valid
@Valid标注我们需要校验的参数;
参见 http://haohaoxuexi.iteye.com/blog/1812584

那么当我们需要使用SpringMVC提供的Validator接口来对该实体类进行校验的时候该如何做呢?这个时候我们应该提供一个Validator的实现类,并实现Validator接口的supports方法和validate方法。Supports方法用于判断当前的Validator实现类是否支持校验当前需要校验的实体类,只有当supports方法的返回结果为true的时候,该Validator接口实现类的validate方法才会被调用来对当前需要校验的实体类进行校验。这里假设我们需要验证User类的username和password都不能为空,先给出其代码:

  1. //验证类  
  2. import org.springframework.validation.Errors;    
  3. import org.springframework.validation.ValidationUtils;    
  4. import org.springframework.validation.Validator;    
  5.      
  6. public class UserValidator implements Validator {    
  7.      
  8.     public boolean supports(Class<?> clazz) {    
  9.        // TODO Auto-generated method stub    
  10.        return User.class.equals(clazz);    
  11.     }    
  12.      
  13.     public void validate(Object obj, Errors errors) {    
  14.        // TODO Auto-generated method stub    
  15.        ValidationUtils.rejectIfEmpty(errors, "username"null"Username is empty.");    
  16.        User user = (User) obj;    
  17.        if (null == user.getPassword() || "".equals(user.getPassword())){   
  18.            errors.rejectValue("password"null"Password is empty.");   
  19.     }   
  20.     }       
  21. }   
  22. //ValidationUtils类是Spring中提供的一个工具类。Errors就是Spring用来存放错误信息的对象。  
//验证类
import org.springframework.validation.Errors;  
import org.springframework.validation.ValidationUtils;  
import org.springframework.validation.Validator;  
   
public class UserValidator implements Validator {  
   
    public boolean supports(Class<?> clazz) {  
       // TODO Auto-generated method stub  
       return User.class.equals(clazz);  
    }  
   
    public void validate(Object obj, Errors errors) {  
       // TODO Auto-generated method stub  
       ValidationUtils.rejectIfEmpty(errors, "username", null, "Username is empty.");  
       User user = (User) obj;  
       if (null == user.getPassword() || "".equals(user.getPassword())){ 
           errors.rejectValue("password", null, "Password is empty."); 
	} 
    }     
} 
//ValidationUtils类是Spring中提供的一个工具类。Errors就是Spring用来存放错误信息的对象。
我们已经定义了一个对User类进行校验的UserValidator了,但是这个时候UserValidator还不能对User对象进行校验,因为我们还没有告诉Spring应该使用UserValidator来校验User对象。在SpringMVC中我们可以使用DataBinder来设定当前Controller需要使用的Validator。先来看下面一段代码:
  1. import javax.validation.Valid;    
  2. import org.springframework.stereotype.Controller;    
  3. import org.springframework.validation.BindingResult;    
  4. import org.springframework.validation.DataBinder;    
  5. import org.springframework.web.bind.annotation.InitBinder;    
  6. import org.springframework.web.bind.annotation.RequestMapping;    
  7.      
  8. @Controller    
  9. public class UserController {    
  10.        
  11.     @InitBinder    
  12.     public void initBinder(DataBinder binder) {    
  13.        binder.setValidator(new UserValidator());    
  14.     }    
  15.      
  16.     @RequestMapping("login")    
  17.     public String login(@Valid User user, BindingResult result) {    
  18.        if (result.hasErrors()) { return "redirect:user/login"; }  
  19.        return "redirect:/";    
  20.     }    
  21. }    
import javax.validation.Valid;  
import org.springframework.stereotype.Controller;  
import org.springframework.validation.BindingResult;  
import org.springframework.validation.DataBinder;  
import org.springframework.web.bind.annotation.InitBinder;  
import org.springframework.web.bind.annotation.RequestMapping;  
   
@Controller  
public class UserController {  
     
    @InitBinder  
    public void initBinder(DataBinder binder) {  
       binder.setValidator(new UserValidator());  
    }  
   
    @RequestMapping("login")  
    public String login(@Valid User user, BindingResult result) {  
       if (result.hasErrors()) { return "redirect:user/login"; }
       return "redirect:/";  
    }  
}  
@Valid标注我们需要校验的参数,否则Spring不会对它进行校验。另外我们的处理器方法必须给定包含Errors的参数,这可以是Errors本身,也可以是它的子类BindingResult,使用了Errors参数就是告诉Spring关于表单对象数据校验的错误将由我们自己来处理,否则Spring会直接抛出异常,而且这个参数是必须紧挨着@Valid参数的,即必须紧挨着需要校验的参数,这就意味着我们有多少个@Valid参数就需要有多少个对应的Errors参数,它们是一一对应的。
17,@Scope
@Scope 简单点说就是用来指定bean的作用域(官方解释:scope用来声明IOC容器中的对象应该处的限定场景或者说该对象的存活空间,即在IOC容器在对象进入相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象),其默认作用域是"singleton",如果要换成其他作用区域,直接后面添加类型即可,比如@Scope("prototype") ,注意spring2.0后 又增加了request ,session和global session 4个作用区域;

如果需要的bean实例是个单例,则定义@Scope("singleton"),如果是每次都new一个新的,则用@Scope("prototype");

四,以 @RequestMapping 标记的控制器(也称:处理器)的方法支持的方法参数和返回类型
原文见: http://haohaoxuexi.iteye.com/blog/1753271

1,支持的方法参数类型
(1)HttpServlet 对象,主要包括HttpServletRequest 、HttpServletResponse 和HttpSession 对象。 这些参数Spring 在调用处理器方法的时候会自动给它们赋值,所以当在处理器方法中需要使用到这些对象的时候,可以直接在方法上给定一个方法参数的申明,然后在方法体里面直接用就可以了。但是有一点需要注意的是在使用HttpSession 对象的时候,如果此时HttpSession 对象还没有建立起来的话就会有问题。
(2)Spring 自己的WebRequest 对象。 使用该对象可以访问到存放在HttpServletRequest 和HttpSession 中的属性值。
(3)InputStream 、OutputStream 、Reader 和Writer 。 InputStream 和Reader 是针对HttpServletRequest 而言的,可以从里面取数据;OutputStream 和Writer 是针对HttpServletResponse 而言的,可以往里面写数据。
(4)使用@PathVariable 、@RequestParam 、@CookieValue 和@RequestHeader 标记的参数。
(5)使用@ModelAttribute 标记的参数。
(6)java.util.Map 、Spring 封装的Model 和ModelMap 。 这些都可以用来封装模型数据,用来给视图做展示。
(7)实体类。 可以用来接收上传的参数。
(8)Spring 封装的MultipartFile 。 用来接收上传文件的。
(9)Spring 封装的Errors 和BindingResult 对象。 这两个对象参数必须紧接在需要验证的实体对象参数之后,它里面包含了实体对象的验证结果。

2,支持的返回类型
(1)一个包含模型和视图的ModelAndView 对象。
(2)一个模型对象,这主要包括Spring 封装好的Model 和ModelMap ,以及java.util.Map ,当没有视图返回的时候视图名称将由RequestToViewNameTranslator 来决定。
(3)一个View 对象。这个时候如果在渲染视图的过程中模型的话就可以给处理器方法定义一个模型参数,然后在方法体里面往模型中添加值。
(4)一个String 字符串。这往往代表的是一个视图名称。这个时候如果需要在渲染视图的过程中需要模型的话就可以给处理器方法一个模型参数,然后在方法体里面往模型中添加值就可以了。
(5)返回值是void 。这种情况一般是我们直接把返回结果写到HttpServletResponse 中了,如果没有写的话,那么Spring 将会利用RequestToViewNameTranslator 来返回一个对应的视图名称。如果视图中需要模型的话,处理方法与返回字符串的情况相同。
(6)如果处理器方法被注解@ResponseBody 标记的话,那么处理器方法的任何返回类型都会通过HttpMessageConverters 转换之后写到HttpServletResponse 中,而不会像上面的那些情况一样当做视图或者模型来处理。
(7)除以上几种情况之外的其他任何返回类型都会被当做模型中的一个属性来处理,而返回的视图还是由RequestToViewNameTranslator 来决定,添加到模型中的属性名称可以在该方法上用@ModelAttribute("attributeName") 来定义,否则将使用返回类型的类名称的首字母小写形式来表示。使用@ModelAttribute 标记的方法会在@RequestMapping 标记的方法执行之前执行。

五,定制自己的类型转换器 
原文见:http://haohaoxuexi.iteye.com/blog/1753271

在通过处理器方法参数接收 request 请求参数绑定数据的时候,对于一些简单的数据类型 Spring 会帮我们自动进行类型转换,而对于一些复杂的类型由于 Spring 没法识别,所以也就不能帮助我们进行自动转换了,这个时候如果我们需要 Spring 来帮我们自动转换的话就需要我们给 Spring 注册一个对特定类型的识别转换器。 
Spring 允许我们提供两种类型的识别转换器,一种是注册在 Controller 中的,一种是注册在 SpringMVC 的配置文件中。聪明的读者看到这里应该可以想到它们的区别了,定义在 Controller 中的是局部的,只在当前 Controller 中有效,而放在 SpringMVC 配置文件中的是全局的,所有 Controller 都可以拿来使用。

1,在控制器类中定义局部的类型转换器,并用 @InitBinder 标记"告知"当前控制器
我们可以使用 @InitBinder 注解标注在 Controller 方法上,然后在方法体里面注册数据绑定的转换器,这主要是通过 WebDataBinder 进行的。我们可以给需要注册数据绑定的转换器的方法一个 WebDataBinder 参数,然后给该方法加上 @InitBinder 注解,这样当该 Controller 中在处理请求方法时如果发现有不能解析的对象的时候,就会看该类中是否有使用 @InitBinder 标记的方法,如果有就会执行该方法,然后看里面定义的类型转换器是否与当前需要的类型匹配。

  1. @Controller    
  2. @RequestMapping ("/myTest")    
  3. public class MyController {    
  4.     
  5.     @InitBinder    
  6.     public void dataBinder(WebDataBinder binder) {    
  7.        DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");    
  8.        PropertyEditor propertyEditor = new CustomDateEditor(dateFormat, true );   
  9.     // 第二个参数true,表示是否允许为空    
  10.        binder.registerCustomEditor(Date. class , propertyEditor);    
  11.     }    
  12.     
  13.     @RequestMapping ( "dataBinder/{date}" )    
  14.     public void testDate( @PathVariable Date date, Writer writer) throws IOException {    
  15.        writer.write(String.valueOf (date.getTime()));    
  16.     }      
  17. }   
@Controller  
@RequestMapping ("/myTest")  
public class MyController {  
  
    @InitBinder  
    public void dataBinder(WebDataBinder binder) {  
       DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");  
       PropertyEditor propertyEditor = new CustomDateEditor(dateFormat, true ); 
	// 第二个参数true,表示是否允许为空  
       binder.registerCustomEditor(Date. class , propertyEditor);  
    }  
  
    @RequestMapping ( "dataBinder/{date}" )  
    public void testDate( @PathVariable Date date, Writer writer) throws IOException {  
       writer.write(String.valueOf (date.getTime()));  
    }    
} 
在上面的代码中当我们请求 /myTest/dataBinder/20121212.do 的时候,Spring 就会利用 @InitBinder 标记的方法里面定义的类型转换器把字符串 20121212 转换为一个 Date 对象。这样定义的类型转换器是局部的类型转换器,一旦出了这个 Controller 就不会再起作用。类型转换器是通过WebDataBinder 对象的 registerCustomEditor 方法来注册的,要实现自己的类型转换器就要实现自己的 PropertyEditor 对象。 Spring 已经给我们提供了一些常用的属性编辑器,如 CustomDateEditor、 CustomBooleanEditor 等

PropertyEditor 是一个接口,要实现自己的 PropertyEditor 类我们可以实现这个接口,然后实现里面的方法。但是 PropertyEditor 里面定义的方法太多了,这样做比较麻烦。在 java 中有一个封装类是实现了 PropertyEditor 接口的,它是 PropertyEditorSupport 类。所以如果需要实现自己的PropertyEditor 的时候只需要继承 PropertyEditorSupport 类,然后重写其中的一些方法。一般就是重写 setAsText 和 getAsText 方法就可以了, setAsText 方法是用于把字符串类型的值转换为对应的对象的,而 getAsText 方法是用于把对象当做字符串来返回的。在 setAsText 中我们一般先把字符串类型的对象转为特定的对象,然后利用 PropertyEditor 的 setValue 方法设定转换后的值。在 getAsText 方法中一般先使用 getValue 方法取代当前的对象,然后把它转换为字符串后再返回给 getAsText 方法。下面是一个示例:

  1. @InitBinder    
  2. public void dataBinder(WebDataBinder binder) {    
  3.    // 定义一个 User 属性编辑器    
  4.    PropertyEditor userEditor = new PropertyEditorSupport() {    
  5.     
  6.        @Override    
  7.        public String getAsText() {    
  8.           // TODO Auto-generated method stub    
  9.           User user = (User) getValue();    
  10.           return user.getUsername();    
  11.        }    
  12.     
  13.        @Override    
  14.        public void setAsText(String userStr) throws IllegalArgumentException {    
  15.           // TODO Auto-generated method stub    
  16.           User user = new User(1, userStr);    
  17.           setValue(user);    
  18.        }    
  19.    };    
  20.    // 使用 WebDataBinder 注册 User 类型的属性编辑器    
  21.    binder.registerCustomEditor(User. class , userEditor);    
  22. }  
@InitBinder  
public void dataBinder(WebDataBinder binder) {  
   // 定义一个 User 属性编辑器  
   PropertyEditor userEditor = new PropertyEditorSupport() {  
  
       @Override  
       public String getAsText() {  
          // TODO Auto-generated method stub  
          User user = (User) getValue();  
          return user.getUsername();  
       }  
  
       @Override  
       public void setAsText(String userStr) throws IllegalArgumentException {  
          // TODO Auto-generated method stub  
          User user = new User(1, userStr);  
          setValue(user);  
       }  
   };  
   // 使用 WebDataBinder 注册 User 类型的属性编辑器  
   binder.registerCustomEditor(User. class , userEditor);  
}
2,实现 WebBindingInitializer 接口定义全局的类型转换器
如果需要定义全局的类型转换器就需要实现自己的 WebBindingInitializer 对象,然后把该对象注入到 AnnotationMethodHandlerAdapter 中,这样 Spring在遇到自己不能解析的对象的时候就会到全局的 WebBindingInitializer 的 initBinder 方法中去找,每次遇到不认识的对象时, initBinder 方法都会被执行一遍。
  1. public class MyWebBindingInitializer implements WebBindingInitializer {    
  2.     
  3.     @Override    
  4.     public void initBinder(WebDataBinder binder, WebRequest request) {    
  5.        // TODO Auto-generated method stub    
  6.        DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");    
  7.        PropertyEditor propertyEditor = new CustomDateEditor(dateFormat, true );    
  8.        binder.registerCustomEditor(Date. class , propertyEditor);    
  9.     }      
  10. }    
public class MyWebBindingInitializer implements WebBindingInitializer {  
  
    @Override  
    public void initBinder(WebDataBinder binder, WebRequest request) {  
       // TODO Auto-generated method stub  
       DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");  
       PropertyEditor propertyEditor = new CustomDateEditor(dateFormat, true );  
       binder.registerCustomEditor(Date. class , propertyEditor);  
    }    
}  
定义了这么一个 WebBindingInitializer 对象之后 Spring 还是不能识别其中指定的对象,这是因为我们只是定义了 WebBindingInitializer 对象,还没有把它交给 Spring , Spring 不知道该去哪里找解析器。要让 Spring 能够识别还需要我们在 SpringMVC 的配置文件中定义一个AnnotationMethodHandlerAdapter 类型的 bean 对象,然后利用自己定义的 WebBindingInitializer 覆盖它的默认属性 webBindingInitializer 。
  1. <bean class = "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">    
  2.    <property name = "webBindingInitializer">    
  3.        <bean class = "com.host.app.web.util.MyWebBindingInitializer"/>    
  4.    </property>    
  5. </bean>   
<bean class = "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">  
   <property name = "webBindingInitializer">  
       <bean class = "com.host.app.web.util.MyWebBindingInitializer"/>  
   </property>  
</bean> 
3,触发数据绑定方法的时间
当Controller处理器方法参数使用@RequestParam、@PathVariable、@RequestHeader、@CookieValue和@ModelAttribute标记的时候都会触发initBinder方法的执行,这包括使用WebBindingInitializer定义的全局方法和在Controller中使用@InitBinder标记的局部方法。而且每个使用了这几个注解标记的参数都会触发一次initBinder方法的执行,这也意味着有几个参数使用了上述注解就会触发几次initBinder方法的执行。

本文很多原文见如下博主链接,部分有修改或整理其它地方得到
http://haohaoxuexi.iteye.com/blog/1753271

更多见博客:
http://haohaoxuexi.iteye.com/blog/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值