Java框架之Spring(五)
本文主要介绍Spring中,
1 Spring JDBC
2 使用注解方式管理事务的传播行为
3 采用XML 方式配置事务
4 SH 整合
5 SSH 整合
一、Spring JDBC
1) 导包 , 使用myeclipse2014, 添加与持久化相关的包
2) 引入名称空间等
3) 配置数据源
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"><!-- 单实例bean -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>
<property name="initialSize" value="10"/> <!-- 连接池启动时的初始值 -->
<property name="maxActive" valu3) e="300"/> <!-- 连接池的最大值 -->
<property name="maxIdle" value="5"/> <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="minIdle" value="3"/> <!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
</bean>
4) dao层
@Repository("userDaoImpl")
public class UserDaoImpl {
private JdbcTemplate jdbcTemplate; //用于进行数据库操作的模板,线程安全的 @Resource
public void setDataSource(DataSource dataSource){
this.jdbcTemplate=new JdbcTemplate(dataSource);
} //添加
public int addUser(UserInfo user){
String sql="insert into userInfo (userName,password,note) values(?,?,?)" ;
Object[] paramList={user.getUserName(),user.getPassword(),user.getNote()}; //也可以不用定义数组,直接这样写 :return jdbcTemplate.update(sql, user.getUserId(),user.getUserName(),user.getPassword(),user.getNote());
return jdbcTemplate.update(sql, paramList);
} //删除
public int delUser(int id){
String sql="delete from userInfo where id=?";
Object[] paramList={id};
return jdbcTemplate.update(sql, paramList);
} //修改
public int update(UserInfo user){
String sql="update userInfo set userName=?,password=?, note=? where id=?";
Object[] paramList={user.getUserName(),user.getPassword(),user.getNote(),user.getId()};
return jdbcTemplate.update(sql, paramList);
} //查询
public UserInfo getUserById(int id){
String sql="select * from userInfo where id=?"; //注意:如果查询结果多于一条,将报异常
Object[] paramList={id};
Object user=jdbcTemplate.queryForObject(sql, paramList, new BeanPropertyRowMapper(UserInfo.class));
return (UserInfo)user;
} //查询出列表
public List<UserInfo>getUserList(){
String sql="select * from userInfo";
return (List<UserInfo>)jdbcTemplate.query(sql,new BeanPropertyRowMapper(UserInfo.class));
} //查询出单个Int值
public int getUserCount(){
String sql="select count(*) from userInfo";
return jdbcTemplate.queryForInt(sql);
} //只查询一个字段
public String getUserName(int id){
String sql="select userName from userInfo where id= "+id;
return (String)jdbcTemplate.queryForObject(sql, String.class); //因为要查询的字段是String型的,所以在这里用 String.class
} //返回map
public Map getUserMapData(int id){
String sql="select * from userInfo where id="+id;
return jdbcTemplate.queryForMap(sql);
} //添加一个用户,返回其ID
public void addUser2(final UserInfo user){ //注意,这里必须用 final
jdbcTemplate.execute(new ConnectionCallback<Object>() { //org.springframework.jdbc.core.包下的 ConnectionCallback
@Override
public Object doInConnection(Connection conn) throws SQLException,DataAccessException { String sql="insert into userInfo (userName,password,note) values(?,?,?)" ;
PreparedStatement stm=conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
stm.setString(2, user.getUserName());
stm.setString(3, user.getPassword());
stm.setString(4, user.getNote()); stm.executeUpdate();
ResultSet rs=stm.getGeneratedKeys(); if(rs.next()){
System.out.println("新添加的用户的主键是:"+rs.getInt(1));
}
return null;
}
});
} //拿到自增主键的另一个方法,来自帮助文档
public void ttt(){
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(
new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement("insert into my_test (name) values(?)", new String[] {"id"});
ps.setString(1, "张三");
return ps;
}
},
keyHolder);
keyHolder.getKey(); //这就是拿到的自增主键
} }
5) 测试用例
public class UserDaoImplTest {
private static ClassPathXmlApplicationContext ctx;
private static UserDaoImpl dao; @BeforeClass
public static void setUpBeforeClass() throws Exception {
ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
dao=(UserDaoImpl)ctx.getBean("userDaoImpl");
} @Before
public void setUp() throws Exception {
} @Test
public void testAddUser() {
UserInfo user=new UserInfo();
user.setUserName("赵明明");
user.setPassword("忘了");
user.setNote("又想起来了"); dao.addUser(user); System.out.println("-----------嘻嘻---------------"); } @Test
public void testDelUser() {
int result=dao.delUser(196611);
System.out.println(result);
} @Test
public void testUpdate() {
UserInfo user=new UserInfo();
user.setId(196610);
user.setUserName("费无极");
user.setPassword("大费费");
user.setNote("中小型费费"); int result=dao.update(user);
System.out.println(result);
} @Test
public void testGetUserById() {
UserInfo user=dao.getUserById(196609);
System.out.println(user.getNote());
} @Test
public void testGetUserList() {
List<UserInfo>userList=dao.getUserList();
for(UserInfo u:userList){
System.out.println(u.getUserName());
}
} @Test
public void testGetUserCount() {
int result=dao.getUserCount();
System.out.println(result);
} @Test
public void testGetUserName() {
String userName=dao.getUserName(491553);
System.out.println(userName);
} @Test
public void testGetUserMapData() {
Map userMap=dao.getUserMapData(2); Set<Map.Entry>entrySet=userMap.entrySet();
Iterator <Map.Entry>it=entrySet.iterator();
while(it.hasNext()){
Map.Entry item=it.next();
System.out.println(item.getKey()+"---"+item.getValue());
}
} @Test
public void testAddUser2() {
UserInfo user=new UserInfo();
user.setUserName("奥八马");
user.setPassword("小狒狒");
user.setNote("超级小狒狒");
dao.addUser2(user);
} @Test
public void testTtt() { }
二、使用注解方式管理事务的传播行为
//删除
public int delUser(int id){
String sql="delete from userInfo where id=?";
Object[] paramList={id};
int result= jdbcTemplate.update(sql, paramList); int a=90/0; //如果程序在这里出现异常,会不会回滚,默认情况下,不回滚
return result;
}
在spring中开启事务管理
1)要加入相应的名称空间
xmlns:tx="http://www.springframework.org/schema/tx"
...
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
2) 配置事务管理器
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean> <!-- 使用注解的方式管理事务(不使用注解,不用加这句) -->
<tx:annotation-driven transaction-manager="txManager"/>
3) 在要进行事务管理的类上,用注解声明
@Transactional @Repository("userDaoImpl")
public class UserDaoImpl {
public int delUser(int id){
String sql="delete from userInfo where id=?";
Object[] paramList={id};
int result= jdbcTemplate.update(sql, paramList); int a=90/0; //如果程序在这里出现异常,会不会回滚,由于声明了事务,所以会回滚
return result;
} ...
} //对这个方法进行细粒度的控制
@Transactional(noRollbackFor=RuntimeException.class)
public void delAllUser(){
String sql="delete from userInfo ";
jdbcTemplate.execute(sql);
throw new RuntimeException(发生了运行时异常); }
这段代码要删除所有用户,但运行时抛出了异常,所以事务会回滚,数据不会被删除,可以更改这种行为,在方法前加上注解。
@Transactional(noRollbackFor=RuntimeException.class) 表示出了异常也不回滚。
有的方法比如查询方法,不需要事务,可以在该方法上指定。
@Transactional(propagation=Propagation.NOT_SUPPORTED) //加上以后,这个方法就不再支持事务了
public List<UserInfo>getUserList(){
String sql="select * from userInfo";
return (List<UserInfo>)jdbcTemplate.query(sql,new BeanPropertyRowMapper(UserInfo.class));
}
事务的传播行为
----REQUIRED:业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务。
----NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。
----REQUIRESNEW:(requiresnew) 属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。
----MANDATORY (mandatory,强制的,命令的,受委托的):该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。 如果业务方法在没有事务的环境下调用,容器就会抛出例外。
----SUPPORTS:这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。
----Never:指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。
----NESTED:nested (窝,嵌套)如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。
内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。
REQUIRED 是默认的 ,比如
//这里什么都不写,相当于写上 @Transactional(propagation=Propagation.REQUIRED)
public int delUser(int id){
String sql="delete from userInfo where id=?";
Object[] paramList={id};
int result= jdbcTemplate.update(sql, paramList); int a=90/0; //如果程序在这里出现异常,会不会回滚
return result; public void testTx(UserInfo user){
jdbcTemplate.update("delete from userInfo where id=1");
addUser(user);
jdbcTemplate.update("delete from userInfo where id=2");
//int x=0/0;
}
说明:
对于这个例子来说,没有指明事务的传播行为,所以这是默认的 @Transactional(propagation=Propagation.REQUIRED)。
对于 addUser 来说,如果如果方法运行时,已经处在一个事务中,则它将加入这个事务。所以,上面的操作,将会成为一个整体。
三、采用XML 方式配置事务
<aop:config>
<aop:pointcut id="myTxPointCut" expression="execution(* cat.dao.UserDaoImpl_other.*(..))" />
<aop:advisor advice-ref="txAdvisor" pointcut-ref="myTxPointCut" />
</aop:config> <tx:advice id="txAdvisor" transaction-manager="txManager" >
<tx:attributes>
<tx:method name="get*" read-only="true" propagation="NOT_SUPPORTED" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>
用XML配置的方式,就不用再写
<tx:annotation-driven transaction-manager="txManager"/>
@Repository("userDaoImpl_other")
public class UserDaoImpl_other {
....
public void testTx(UserInfo user){
jdbcTemplate.update("delete from userInfo where id=5");
addUser(user);
jdbcTemplate.update("delete from userInfo where id=6"); int x=0/0;
} public static void main(String[] args) {
ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl_other dao=(UserDaoImpl_other)ctx.getBean("userDaoImpl_other"); UserInfo user=new UserInfo();
user.setUserName("9999999999999");
user.setPassword("小狒狒");
user.setNote("超级小狒狒"); dao.testTx(user); } }
四、SH 整合
1) 导入名称空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
2) 打开扫描
<context:component-scan base-package="cat" />
3) 配置 DataSource //hibernate用的
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/production?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="initialSize" value="10"/> 连接池启动时的初始值
<property name="maxActive" value="500"/> 连接池的最大值
<property name="maxIdle" value="2"/> 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止
<property name="minIdle" value="3"/> 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请
</bean>
4) 配置SessionFactory //hibernate用的
/*
sessionFactory 建议做成单例
LocalSessionFactoryBean 除了可建一个 sessionFactory 对象出来,把它做成单例,还专门用于集成
Hibernate ,用于做一些额外的工作,比如接管hibernate的事务
*/
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean" >
<property name="dataSource">
<ref bean="dataSource" />
</property> <property name="mappingResources">
<list>
<value>cat/beans/UserInfo.hbm.xml</value>
</list>
</property> <property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.hbm2ddl.auto=update
hibernate.show_sql=true
hibernate.format_sql=true //配置二级缓存用的
// hibernate.cache.use_second_level_cache=true
// hibernate.cache.use_query_cache=false //是否使用查询缓存 因为它的效率比较差,所以不用
// hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider //指定的缓存产品 要导入 lib\optional\ejcacje -1-1-3/jar
</value>
</property>
</bean>
5) 配置事务管理器 txManager
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean> <tx:annotation-driven transaction-manager="txManager"/> 如果要用注解方式管理事务,要加这个配置
五、SSH 整合
再和 struts2 进行整合
1) 在web.xml 中 对Spring容器进行初始化
//指定spring 的配置文件,默认从web根目录寻找配置文件,可以通过string 提供的classpath:前缀指定从类路径下寻找
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value> 如果配置多个配置文件,可以用逗号隔开
</context-param>
// 对spring 容器进行实例化 ,实例化后,这个实例会放在applicateion 范围-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
2) 在web.xml中添加struts2应用
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>杠*</url-pattern>
</filter-mapping>
3) 在类路径下添加 sruts.xml 文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
"http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
<constant name="struts.objectFactory" value="spring" /> //声明要用spring来创建 action 类 <package name="p_user" namespace="" extends="struts-default">
<action name="userAction_name" class="userAction">
<result name="success">/main.jsp</result>
</action>
</package>
</struts>
4) Action 类
@Controller
public class UserAction {
@Resource(name="userDaoImpl")
private IUserDao dao;
public String execute(){
UserInfo user=new UserInfo();
user.setUserName("SSH整合成功");
dao.addUser(user);
return "success";
}
}
5)接口
public interface IUserDao {
void addUser(UserInfo user) ; void delUser(int id) ; UserInfo getUserById(int id); List<UserInfo>getUserList(); void updateUser(UserInfo user); void deleteUser(UserInfo user);
}
6)实现类
@Transactional @Repository
public class UserDaoImpl implements IUserDao{
@Resource //这个注解默认是按名称注入
private SessionFactory sessionFactory; public void addUser(UserInfo user) {
//Session s=sessionFactory.openSession();
//它得到的是spring容器管理的session
Session s=sessionFactory.getCurrentSession();
s.save(user);
} public void delUser(int id) {
Session s=sessionFactory.getCurrentSession();
UserInfo user=(UserInfo)s.get(UserInfo.class, id);
s.delete(user); //先查一下,再删除
} @Transactional(propagation=Propagation.NOT_SUPPORTED)
public UserInfo getUserById(int id) {
Session s=sessionFactory.getCurrentSession();
return (UserInfo)s.get(UserInfo.class, id);
} @Transactional(propagation=Propagation.NOT_SUPPORTED)
public List<UserInfo> getUserList() {
Session s=sessionFactory.getCurrentSession();
return s.createQuery("from UserInfo").list();
} public void updateUser(UserInfo user) {
Session s=sessionFactory.getCurrentSession();
s.saveOrUpdate(user);
} public void deleteUser(UserInfo user) {
Session s=sessionFactory.getCurrentSession();
s.delete(user);
} }
附 如何知道一个类在哪个jar包中
ctl + shift + t
附 关于被Spring 管理的 dao 使用接口访问的问题
Spring的文档中这么写的:Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理,如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。使用beanNameAutoProxyCreator来进行事务代理的话,他的proxyTargetClass这个属性设置为false(默认是false),即使用JDK动态代理,如果你的service类没有实现接口的话,就会报类型转换错误。
解决办法有:
1、给service类添加一个接口iService,让service类实现它,则创建代理类时使用JDK动态代理就不会出现问题
2、设置beanNameAutoProxyCreator的proxyTargetClass属性为true,意思是强制使用CGLIB代理,前提是你已经将CGLIB包加入到项目中。
其实,就是 在配置文件中配置:
<aop:aspectj-autoproxy proxy-target-class="true"/>
但要注意:
需要导入 cglib-nodep-2.2.jar (它可以从myeclipse 自动生成的 Spring 项目中获得),还要导入 aspectj 相关的几个jar包。
Java框架之Spring(五)的更多相关文章
- java框架篇---spring AOP 实现原理
什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入 ...
- java框架之Spring(2)-注解配置IOC&AOP配置
注解配置IoC 准备 1.要使用注解方式配置 IoC,除了之前引入的基础 jar 包,还需要引入 spring-aop 支持包,如下: 2.在 applicationContext.xml 中引入 c ...
- Java框架之Spring(四)
本文主要讲述在Spring中 1 注解方式装配 2 以自动扫描把组件纳入spring容器中管理 3 面象切面编程-代理的jdk 版实现 4 使用 Cglib 生成代理 5 aop编程的一些概念 6 使 ...
- Java - 框架之 Spring
一. IOC 和 DI IOC : 控制反转,将对象的创建权反转给了 Spring.DI : 依赖注入,前提是必须要有 IOC 的环境,Spring 管理这个类的时候将类的依赖的属性注入(设置)进来 ...
- java框架篇---spring IOC 实现原理
IOC(DI):其实这个Spring架构核心的概念没有这么复杂,更不像有些书上描述的那样晦涩.java程序员都知道:java程序中的每个业务逻辑至少需要两个或以上的对象来协作完成,通常,每个对象在使用 ...
- Java框架之Spring MVC(二)
一.Spring MVC 验证 JSR 303 是ajvaEE6 中的一项子规范 ,叫 Bean Validation 用于对javaBean中的字段进行校验. 官方的参考实现是: Hibernate ...
- Java框架之Spring MVC(一)
一.Spring简介 Spring MVC是当前最优秀的 MVC 框架,自从Spring 2.5 版本发布后,由于支持注解配置,易用性有了大幅度的提高.Spring 3.0 更加完善,实现了对 Str ...
- java框架之spring
一.HelloWorld程序 导入四个核心包(core.beans.expression.context)和一个logging的包: 写一个类并在 xml 中配置相应的bean(两个重要属性 id 和 ...
- java框架之Spring(1)-入门
介绍 概述 Spring 是一个开放源代码的设计层面框架,它解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用.Spring 是于 2003 年兴起的一个轻量级的 J ...
随机推荐
- 轻谈BFC
BFC 定义 CSS2.1的定义 Block formatting contexts 9.4.1 Block formatting contexts Floats, absolutely positi ...
- C++ Primer高速入门之三:几种常见的控制语句
语句总是顺序运行的:第一条语句运行完了接着是第二条,第三条等等.这是最简单的情况,为了更好的控制语句的运行.程序设计语言提供了多种控制结构支持更为复杂的语句运行.我们就来看看C++ 提供的控制方式. ...
- 为Android Studio设置HTTP代理
大陆的墙非常厚非常高.初次安装Android Studio下载SDK等必然失败,设置代理方法例如以下: 1. 到android studio安装文件夹,打开bin文件夹.编辑idea.properti ...
- TCP服务端开发为例--web开发不同url请求走不同control方法
拿java的web开发为例子,相信有很多小伙伴是做j2EE开发的,htpp请求,json数据传输都是工作中经常用的,查询请求,添加请求,修改请求前端配个url,例如https://localhost/ ...
- 二、springcloud Netflix 注册中心
Eureka是Netflix开源的一款提供服务注册和发现的产品,它提供了完整的Service Registry和Service Discovery实现.也是springcloud体系中最重要最核心的组 ...
- 从源码角度简单看StringBuilder和StringBuffer的异同
概述 StringBuilder和StringBuffer是两个容易混淆的概念,本文从源码入手,简单看二者的异同. 容易知道的是,这两者有一个是线程安全的,而且线程安全的那个效率低. java doc ...
- 【java设计模式】【行为模式Behavioral Pattern】策略模式Strategy Pattern
package com.tn.策略模式; public class Client { private Strategy strategy; public void setStrategy(Strate ...
- 【NOIP模拟】【USACO】 Bovine Genomics
Description 给定两个字符串集合A,B,均包含N个字符串,长度均为M,求一个最短的区间[l,r],使得不存在字符串\(a\in A,b\in B,\)且\(a[l,r]=b[l,r]\) , ...
- 链表创建和链表遍历算法的演示_C语言
今天搞了一个多小时,头是疼的,应该是没休息好吧,学习了数据结构这一节,感觉收益良多,下面贴上代码和心得: /*24_链表创建和链表遍历算法的演示*/ # include <stdio.h> ...
- async和enterproxy控制并发数量
聊聊并发与并行 并发我们经常提及之,不管是web server,app并发无处不在,操作系统中,指一个时间段中几个程序处于已经启动运行到完毕之间,且这几个程序都是在同一处理机上运行,并且任一个时间点只 ...