java多线程学习(三) 之 ThreadLocal

ThreadLocal 中文可以叫 线程变量 官方解释 This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). 这个类提供一个线程局部变量,意思就是变量只在该线程内可见,对于每个线程都可以用get和set方法获取属于这个线程自己的变量。ThreadLocal变量通常定义在那些想和线程绑定的类里面,例如访问者的ID,事务ID等等。 官方举例: import java.util.concurrent.atomic.AtomicInteger; public class ThreadId { private static final AtomicInteger nextId = new AtomicInteger(0); private static final ThreadLocal threadId = new ThreadLocal() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, // assigning it if necessary public static int get() { return threadId.get(); } } 其中threadId就是一个线程变量。具体的使用可以是这样的: ...

2014年5月27日 · 1 分钟

java多线程学习(二) 之 synchronized

在实现线程同步方法里,synchronized是java中最简单的方法。 官方解释: The use of synchronized methods or statements provides access to the implicit monitor lock associated with every object, but forces all lock acquisition and release to occur in a block-structured way synchronized 的使用有同步方法,和同步代码块,它提供了对一个对象隐式监视器锁的访问,也就是说synchronized之所以能实现线程间同步,是通过获取对象的锁实现的,只是这个锁是隐式的,看不见的,默认java每个对象都有一个锁存在。还有之所以说synchronized是java中实现线程同步最简单的方法,是因为synchronized对锁的获取和释放是有限制的,必须是在一个方法或者一段代码块前后。 synchronized 有两种使用方法: 一种是修饰方法,修饰方法的时候,如果是修饰的普通方法,那就是获取这个类对应的实例的锁,如果是修饰的静态方法,那就是获取这个类的Class对象的锁。例如: public MyObject{ public synchronized void print() { //TODO } public static synchronized void printStatic() { //TODO } } MyObject my = new MyObject(); 也就是说,一个线程在执行my.print()之前必须先获取my这个对象对应的隐式锁,在方法执行完之后自动释放。同样的一个线程在执行MyObject.printStatic()之前必须线获取MyObject.class这个对象的锁。如果对象的锁被别的线程占用,在调用方法的时候,线程就会等待在那里,直到别的线程释放锁为止。 另外一种方法是修饰代码块,表示在执行一块代码之前明确要求要获取哪个对象的锁,这种方法释放锁的标志是代码块执行结束,例如 public MyObject{ private MyLock mylock = new MyLock(); public void print1() { synchronized(this) { //todo; } } public void print2() { synchronized(MyObject.class) { //todo; } } public void print3() { synchronized(MyObject2.class) { //todo; } } public void print4() { synchronized(mylock) { //todo; } } } 代码已经很明确了,你可以以任何对象作为锁对象。需要说明的时,这里线程之间的同步,只是针对synchronized修饰过的代码,而且必须是锁对象是相同的,才会发生线程互斥,线程等待。对于没有synchronized或者锁对象不同的线程是不互斥的,是可以同步执行的。 例如第一种里的print方法和第二种里print1里的代码块都是锁的相应的对象,一个线程如果执行对象my.print()的时候,另外一个线程在调my.print1()的时候,就会堵塞在synchronized(this)的地方,直到my.print()执行结束,释放锁。 ...

2014年5月25日 · 1 分钟

java多线程学习(一) 之 CountDownLatch和CyclicBarrier

CountDownlatch和CyclicBarrier是java并发包java.util.concurrent里的两个线程辅助类,学习这两个辅助类,可以认识到两个多线程的使用场景。 java.util.concurrent.CountDownLatch 官方的解释是很明确的: A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes. 一个同步辅助,是为了允许一个或者多个线程在其它前提操作完成之后,继续往下执行。 A CountDownLatch is initialized with a given count. The await methods block until the current count reaches zero due to invocations of the countDown method, after which all waiting threads are released and any subsequent invocations of await return immediately 一个CountDownLatch实例首先要给一个计数,然后线程可以调用await方法进行阻塞等待,当其它线程通过countDown方法将计数减到零的时候,这些等待的线程就会很快解除阻塞 ,继续往下执行。 实例: public static void main(String[] args) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(5); for (int i = 0; i < 2; i++) { final int threadId = i; new Thread() { public void run() { System.err.println(threadId + "线程等待..."); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.err.println(threadId + "线程结束..."); }; }.start(); } Thread.sleep(1000); for (int i = 0; i < 5; i++) { final int threadId = i; new Thread() { public void run() { System.err.println("前提线程" + threadId + "完成"); latch.countDown(); }; }.start(); } } 运行结果: ...

2014年5月24日 · 2 分钟

JAVA访问memcache和redis

一、JAVA访问memcache之Xmemcached Xmemcached是采用nio连接,并发效率会高,大都采用该客户端。 1、引入Xmemcached <dependency> <groupId>com.googlecode.xmemcached</groupId> <artifactId>xmemcached</artifactId> <version>2.0.0</version> </dependency> 2、配置客户端(结合Spring,factorybean是xmemecached内部提供的) <bean name="memcachedClient" > <!-- nio connection pool size --> <property name="connectionPoolSize" value="2" /> <!-- Use binary protocol,default is TextCommandFactory --> <property name="commandFactory"> <bean /> </property> <!-- Distributed strategy --> <property name="sessionLocator"> <bean /> </property> <!-- Serializing transcoder --> <property name="transcoder"> <bean /> </property> <property name="servers" value="192.168.22.165:10001 192.168.22.165:10002" /> <!-- server's weights --> <property name="weights"> <list> <value>1</value> <value>1</value> </list> </property> </bean> 可以配置 nio连接数:connectionPoolSize 传输是文本还是二进制形式:commandFactory 不同的hash散列算法:sessionLocator 序列化方式:transcoder 多个服务器负载均衡:servers 服务器的比重:weights bean工厂返回的是一个MemcachedClient client 3、使用客户端 ...

2014年5月14日 · 1 分钟

JAVA的那些池子

1、数据库连接池 JDBC在JDK中有一整套API,也就是接口。对于不同的数据库都有自己的实现。但是这些实现都是单个的连接,如果一个应用只有一个连接,那不能支持并发,如果一个操作就有一个连接,那连接的开销又非常大。 最好的办法就是一个线程一个连接,或者是总共建立30个连接,你用完了它用。这就是JDBC连接池的做法。 连接池要取出连接,再放回去,当连接不够的时候怎么办,连接闲置的时候怎么办,不同的连接池算法不一样,实现方式不一样,那连接池的开销也就是效率不一样。这个需要你自己去选择,不过连接池的实现原理大致都是相同的,就是实现JDK中的javax.sql.DataSource接口,通过javax.sql.DataSource.getConnection(),获取一个连接java.sql.Connection,也就是从池子里取出一个连接,这个Connection也由连接池去实现,在Connection.close()的时候,并没有关闭连接,而是把连接放回到池子中,从而达到池子的效果。 所以连接池的配置关键是DataSource的配置,等DataSource配好了,对连接池的使用,就像基础的JDBC连接一样使用。一般DataSource的配置都大同小异,都是那几个参数,参数的含义,网上都可以搜的到,下面是阿里巴巴的Druid数据库连接池的配置: <bean id="dataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc1.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxActive" value="${jdbc.maxActive}" /> <property name="initialSize" value="${jdbc.initialSize}" /> <property name="maxWait" value="${jdbc.maxWait}" /> <property name="minIdle" value="${jdbc.minIdle}" /> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" /> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${jdbc.validationQuery}" /> <property name="testWhileIdle" value="${jdbc.testWhileIdle}" /> <property name="testOnBorrow" value="${jdbc.testOnBorrow}" /> <property name="testOnReturn" value="${jdbc.testOnReturn}" /> <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/> </bean> 2、HTTP连接池 HTTP连接,有一个keepalive的参数,只要你带了这个参数并且服务器支持这个参数,那么你就可以在timeout的时间内保持和服务器的连接,不断的发送请求,得到响应。这样你喝服务器的交互,就不用每次请求都去进行TCP的连接的三次握手等等,减少连接等待时间。所以如果你经常和一个服务器进行http交互的话,使用HTTP连接池是个不错的选择。 HTTP连接池的原理就是利用了keepalive的参数,保持几个或者多个服务器的连接,当你下次在有效的时间内去同一个服务器请求的时候,就会减少不小的开支。这就是为什么浏览器浏览一个网页,第一个请求可能很慢,后面的请求会很快,一方面可能是因为DNS初次解析的缘故,另一方面有可能就是keepalive参数的效果。 HTTP连接池的实现也有很多种,而且一般的HTTP包里的client都默认实现了连接池,你可以给它配置不同需求的连接池,这里示例apache的httpclient4X的连接池:org.apache.http.impl.conn.PoolingClientConnectionManager SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register( new Scheme("http", 80, PlainSocketFactory.getSocketFactory())); schemeRegistry.register( new Scheme("https", 443, SSLSocketFactory.getSocketFactory())); PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager(schemeRegistry); connectionManager.setMaxTotal(maxConnections); connectionManager.setDefaultMaxPerRoute(maxPerRoute); 把连接池管理器配给client即可 DefaultHttpClient httpClient = new DefaultHttpClient(connectionManager, params); 其中params 是你要配的其它参数,例如连接超时时间,请求超时时间等等。 ...

2014年5月12日 · 1 分钟

Spring集合类型的bean及其注入

集合类型的注入,下面是大家都熟悉的形式 <bean id="service"> <property name="map"> <map> <entry key="key1" value="value1" /> <entry key="key2" value="value2" /> </map> </property> <property name="set"> <set> <value>value1</value> <value>value2</value> </set> </property> <property name="list"> <list> <value>value1</value> <value>value2</value> </list> </property> <property name="props"> <props> <prop key="key1">value1</prop> <prop key="key2">value2</prop> </props> </property> </bean> 也可以让集合类型,独立出一个bean,然后再注入,这要用到util标签 <util:map id="map"> <entry key="key1" value="value1" /> <entry key="key2" value="value2" /> </util:map> <util:list id="list"> <value>value1</value> <value>value2</value> </util:list> <util:set id="set"> <value>value1</value> <value>value2</value> </util:set> <util:properties id="props"> <prop key="key1">value1</prop> <prop key="key2">value2</prop> </util:properties> <bean id="service"> <property name="map" ref="map" /> <property name="list" ref="list" /> <property name="set" ref="set" /> <property name="props" ref="props" /> </bean> 如果你想使用下面的这种形式,你需要在XML文件的头上引入util命名空间和模型规范文件: http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd 如下例: <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

2014年5月11日 · 1 分钟

Spring中特殊的接口(持续更新中)

Spring中提供很多特殊的接口,只要你的bean,实现了这些接口,Spring就会自动帮你完成一些相应的功能,让你的bean实现的丰富多彩,方便实用。 1、初始化接口,org.springframework.beans.factory.InitializingBean 实现该接口,需要实现一个 afterPropertiesSet() 方法。该方法会在所以的成员属性set之后执行。你可以在这个方法里完成一些初始化的工作,例如读取配置文件,配置连接参 数,启动线程池等待。 2、销毁接口 org.springframework.beans.factory.InitializingBean 实现该接口,需要实现一个destroy() 方法。该方法当在Spring容器关闭的时候执行,你可以在这个方法里完成一些程序关闭时的销毁工作,例如线程池的关闭,连接的关闭,资 源的关闭等等。 3、获取bean的id接口 org.springframework.beans.factory.BeanNameAware 实现该接口,需要实现一个setBeanName(String name)方法,Spring会像其他属性一样,把bean的id注入给你,用处举例:你可以根据业务设置不同的bean,你可以根据不同的id ,完成不同的业务逻辑 4、获取Spring容器接口 org.springframework.context.ApplicationContextAware 实现该接口,需要实现一个setApplicationContext(ApplicationContext applicationContext)接口,Spring会像其他属性一样,把应用上下文注入给你,你拿到这个context,可以对Spring完成一些自己想要操作。 5、bean工厂接口 org.springframework.beans.factory.FactoryBean<MyClass> 该接口是以工厂的形式产生一个MyClass类型的bean。实现该接口需要实现三个方法,MyClass getObject() 返回MyClass实例,Class<?> getObjectType()返回的MyClass实例的具体类型, boolean isSingleton(),该bean是否在spring容器中单例存在。

2014年5月10日 · 1 分钟

Spring使用占位符可配加密字符串

一个项目肯定会多少有些配置项,例如jdbc的一些配置,Spring的占位符,简化了对配置文件的读取,用起来极其方便。 1、假设你的配置项如下: jdbc.url=jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF8 jdbc.username=user jdbc.password=password jdbc.validationQuery=select 1 jdbc.maxActive=20 jdbc.initialSize=1 jdbc.minIdle=1 jdbc.maxWait=60000 jdbc.timeBetweenEvictionRunsMillis=3000 jdbc.minEvictableIdleTimeMillis=300000 jdbc.testWhileIdle=true jdbc.testOnReturn=false jdbc.testOnBorrow=false jdbc.defaultAutoCommit=true 写在配置文件config.properties中 2、你只要在Spring配置文件中配上如下bean <bean id="propertyConfigurer"> <property name="location" value="classpath:config.properties"/> </bean> 3、就可以在spring中自由注入你的配置项了 <bean abstract="true"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxActive" value="${jdbc.maxActive}" /> <property name="initialSize" value="${jdbc.initialSize}" /> <property name="maxWait" value="${jdbc.maxWait}" /> <property name="minIdle" value="${jdbc.minIdle}" /> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" /> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${jdbc.validationQuery}" /> <property name="testWhileIdle" value="${jdbc.testWhileIdle}" /> <property name="testOnBorrow" value="${jdbc.testOnBorrow}" /> <property name="testOnReturn" value="${jdbc.testOnReturn}" /> <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/> </bean> 4、如果你有些配置项是要加密的,在使用的时候需要解密。 这时你可以继承PropertyPlaceholderConfigurer,重写它的convertPropertyValue或者convertProperty方法进行解密 @Override protected String convertPropertyValue(String originalValue) { return 解密(originalValue); } @Override protected String convertProperty(String propertyName, String propertyValue) { return 解密(originalValue); } 然后把id=“propertyConfigurer"的class换成你自己的class。 这样就可以在配置文件中按你的格式配置密文了。

2014年5月8日 · 1 分钟

JAVA泛型之我见

java在1.5之前是没有泛型的,是后来加的,加了之后用起来感觉和C++的泛型差不多,但是原理是不一样的,C++的泛型是有宏演变而来的,虽然对泛型的处理不包含在预编译阶段 ,但是对泛型的处理也是简单的替换操作,也就是说泛型就相当于一个模版,在编译过后,编译器会帮你生成一个个具体的类。 java不同,java的模版类就是一个类,里面的参数T 不过是它的一个成员变量而已,只不过在构造示例的时候,以模版类型的方式传递。举个例子说: class Template<T>{} 它就是一个类,Template<String>(); 就是一个实例,String只不过是构造方法(可以这样理解)的一个参数而已,java编译器不会帮你生成一个新的类。 java的泛型在设计之初就是因为Object当作“任意”类型的时候,编译阶段无法进行类型检查,而引入了一个T参数,让你在构造对象的时候,强制你指定类型,它就可以做一些类 型检查了,你也可以进行对一个“任意”类型的功能实现,达到代码重用的目的。 对于Template<T> 这样的类型的实现子类,例如: class MyTemplate1 extend Template<String>{} class MyTemplate2 extend Template<Integer>{} 对于MyTemplate1和MyTemplate2的父类都是Template.class 但是又有所不同,上文说过Template本身就是一个类型,不是一个类的模版。所以为了描述MyTemplate1和MyTemplate2 的父类型的不同。 jdk又引入了一个ParameterizedType类型,它和Class 一样继承于Type类型。 它包含了一个泛型实例( Template<Integer>())的类型信息。 如果你想得到一个这样的类型,你只有通过泛型类型的子类得到,也就是说MyTemplate1的父类型才是这样的类型。 你通过Template<Integer> instance = new Template<Integer>()是得不到的这样的类型的。 你只有通过MyTemplate2的才可以得到: ParameterizedType type = (ParameterizedType)MyTemplate2.class.getGenericSuperclass(); 或者是通过一个匿名类 ParameterizedType type = new Template<Integer>(){}.getClass().getGenericSuperclass(); 可以通过ParameterizedType 的getActualTypeArguments()方法 拿到里面的Integer类型。 所以你在用各种json工具的时候,你会发现他们当把Json转成一个对象的时候,它都要求你传入一个Type类型,而不是一个Class类型 因为一个Class类型是无法描述Map<String,String>()这种类型全部信息的,那些json工具是无法通过一个Map.class帮你把把字符串转成一个具体的map的。 而且Map<String,String>()也不是一个类, 这时你可以传入一个ParameterizedType类型。 这样你就可以理解为什么像fastjson那样会提供下面这样一个类了: public class TypeReference<T> { private final Type type; protected TypeReference(){ Type superClass = getClass().getGenericSuperclass(); type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; } public Type getType() { return type; } public final static Type LIST_STRING = new TypeReference<List<String>>() {}.getType(); } 解释一下 new TypeReference<List<String>>() {} 是TypeReference<List<String>>的一个匿名子类。 它的getGenericSuperclass() 就是TypeReference<List<String>> 这种类型(ParameterizedType)。 它的参数也就是List<String> 也是一个ParameterizedType类型。

2014年4月30日 · 1 分钟

slf4j、log4j 的使用

驱动和桥接 log4j是一个日志系统。 slf4j是一个日志系统的封装,对外提供统一的API 使用slf4j需要下载 slf4j-api-x.x.x.jar 它提供对外一致的API接口,其本身不提供日志实现。 假设我们选择log4j作为我们的日志实现,需要下载 log4j-x.x.x.jar 如果想把slf4j绑定log4j,则需要下载slf4j对log4j的相应”驱动”。 slf4j-log4j12-x.x.x.jar 这样就可以使用slf4j提供的API,用log4j实现打日志了。 所谓驱动,就是实现了slf4j的一些接口,用你喜欢的日志系统打日志。 slf4j还支持好多日志系统,并提供了相应的“驱动”包 例如: slf4j-jdk14-x.x.x.jar是为java.util.logging提供的驱动 slf4j-simple-x.x.x.jar直接绑定System.err lf4j-jcl-x.x.x.jar是为commons-logging提供的驱动 logback-classic-x.x.x.jar是为logback提供的驱动 如果你引入了一个第三方jar包或者你之前的工程使用了commons-logging打日志。你想换成slf4j,你不需要更改代码,你需要使用桥接,你可以引入jcl-over-slf4j.jar,同时去掉commons-logging.jar包,这样之前打的日志会自动切换到你的slf4j中来了。同样还有log4j-over-slf4j.jar and jul-to-slf4j.jar等用于使用其它日志系统的应用自动切换到slf4j统一打日志。 logger,Appender Logger:日志对象,用来写日志的对象,每个Logger都会有一个名字,除了root Logger。root Logger是log4j始终存在的一个Logger. root Logger只能用Logger.getRootLogger()来获取,其它Logger可以用名字来得到Logger.getLogger(name); 每个Logger都可以设置它的输出日志的最低LEVEL和Appender。 LEVEL有 TRACE,DEBUG,INFO,WARN,ERROR FATAL几种。 Appender可以理解为日志的一个输出方式。每个Logger可以有多个Appender。 Logger之间根据名字,对输出日志的最低LEVEL和Appender有继承关系。 rootLogger<<Logger(“DEBUG”) rootLogger<<Logger(“INTERFACE”) rootLogger<<Logger(“com.zhaoyanblog”)<<Logger(“com.zhaoyanblog.xml”) 对于LEVEL的继承: 如果一个Logger没有设置最低LEVEL,它会继承它的直接父Logger的最低LEVEL。 对于Appender的继承:一个Logger除了配置给它的Appender之外,它会继承它所有父Logger的Appender,除非它的Additivity属性设为false。 log4j的配置方式。 log4j的配置方式有两种: 第一种:properties键值对的 方式 log4j默认配置文件为classpath下的log4j.properties #设置等级和Appender。 log4j.rootLogger=debug,appender1,apppender2 log4j.logger.com.huawei=error,apppender2 #定义appender和它的属性 #控制台输出的appender log4j.appender. appender1= org.apache.log4j.ConsoleAppender log4j.appender. appender1.layout=org.apache.log4j.PatternLayout log4j.appender. appender1.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n #文件输出的appender log4j.appender. appender2=org.apache.log4j.RollingFileAppender log4j.appender.apppender2.File=example.log log4j.appender.appender2.MaxFileSize=100KB log4j.appenderappender2.MaxBackupIndex=1 log4j.appender.appender2.layout=org.apache.log4j.PatternLayout log4j.appender.appender2.layout.ConversionPattern=%p %t %c - %m%n #输出日志的格式和信息都是可配可选的。 #设置Logger是否继承父logger的appender log4j. Additivity.com.zhaoyan=false 第二种:XML方式 ...

2014年4月16日 · 1 分钟

Spring使用注解形式的MVC

1、 在web.xml建立servlet <servlet> <servlet-name>myServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/my-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> Dispatcher 是spring的一个类,他会根据myServlet-context.xml配置文件 生成ApplicationContext, 如果不设置contextConfigLoaction参数,它会自动找/WEB-INF/目录下的[servlet-name]-servlet.xml 文件 对于本例:myServlet-servlet.xml 2、在web.xml设定调度servlet的一些URL类型。 <servlet-mapping> <servlet-name>myServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> 3、在/WEB-INF/myServlet-context.xml配置文件中添加,解析注解的语句以及相应的bean。 <!—加入处理注解的类 --> <mvc:annotation-driven/> <!—设置返回的字符串的前后缀,拼接到当前路径-> <bean> <property name="prefix" value="/"/> <property name="suffix" value=".jsp"/> </bean> <!—支持上传文件,它有一些属性,可以对上传文件的大小等参数进行控制-> <bean id="multipartResolver"/> <!—Controller 类-> <bean /> 4、写controller类 @Controller @RequestMapping("/admin") public class AccountController { //响应 POST 提交给"/admin/account.do "的方法 @RequestMapping(value = "/account.do", method = RequestMethod.POST) public void createAccount(@RequestParam MultipartFile file, //提交文件 String name, // 文本框之类的 boolean supportVersion, //单选按钮之类的 Boolean[]books, //复选框之类的 long space, int maxVersion, Model model) { ...... return "xxxxxx"; } 参数名要和jsp中的控件的名字相同, 根据第3步的设置,这里处理完后自动转向/xxxxxx.jsp页面进行展示。 ...

2014年4月9日 · 1 分钟