Spring详解
Spring是什么
Spring是一个开源的控制反转(Inversion of Control ,IoC)和面向切面(AOP)的容器框架.它的主要目的是简化企业开发.
IOC 控制反转
public class PersonServiceBean {
private PersonDao personDao = new PersonDaoBean();
public void save(Person person){
personDao.save(person);
}
}
PersonDaoBean 是在应用内部创建及维护的。所谓控制反转就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的。这样控制权就由应用转移到了外部容器,控制权的转移就是所谓反转。
依赖注入(Dependency Injection)
当我们把依赖对象交给外部容器负责创建,那么PersonServiceBean类可以改成如下:
public class PersonServiceBean {
private PersonDao personDao ;
//通过构造器参数,让容器把创建好的依赖对象注入进PersonServiceBean,当然也可以使用setter方法进行注入。
public PersonServiceBean(PersonDao personDao){
this.personDao=personDao;
}
public void save(Person person){
personDao.save(person);
}
}
所谓依赖注入就是指:在运行期,由外部容器动态地将依赖对象注入到组件中。
为何要使用Spring
至少在我看来,在项目中引入spring立即可以带来下面的好处
降低组件之间的耦合度,实现软件各层之间的解耦。
可以使用容器提供的众多服务,如:事务管理服务、消息服务等等。当我们使用容器管理事务时,开发人员就不再需要手工控制事务.也不需处理复杂的事务传播。
容器提供单例模式支持,开发人员不再需要自己编写实现代码。
容器提供了AOP技术,利用它很容易实现如权限拦截、运行期监控等功能。
容器提供的众多辅作类,使用这些类能够加快应用的开发,如: JdbcTemplate、 HibernateTemplate。
Spring对于主流的应用框架提供了集成支持,如:集成Hibernate、JPA、Struts等,这样更便于应用的开发。
使用Spring的好处
当使用spring时,我们可以使用容器提供的众多服务
如果使用Spring,我们就不再需要手工控制事务
另外,如果使用spring,我们也不需要处理复杂的事务传播行为
public void payment(){
Bean1.update();//更新金额
Bean2.save();//记录操作日志
}
如果我们不使用Spring,针对下面这两种业务需求,我们该如何做?
第1种可能的业务需求:要求Bean1.update()和Bean2.save()在同一个事务中执行。
第2种可能的业务需求:要求不管Bean1.update()的事务是否成功,都需要记录操作日志。
public class Bean1 {
public void update(){//注意:下面省略了一些代码
Connection conn = null;
conn.setAutoCommit(false);
Statement.executeUpdate(“update account set amount=? where id=?");
}
}
public class Bean2 {
public void save(){//注意:下面省略了一些代码
Connection conn = null;
conn.setAutoCommit(false);
Statement.executeUpdate(“insert into Log (content) values (?)");
}
}
使用Spring,不再需要我们处理复杂的事务传播行为
使用Spring,我们只需要通过声明式的事务属性配置就可以轻松地实现这两种业务需求
1.要求Bean1.update()和Bean2.save()的在同一个事务中执行
2.要求不管Bean1.update()的事务是否成功,都需要记录日志。
@Transactional(propagation=Propagation.Required)
public void payment(){
Bean1.update();//更新金额
Bean2.save();//记录日志
}
public class Bean1 {
@Transactional(propagation=Propagation.Required)
public void update(){
executeUpdate(“update account set amount=? where id=?");
}
}
public class Bean2 {
@Transactional(propagation=Propagation.RequiresNew)
public void save(){
executeUpdate(“insert into Log (content) values (?)");
}
}
轻量级与重量级概念的划分
经常会有同学问到spring属于轻量级框架,还是重量框架?其实划分一个应用是否属于轻量级还是重量级,主要看它使用了多少服务.使用的服务越多,容器要为普通java对象做的工作就越多,必然会影响到应用的发布时间或者是运行性能.
对于spring容器,它提供了很多服务,但这些服务并不是默认为应用打开的,应用需要某种服务,还需要指明使用该服务,如果应用使用的服务很少,如:只使用了spring核心服务,那么我们可以认为此时应用属于轻量级的,如果应用使用了spring提供的大部分服务,这时应用就属于重量级。目前EJB容器就因为它默认为应用提供了EJB规范中所有的功能,所以它属于重量级。
使用Spring需要的jar
到http://www.springsource.org/download下载spring,然后进行解压缩,在解压目录中找到下面jar文件,拷贝到类路径下
dist\spring.jar
lib\jakarta-commons\commons-logging.jar
如果使用了切面编程(AOP),还需要下列jar文件
lib/aspectj/aspectjweaver.jar和aspectjrt.jar
lib/cglib/cglib-nodep-2.1_3.jar
如果使用了JSR-250中的注解,如@Resource/@PostConstruct/@PreDestroy,还需要下列jar文件
lib\j2ee\common-annotations.jar
spring的配置文件模版
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
.....
</beans>
该配置模版可以从spring的参考手册或spring的例子中得到。配置文件的取名可以任意,文件可以存放在任何目录下,但考虑到通用性,一般放在类路径下。
编写spring配置文件时,不能出现帮助信息
由于spring的schema文件位于网络上,如果机器不能连接到网络,那么在编写配置信息时候就无法出现提示信息,解决方法有两种:
1。让机器上网,eclipse会自动从网络上下载schema文件并缓存在硬盘上。
2。手动添加schema文件,方法如下:
windwos->preferences->myeclipse->files and editors->xml->xmlcatalog
点"add",在出现的窗口中的Key Type中选择URI,在location中选"File system",然后在spring解压目录的dist/resources目录中选择spring-beans-2.5.xsd,回到设置窗口的时候不要急着关闭窗口,应把窗口中的Key Type改为Schema location,Key改为http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
实例化spring容器
实例化Spring容器常用的两种方式:
方法一:
在类路径下寻找配置文件来实例化容器
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"beans.xml"});
方法二:
在文件系统路径下寻找配置文件来实例化容器
ApplicationContext ctx = new FileSystemXmlApplicationContext(new String[]{“d:\\beans.xml“});
Spring的配置文件可以指定多个,可以通过String数组传入。
从spring容器中得到bean
当spring容器启动后,因为spring容器可以管理bean对象的创建,销毁等生命周期,所以我们只需从容器直接获取Bean对象就行,而不用编写一句代码来创建bean对象。从容器获取bean对象的代码如下:
ApplicationContext ctx = new ClassPathXmlApplicationContext(“beans.xml”);
OrderService service = (OrderService)ctx.getBean("personService");
使用dom4j读取spring配置文件
public class SdkdClassPathXmlApplicationContext {
private List<BeanDefinition> beanDefines = new ArrayList<BeanDefinition>();
public SdkdApplicationContext(String filename){
init(filename);
}
private void init(String filename){
SAXReader saxReader = new SAXReader();
Document document=null;
try{
URL xmlpath = this.getClass().getClassLoader().getResource(filename);
document = saxReader.read(xmlpath);
Map<String,String> nsMap = new HashMap<String,String>();
nsMap.put("ns","http://www.springframework.org/schema/beans");//加入命名空间
XPath xsub = document.createXPath("//ns:beans/ns:bean");//创建beans/bean查询路径
xsub.setNamespaceURIs(nsMap);//设置命名空间
List<Element> beans = xsub.selectNodes(document);//获取文档下所有bean节点
for(Element element: beans){
String id = element.attributeValue("id");//获取id属性值
String clazz = element.attributeValue("class"); //获取class属性值
BeanDefinition beanDefine = new BeanDefinition(id, clazz);
beanDefines.add(beanDefine);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
三种实例化bean的方式
1.使用类构造器实例化
<bean id=“orderService" class="cn.sdkd.OrderServiceBean"/>
2.使用静态工厂方法实例化
<bean id="personService" class="cn.sdkd.service.OrderFactory" factory-method="createOrder"/>
public class OrderFactory {
public static OrderServiceBean createOrder(){
return new OrderServiceBean();
}
}
3.使用实例工厂方法实例化:
<bean id="personServiceFactory" class="cn.sdkd.service.OrderFactory"/>
<bean id="personService" factory-bean="personServiceFactory" factory-method="createOrder"/>
public class OrderFactory {
public OrderServiceBean createOrder(){
return new OrderServiceBean();
}
}
Bean的作用域
.singleton
在每个Spring IoC容器中一个bean定义只有一个对象实例。默认情况下会在容器启动时初始化bean,但我们可以指定Bean节点的lazy-init=“true”来延迟初始化bean,这时候,只有第一次获取bean会才初始化bean。如:
<bean id="xxx" class="cn.sdkd.OrderServiceBean" lazy-init="true"/>
如果想对所有bean都应用延迟初始化,可以在根节点beans设置default-lazy-init=“true“,如下:
<beans default-lazy-init="true“ ...>
.prototype
每次从容器获取bean都是新的对象。scope=“prototype”
只有在调用getBean()时才会实例化bean。
.request
.session
.global session
指定Bean的初始化方法和销毁方法
指定Bean的初始化方法和销毁方法
<bean id="xxx" class="cn.sdkd.OrderServiceBean" init-method="init" destroy-method="close"/>
注入依赖对象
基本类型对象注入:
<bean id="orderService" class="cn.sdkd.service.OrderServiceBean">
<constructor-arg index=“0” type=“java.lang.String” value=“xxx”/>//构造器注入
<property name=“name” value=“zhao/>//属性setter方法注入
</bean>
注入其他bean:
方式一
<bean id="orderDao" class="cn.sdkd.service.OrderDaoBean"/>
<bean id="orderService" class="cn.sdkd.service.OrderServiceBean">
<property name="orderDao" ref="orderDao"/>
</bean>
方式二(使用内部bean,但该bean不能被其他bean使用)
<bean id="orderService" class="cn.sdkd.service.OrderServiceBean">
<property name="orderDao">
<bean class="cn.sdkd.service.OrderDaoBean"/>
</property>
</bean>
集合类型的装配
public class OrderServiceBean {
private Set<String> sets = new HashSet<String>();
private List<String> lists = new ArrayList<String>();
private Properties properties = new Properties();
private Map<String, String> maps = new HashMap<String, String>();
....//这里省略属性的getter和setter方法
}
集合类型的装配
<bean id="order" class="cn.sdkd.service.OrderServiceBean">
<property name="lists">
<list>
<value>lihuoming</value>
</list>
</property>
<property name="sets">
<set>
<value>set</value>
</set>
</property>
<property name="maps">
<map>
<entry key="lihuoming" value="28"/>
</map>
</property>
<property name="properties">
<props>
<prop key="12">sss</prop>
</props>
</property>
</bean>
依赖注入
使用构造器注入
使用属性setter方法注入
使用Field注入(用于注解方式)
注入依赖对象可以采用手工装配或自动装配,在实际应用中建议使用手工装配,因为自动装配会产生未知情况,开发人员无法预见最终的装配结果。
1.手工装配依赖对象
2.自动装配依赖对象
依赖注入--手工装配
手工装配依赖对象,在这种方式中又有两种编程方式
1. 在xml配置文件中,通过在bean节点下配置,如
<bean id="orderService" class="cn.sdkd.service.OrderServiceBean">
<constructor-arg index=“0” type=“java.lang.String” value=“xxx”/>//构造器注入
<property name=“name” value=“zhao/>//属性setter方法注入
</bean>
2. 在java代码中使用@Autowired或@Resource注解方式进行装配。但我们需要在xml配置文件中配置以下信息:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config/>
</beans>
这个配置隐式注册了多个对注释进行解析处理的处理器:AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,PersistenceAnnotationBeanPostProcessor,RequiredAnnotationBeanPostProcessor
注: @Resource注解在spring安装目录的lib\j2ee\common-annotations.jar
依赖注入--手工装配
在java代码中使用@Autowired或@Resource注解方式进行装配,这两个注解的区别是:@Autowired默认按类型装配,@Resource默认按名称装配,当找不到与名称匹配的bean才会按类型装配。
@Autowired
private PersonDao personDao;//用于字段上
@Autowired
public void setOrderDao(OrderDao orderDao) {//用于属性的setter方法上
this.orderDao = orderDao;
}
@Autowired注解是按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false。如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。如下:
@Autowired @Qualifier("personDaoBean")
private PersonDao personDao;
@Resource注解和@Autowired一样,也可以标注在字段或属性的setter方法上,但它默认按名称装配。名称可以通过@Resource的name属性指定,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。
@Resource(name=“personDaoBean”)
private PersonDao personDao;//用于字段上
注意:如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。
依赖注入--自动装配依赖对象
对于自动装配,大家了解一下就可以了,实在不推荐大家使用。例子:
<bean id="..." class="..." autowire="byType"/>
autowire属性取值如下:
byType:按类型装配,可以根据属性的类型,在容器中寻找跟该类型匹配的bean。如果发现多个,那么将会抛出异常。如果没有找到,即属性值为null。
byName:按名称装配,可以根据属性的名称,在容器中寻找跟该属性名相同的bean,如果没有找到,即属性值为null。
constructor与byType的方式类似,不同之处在于它应用于构造器参数。如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常。
autodetect:通过bean类的自省机制(introspection)来决定是使用constructor还是byType方式进行自动装配。如果发现默认的构造器,那么将使用byType方式。
通过在classpath自动扫描方式把组件纳入spring容器中管理
前面的例子我们都是使用XML的bean定义来配置组件。在一个稍大的项目中,通常会有上百个组件,如果这些这组件采用xml的bean定义来配置,显然会增加配置文件的体积,查找及维护起来也不太方便。spring2.5为我们引入了组件自动扫描机制,他可以在类路径底下寻找标注了@Component、@Service、@Controller、@Repository注解的类,并把这些类纳入进spring容器中管理。它的作用和在xml文件中使用bean节点配置组件是一样的。要使用自动扫描机制,我们需要打开以下配置信息:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="cn.sdkd"/>
</beans>
其中base-package为需要扫描的包(含子包)。
@Service用于标注业务层组件、 @Controller用于标注控制层组件(如struts中的action)、@Repository用于标注数据访问组件,即DAO组件。而@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
JDK动态代理
public class JDKProxy implements InvocationHandler {
private Object targetObject;//代理的目标对象
public Object createProxyInstance(Object targetObject){
this.targetObject = targetObject;
/*
* 第一个参数设置代码使用的类装载器,一般采用跟目标类相同的类装载器
* 第二个参数设置代理类实现的接口
* 第三个参数设置回调对象,当代理对象的方法被调用时,会委派给该参数指定对象的invoke方法
*/
return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
this.targetObject.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return method.invoke(this.targetObject, args);//把方法调用委派给目标对象
}
}
当目标类实现了接口,我们可以使用jdk的Proxy来生成代理对象。
使用CGLIB生成代理
public class CGLIBProxy implements MethodInterceptor {
private Object targetObject;//代理的目标对象
public Object createProxyInstance(Object targetObject){
this.targetObject = targetObject;
Enhancer enhancer = new Enhancer();//该类用于生成代理对象
enhancer.setSuperclass(this.targetObject.getClass());//设置父类
enhancer.setCallback(this);//设置回调用对象为本身
return enhancer.create();
}
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(this.targetObject, args);
}
}
CGLIB可以生成目标类的子类,并重写父类非final修饰符的方法。
AOP中的概念
Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面横切性关注点的抽象.
joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器)
Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义.
Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知
Target(目标对象):代理的目标对象
Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入.
Introduction(引入):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
使用Spring进行面向切面(AOP)编程
要进行AOP编程,首先我们要在spring的配置文件中引入aop命名空间:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
</beans>
Spring提供了两种切面声明方式,实际工作中我们可以选用其中一种:
基于XML配置方式声明切面。
基于注解方式声明切面。
基于注解方式声明切面
首先启动对@AspectJ注解的支持(蓝色部分):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<aop:aspectj-autoproxy/>
<bean id="orderservice" class="cn.sdkd.service.OrderServiceBean"/>
<bean id="log" class="cn.sdkd.service.LogPrint"/>
</beans>
基于注解方式声明切面
@Aspect
public class LogPrint {
@Pointcut("execution(* cn.sdkd.service..*.*(..))")
private void anyMethod() {}//声明一个切入点
@Before("anyMethod() && args(userName)")//定义前置通知
public void doAccessCheck(String userName) {
}
@AfterReturning(pointcut="anyMethod()",returning="revalue")//定义后置通知
public void doReturnCheck(String revalue) {
}
@AfterThrowing(pointcut="anyMethod()", throwing="ex")//定义例外通知
public void doExceptionAction(Exception ex) {
}
@After("anyMethod()")//定义最终通知
public void doReleaseAction() {
}
@Around("anyMethod()")//环绕通知
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
return pjp.proceed();
}
}
基于基于XML配置方式声明切面
public class LogPrint {
public void doAccessCheck() {}定义前置通知
public void doReturnCheck() {}定义后置通知
public void doExceptionAction() {}定义例外通知
public void doReleaseAction() {}定义最终通知
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
return pjp.proceed();环绕通知
}
}
基于基于XML配置方式声明切面
<bean id="orderservice" class="cn.sdkd.service.OrderServiceBean"/>
<bean id="log" class="cn.sdkd.service.LogPrint"/>
<aop:config>
<aop:aspect id="myaop" ref="log">
<aop:pointcut id="mycut" expression="execution(* cn.sdkd.service..*.*(..))"/>
<aop:before pointcut-ref="mycut" method="doAccessCheck"/>
<aop:after-returning pointcut-ref="mycut" method="doReturnCheck "/>
<aop:after-throwing pointcut-ref="mycut" method="doExceptionAction"/>
<aop:after pointcut-ref="mycut" method=“doReleaseAction"/>
<aop:around pointcut-ref="mycut" method="doBasicProfiling"/>
</aop:aspect>
</aop:config>
Spring+JDBC组合开发
使用Spring+JDBC集成步骤如下:
配置数据源,如:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.gjt.mm.mysql.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/sdkd?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
.....略
</bean>
配置事务。配置事务时,需要在xml配置文件中引入用于声明事务的tx命名空间(见下页),事务的配置方式有两种:注解方式和基于XML配置方式。
在spring配置文件中引入用于声明事务的tx命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
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-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
</beans>
配置数据源
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.gjt.mm.mysql.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/sdkd?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<!-- 连接池启动时的初始值 -->
<property name="initialSize" value="1"/>
<!-- 连接池的最大值 -->
<property name="maxActive" value="500"/>
<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="maxIdle" value="2"/>
<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
<property name="minIdle" value="1"/>
</bean>
使用<context:property-placeholder location=“jdbc.properties”/>属性占位符
使用属性占位符方式配置数据源
<context:property-placeholder location=“jdbc.properties”/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<!-- 连接池启动时的初始值 -->
<property name="initialSize" value="${initialSize}"/>
<!-- 连接池的最大值 -->
<property name="maxActive" value="${maxActive}"/>
<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="maxIdle" value="${maxIdle}"/>
<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
<property name="minIdle" value="${minIdle}"/>
</bean>
采用注解方式配置事务
采用注解方式
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!–采用@Transactional注解方式使用事务 -->
<tx:annotation-driven transaction-manager="txManager"/>
@Service @Transactional
public class PersonServiceBean implements PersonService {
}
采用基于XML方式配置事务
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<aop:config>
<aop:pointcut id="transactionPointcut" expression="execution(* cn.sdkd.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointcut"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" propagation="NOT_SUPPORTED"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
使用JdbcTemplate进行insert/update/delete操作
@Service @Transactional
public class PersonServiceBean implements PersonService {
private JdbcTemplate jdbcTemplate;
@Resource
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
//添加
public void save(Person person) throws Exception{
jdbcTemplate.update("insert into person (name) values(?)",
new Object[]{person.getName()}, new int[]{java.sql.Types.VARCHAR});
}
}
使用JdbcTemplate获取一条记录
@Service @Transactional
public class PersonServiceBean implements PersonService {
private JdbcTemplate jdbcTemplate;
@Resource
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public Person getPerson(Integer id){
RowMapper rowMapper = new RowMapper(){
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Person person = new Person();
person.setId(rs.getInt("id"));
person.setName(rs.getString("name"));
return person;
}
};
return (Person)jdbcTemplate.queryForObject("select * from person where id=?",
new Object[]{id}, new int[]{java.sql.Types.INTEGER}, rowMapper);
}}
使用JdbcTemplate获取多条记录
@Service @Transactional
public class PersonServiceBean implements PersonService {
private JdbcTemplate jdbcTemplate;
@Resource
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<Person> getPersons(){
RowMapper rowMapper = new RowMapper(){
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Person person = new Person();
person.setId(rs.getInt("id"));
person.setName(rs.getString("name"));
return person;
}
};
return jdbcTemplate.query("select * from person", rowMapper);
}
}
事务传播属性
REQUIRED:业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务。
NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。
REQUIRESNEW:属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。
MANDATORY:该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。
SUPPORTS:这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。
Never:指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中.如果没有活动事务,则按REQUIRED属性执行.它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效
Connection conn = null;
try {
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
stmt.executeUpdate("update person set name='888' where id=1");
Savepoint savepoint = conn.setSavepoint();
try{
conn.createStatement().executeUpdate("update person set name='222' where sid=2");
}catch(Exception ex){
conn.rollback(savepoint);
}
stmt.executeUpdate("delete from person where id=9");
conn.commit();
stmt.close();
} catch (Exception e) {
conn.rollback();
}finally{
try {
if(null!=conn && !conn.isClosed()) conn.close();
} catch (SQLException e) { e.printStackTrace(); }
}
}
数据库系统提供了四种事务隔离级
数据库系统提供了四种事务隔离级别供用户选择。不同的隔离级别采用不同的锁类型来实现,在四种隔离级别中,Serializable的隔离级别最高,Read Uncommited的隔离级别最低。大多数据库默认的隔离级别为Read Commited,如SqlServer,当然也有少部分数据库默认的隔离级别为Repeatable Read,如Mysql
Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读)。
Read Commited:读已提交数据(会出现不可重复读和幻读)
Repeatable Read:可重复读(会出现幻读)
Serializable:串行化
脏读:一个事务读取到另一事务未提交的更新新据。
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。
幻读:一个事务读取到另一事务已提交的insert数据。
Spring2.5+Hibernate3.3+Struts1.3整合开发
hibernate核心安装包下的:
hibernate3.jar
lib\required\*.jar
lib\optional\ehcache-1.2.3.jar
hibernate 注解安装包下的
lib\test\slf4j-log4j12.jar
Spring安装包下的
dist\spring.jar
dist\modules\spring-webmvc-struts.jar
lib\jakarta-commons\commons-logging.jar、commons-dbcp.jar、commons-pool.jar
lib\aspectj\aspectjweaver.jar、aspectjrt.jar
lib\cglib\cglib-nodep-2.1_3.jar
lib\j2ee\common-annotations.jar
lib\log4j\log4j-1.2.15.jar
Struts
下载struts-1.3.8-lib.zip,需要使用到解压目录下的所有jar,建议把jstl-1.0.2.jar和standard-1.0.2.jar更换为1.1版本。Spring中已经存在一个antlr-2.7.6.jar,所以把struts中的antlr-2.7.2.jar删除,避免jar冲突。
数据库驱动jar
Spring2.5+Hibernate3.3+Struts1.3整合开发
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
..... 略 </bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>cn/sdkd/bean/Person.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.hbm2ddl.auto=update
hibernate.show_sql=false
hibernate.format_sql=false
</value>
</property>
</bean>
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
Spring2.5+Hibernate3.3+Struts1.3整合开发
实体bean配置模版.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.sdkd.bean">
<class name="Person" table="person">
<id name="id" type="integer" >
<generator class="native"/>
</id>
<property name="name" length="10" not-null="true"/>
</class>
</hibernate-mapping>
Spring2.5+Hibernate3.3+Struts1.3整合开发
Hibernate二级缓存的配置
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>cn/sdkd/bean/Person.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.hbm2ddl.auto=update
hibernate.show_sql=false
hibernate.format_sql=false
hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=false
hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider
</value>
</property>
</bean>
Spring2.5+Hibernate3.3+Struts1.3整合开发
在需要缓存的实体bean配置文件中加入缓存配置项
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.sdkd.bean">
<class name="Person" table="person">
<cache usage="read-write" region="cn.sdkd.bean.Person"/>
<id name="id">
<generator class="native"/>
</id>
<property name="name" length="10" not-null="true"/>
</class>
</hibernate-mapping>
usage说明了缓存的策略,region指定缓存的区域名
Spring2.5+Hibernate3.3+Struts1.3整合开发
Ehcache默认的配置文件ehcache.xml(放在类路径下)
<ehcache>
<diskStore path="D:\cache"/>
<defaultCache maxElementsInMemory="1000“ eternal="false“ overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="180"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="60"/>
<cache name="cn.sdkd.bean.Person" maxElementsInMemory="100" eternal="false"
overflowToDisk="true" timeToIdleSeconds="300" timeToLiveSeconds="600" diskPersistent="false"/>
</ehcache>
defaultCache节点为缺省的缓存策略
maxElementsInMemory内存中最大允许存在的对象数量
eternal 设置缓存中的对象是否永远不过期
overflowToDisk 把溢出的对象存放到硬盘上
timeToIdleSeconds指定缓存对象空闲多长时间就过期,过期的对象会被清除掉
timeToLiveSeconds指定缓存对象总的存活时间
diskPersistent 当jvm结束是是否持久化对象
diskExpiryThreadIntervalSeconds指定专门用于清除过期对象的监听线程的轮询时间
Spring2.5+Hibernate3.3+Struts1.3整合开发
在web容器中实例化spring容器,
<!-- 指定spring的配置文件,默认从web根目录寻找配置文件,我们可以通过spring提供的classpath:前缀指定从类路径下寻找 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
<!-- 对Spring容器进行实例化 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Spring2.5+Hibernate3.3+Struts1.3整合开发
在web容器中配置struts,
<servlet>
<servlet-name>struts</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>struts</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
Spring2.5+Hibernate3.3+Struts1.3整合开发
如果action没有交给spring管理时,我们通过下面语句获取spring容器实例
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(this.getServlet().getServletContext());
把action交给spring管理后,我们可以使用依赖注入在action中注入业务层的bean。确保action的path属性值与bean的名称相同。
<action path="/person/list" ...>
</action>
Spring 配置:
<bean name="/person/list" class="cn.sdkd.web.action.PersonAction"/>
在struts配置文件中添加进spring的请求控制器,该请法语控制器会先根据action的path属性值到spring容器中寻找跟该属性值同名的bean。如果寻找到即使用该bean处理用户请求
<controller>
<set-property property="processorClass" value="org.springframework.web.struts.DelegatingRequestProcessor"/>
</controller>
Spring2.5+Hibernate3.3+Struts1.3整合开发
org.springframework.web.struts.DelegatingRequestProcessor的处理过程。
假设用户请求的路径为:/person/list.do
Spring2.5+Hibernate3.3+Struts1.3整合开发
使用spring解决struts1.3乱码问题。
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Spring2.5+Hibernate3.3+Struts1.3整合开发
使用spring解决hibernate因session关闭导致的延迟加载例外问题。
<filter>
<filter-name>OpenSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>OpenSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Spring2.5+JPA+Struts1.3整合开发
这里JPA的实现采用hibernate,需要使用到下面的jar文件
Hiberante核心包(8个文件)
hibernate-distribution-3.3.1.GA
---------------------------------------------
hibernate3.jar
lib\bytecode\cglib\hibernate-cglib-repack-2.1_3.jar
lib\required\*.jar
Hiberante注解包(3个文件):hibernate-annotations-3.4.0.GA
------------------------------------------------------------------------------------
hibernate-annotations.jar
lib\ejb3-persistence.jar、hibernate-commons-annotations.jar
Hibernate针对JPA的实现包(3个文件):hibernate-entitymanager-3.4.0.GA
------------------------------------------------------------------------------------------------------
hibernate-entitymanager.jar
lib\test\log4j.jar、slf4j-log4j12.jar
Spring2.5+JPA+Struts1.3整合开发
JPA规范要求在类路径的META-INF目录下放置persistence.xml,文件的名称是固定的,配置模版如下:
<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="sdkd" transaction-type="RESOURCE_LOCAL">
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
<property name="hibernate.connection.driver_class" value="org.gjt.mm.mysql.Driver"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="123456"/>
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/sdkd?useUnicode=true&characterEncoding=UTF-8"/>
<property name="hibernate.max_fetch_depth" value="3"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
Spring2.5+JPA+Struts1.3整合开发
在spring中配置EntityManagerFactory
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="sdkd"/>
</bean>
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
Spring2.5+JPA+Struts1.3整合开发
使用spring解决JPA因EntityManager关闭导致的延迟加载例外问题。
<filter>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Spring2.5+Hibernate3.3+Struts2整合开发
使用到struts2的lib目录下所有不带-plugin结尾的jar文件,但除了struts2-spring-plugin-2.0.11.1.jar
Spring2.5+Hibernate3.3+Struts2整合开发
在web容器中实例化spring容器和配置struts2
<!-- 指定spring的配置文件,默认从web根目录寻找配置文件,我们可以通过spring提供的classpath:前缀指定从类路径下寻找 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
<!-- 对Spring容器进行实例化 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
配置struts2
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Spring2.5+Hibernate3.3+Struts2整合开发
struts2的配置文件模版struts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<!-- 该属性指定需要Struts 2处理的请求后缀,该属性的默认值是action,即所有匹配*.action的请求都由Struts2处理。
如果用户需要指定多个请求后缀,则多个后缀之间以英文逗号(,)隔开。 -->
<constant name="struts.action.extension" value="do"/>
<!-- 设置浏览器是否缓存静态内容,默认值为true(生产环境下使用),开发阶段最好关闭 -->
<constant name="struts.serve.static.browserCache" value="false"/>
<!-- 当struts的配置文件修改后,系统是否自动重新加载该文件,默认值为false(生产环境下使用),开发阶段最好打开 -->
<constant name="struts.configuration.xml.reload" value="true"/>
<!-- 开发模式下使用,这样可以打印出更详细的错误信息 -->
<constant name="struts.devMode" value="true" />
<!-- 默认的视图主题 -->
<constant name="struts.ui.theme" value="simple" />
<constant name="struts.objectFactory" value="spring" />
<package name="person" namespace="/person" extends="struts-default">
<global-results>
<result name="message">/WEB-INF/page/message.jsp</result>
</global-results>
<action name="action_*" class="personList" method="{1}">
<result name="list">/WEB-INF/page/persons.jsp</result>
<result name="add">/WEB-INF/page/add_person.jsp</result>
<result name="edit">/WEB-INF/page/edit_person.jsp</result>
</action>
</package>
</struts>
Spring2.5+Hibernate3.3+Struts2整合开发
struts2的标签
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib uri="/struts-tags" prefix="s" %>
<s:form action="action_edit" method="post" namespace="/person">
<s:hidden name="person.id"/>
姓名:<s:textfield name="person.name"/><br>
<input type="submit" value="发送"/>
</s:form>
<s:iterator value="persons" >
<s:property value="id"/>, <s:property value="name"/>
<a href='<s:url action="action_editUI" namespace="/person"><s:param name="person.id" value="id"/></s:url>'>修改</a>
</s:iterator>
相关推荐
以下是对"百知教育Spring详解笔记"的详细解读。 1. **控制反转(IoC)**:Spring通过IoC容器管理对象的生命周期和依赖关系,使得开发者不再需要在代码中手动创建和管理对象,而是由容器负责。这样提高了代码的可...
《Spring详解——Java全栈开发的关键》 Spring框架是Java企业级应用开发的基石,它以其灵活、高效和模块化的特性赢得了广大开发者的一致好评。本篇将深入探讨Spring的核心概念,包括IoC(Inversion of Control)...
### SSH—Spring详解 #### Spring框架概述 Spring框架是一款轻量级开源框架,专注于解决业务逻辑层和其他层次之间的松耦合问题。它采用面向接口的编程思想,贯穿整个应用程序,从而提高了系统的灵活性和可维护性。...
这个“spring详解(中文版)”压缩包很可能是包含了关于Spring框架的详细文档、教程或者案例分析,旨在帮助开发者深入理解和掌握Spring的核心概念和技术。 首先,让我们来了解一下Spring框架的基本组成部分: 1. *...
在这个“三大框架--Spring详解”中,我们可以深入理解Spring框架的主要组件和功能。 首先,Spring的核心是依赖注入(Dependency Injection,简称DI),这是一种设计模式,它允许我们解耦组件,使代码更加灵活和可...
标题 "开源框架spring详解-----AOP的深刻理解" 指向的是对Spring框架中核心概念之一的面向切面编程(Aspect Oriented Programming, AOP)的深入探讨。AOP是Spring用来解决横切关注点问题的一种编程模式,它允许...
spring 详解,介绍spring框架的由来。
Spring IOC aop 原理详解
《Spring详解教程》 Spring框架是Java开发中最广泛使用的轻量级开源框架之一,它以其依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Programming,AOP)为核心,极大地简化了企业级应用的...
Spring 框架详解与配置指南 Spring 是一个开源的 Java 应用程序框架,它以其依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Programming,AOP)为核心,为开发人员提供了丰富的功能,简化了...
Spring框架详解 Spring框架是Java开发中的一个核心组件,它为构建高质量、可维护和可测试的Java应用程序提供了全面的基础设施。Spring以其依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented ...
Spring 框架是Java开发中的核心组件,它以其强大的依赖注入(DI)和面向切面编程(AOP)功能而闻名。本文将深入探讨Spring的工作原理、AOP和IOC的概念,以及SSH整合的细节。此外,我们还将讨论Spring的事务管理机制...
### Spring 框架详解 #### 一、Spring 概述及特点 ##### 1.1 Spring 框架简介 - **Spring** 是一款开源的轻量级 Java 应用框架,主要用于简化企业级应用的开发过程。它提供了一个全面的编程模型,帮助开发者更加...
此“spring框架技术详解及使用指导”电子书PDF,旨在帮助读者深入理解Spring框架,并提供实践指导。 首先,让我们深入了解一下Spring框架的主要模块: 1. **核心容器**:这是Spring框架的基础,包括Bean工厂...
Spring 框架 Spring 是一个开源的容器框架,它主要提供了两种核心特性:Inversion of Control(控制反转,简称 IOC)和 Aspect-Oriented Programming(面向切面编程,简称 AOP)。Spring 框架为开发 Java 应用提供...
### Spring框架核心知识点详解 #### 一、Spring框架概述 **Spring** 是一个开源的、轻量级的Java开发框架,旨在解决企业级应用程序开发中的复杂性问题。它由Rod Johnson于2003年创建,并迅速成为Java开发领域中最...