1.Spring基本原理
1.1.spring概述
1.1.1.spring是什么
Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多 著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
1.1.2.spring的优势
方便解耦,简化开发
通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
AOP 编程的支持
通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
方便集成各种优秀框架
Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持。
降低 JavaEE API 的使用难度
Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
Java 源码是经典学习范例
Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
1.2.Spring IoC的作用
1.2.1.程序的耦合
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调 用模块的方式以及通过界面传送数据的多少。
模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。
耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。
划分模块的一个准则就是高内聚低耦合。
它有如下分类:
- 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
- 公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
- 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
- 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进 行适当的动作,这种耦合被称为控制耦合。
- 标记耦合 。若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间 存在一个标记耦合。
- 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
- 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
总结:
耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
内聚与耦合
内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。
内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。
程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。
内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
工厂模式解耦
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的 方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。
那么,这个读取配置文件,创建和获取三层对象的类就是工厂。
IoC的作用
削减计算机程序的耦合(解除我们代码中的依赖关系)。 控制反转是把创建的对象的权利交给框架,是框架的重要的特征。它包括依赖注入和依赖查找
1.2.2.工厂模式模拟实现IoC
- 配置文件beans.properties
accountService=com.itheima.service.impl.AccountServiceImpl
accountDao=com.itheima.dao.impl.AccountDaoImpl
- 工厂类BeanFactory
/**
* 一个创建Bean对象的工厂
*
* Bean:在计算机英语中,有可重用组件的含义。
* JavaBean:用java语言编写的可重用组件。
* javabean > 实体类
*
* 它就是创建我们的service和dao对象的。
*
* 第一个:需要一个配置文件来配置我们的service和dao
* 配置的内容:唯一标识=全限定类名(key=value)
* 第二个:通过读取配置文件中配置的内容,反射创建对象
*
* 我的配置文件可以是xml也可以是properties
*/
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String,Object>();
//取出配置文件中所有的Key
Enumeration keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个Key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key,value);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据bean的名称获取对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
/**
* 根据Bean的名称获取bean对象
* @param beanName
* @return
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = props.getProperty(beanName);
// System.out.println(beanPath);
bean = Class.forName(beanPath).newInstance();//每次都会调用默认构造函数创建对象
}catch (Exception e){
e.printStackTrace();
}
return bean;
}*/
}
1.3.Spring入门
- 引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
- 配置文件
<!--把对象的创建交给spring来管理-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
- 读取配置文件
获取spring的Ioc核心容器,并根据id获取对象
ApplicationContext的三个常用实现类:
- ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
- FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
- AnnotationConfigApplicationContext:它是用于读取注解创建容器的,是明天的内容。
核心容器的两个接口引发出的问题:
- ApplicationContext: 单例对象适用 采用此接口
它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。- BeanFactory: 多例对象使用
它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。
/**
* 模拟一个表现层,用于调用业务层
*/
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// ApplicationContext ac = new FileSystemXmlApplicationContext("C:\Users\zhy\Desktop\bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
System.out.println(as);
System.out.println(adao);
as.saveAccount();
//--------BeanFactory----------
// Resource resource = new ClassPathResource("bean.xml");
// BeanFactory factory = new XmlBeanFactory(resource);
// IAccountService as = (IAccountService)factory.getBean("accountService");
// System.out.println(as);
}
}
1.4.Spring Bean的配置
把对象的创建交给spring来管理
spring对bean的管理细节
- 创建bean的三种方式
- bean对象的作用范围
- bean对象的生命周期
创建Bean的三种方式
使用默认构造函数创建
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
使用普通工厂中的方法创建对象
使用某个类中的方法创建对象,并存入spring容器
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean> <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
使用工厂中的静态方法创建对象
使用某个类中的静态方法创建对象,并存入spring容器
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
Bean的作用范围调整
bean标签的scope属性,作用:用于指定bean的作用范围,取值: 常用的就是单例的和多例的。
- singleton:单例的(默认值)
- prototype:多例的
- request:作用于web应用的请求范围
- session:作用于web应用的会话范围
- global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>
bean对象的生命周期
- 单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同 - 多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着。
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype" init-method="init" destroy-method="destroy"></bean>
1.5.Spring依赖注入
依赖注入:Dependency Injection
依赖关系的管理以后都交给spring来维护
在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明,依赖关系的维护,就称之为依赖注入。
能注入的数据
- 基本类型和String
- 其他bean类型(在配置文件中或者注解配置过的bean)
- 复杂类型/集合类型
注入的方式
- 使用构造函数提供
- 使用set方法提供
- 使用注解提供(明天的内容)
构造函数注入
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
name:用于指定给构造函数中指定名称的参数赋值(常用的)
以上三个用于指定给构造函数中哪个参数赋值
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
弊端:
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <constructor-arg name="name" value="泰斯特"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> <constructor-arg name="birthday" ref="now"></constructor-arg></bean><!-- 配置一个日期对象 --><bean id="now" class="java.util.Date"></bean>
set方法注入
涉及的标签:property
出现的位置:bean标签的内部
标签的属性
name:用于指定注入时所调用的set方法名称
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2"> <property name="name" value="TEST" ></property> <property name="age" value="21"></property> <property name="birthday" ref="now"></property></bean>
复杂类型的注入/集合类型的注入
用于给List结构集合注入的标签:
list
array
set
用于个Map结构集合注入的标签:
map
props
结构相同,标签可以互换
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3"> <property name="myStrs"> <set> <value>AAA</value> <value>BBB</value> <value>CCC</value> </set> </property> <property name="myList"> <array> <value>AAA</value> <value>BBB</value> <value>CCC</value> </array> </property> <property name="mySet"> <list> <value>AAA</value> <value>BBB</value> <value>CCC</value> </list> </property> <property name="myMap"> <props> <prop key="testC">ccc</prop> <prop key="testD">ddd</prop> </props> </property> <property name="myProps"> <map> <entry key="testA" value="aaa"></entry> <entry key="testB"> <value>BBB</value> </entry> </map> </property></bean>
1.6.Spring注解配置
配置文件
告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为context
名称空间和约束中
<context:component-scan base-package="com.itheima"></context:component-scan>
注解配置
-
用于创建对象的
他们的作用就和在XML配置文件中编写一个
<bean>
标签实现的功能是一样的
Component
作用:用于把当前类对象存入spring容器中
属性:
- value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
Controller
:一般用在表现层/Service
:一般用在业务层/Repository
:一般用在持久层以上三个注解他们的作用和属性与Component是一模一样。他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
- 用于注入数据的
他们的作用就和在xml配置文件中的bean标签中写一个<property>
标签的作用是一样的
Autowired
作用:
自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
如果Ioc容器中有多个类型匹配时:
在使用注解注入时,set方法就不是必须的了。
可以是变量上,也可以是方法上
Qualifier
作用:
- 在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以
属性: value:用于指定注入bean的id。
Resource
作用:
- 直接按照bean的id注入。它可以独立使用
属性:name:用于指定bean的id。
以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过XML来实现。
Value
作用:
- 用于注入基本类型和String类型的数据
属性:
- value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)SpEL的写法:${表达式}
- 用于改变作用范围的
他们的作用就和在bean标签中使用scope属性实现的功能是一样的
Scope
作用:用于指定bean的作用范围
属性:
value:指定范围的取值。
常用取值:singleton prototype
- 和生命周期相关了解
他们的作用就和在bean
标签中使用init-method
和destroy-methode
的作用是一样的
PreDestroy
作用:用于指定销毁方法
PostConstruct
作用:用于指定初始化方法
@Service("accountService")//@Scope("prototype")public class AccountServiceImpl implements IAccountService {// @Autowired// @Qualifier("accountDao1") @Resource(name = "accountDao2") private IAccountDao accountDao = null; @PostConstruct public void init(){ System.out.println("初始化方法执行了"); } @PreDestroy public void destroy(){ System.out.println("销毁方法执行了"); } public void saveAccount(){ accountDao.saveAccount(); }}
- 读取配置
public static void main(String[] args) { //1.获取核心容器对象// ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取Bean对象 IAccountService as = (IAccountService)ac.getBean("accountService");// IAccountService as2 = (IAccountService)ac.getBean("accountService");// System.out.println(as);// IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);// System.out.println(adao);// System.out.println(as == as2); as.saveAccount(); ac.close(); }
1.7.Spring常规使用
- 配置文件
<!-- 配置Service --><bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property></bean><!--配置Dao对象--><bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property></bean><!--配置QueryRunner--><bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--注入数据源--> <constructor-arg name="ds" ref="dataSource"></constructor-arg></bean><!-- 配置数据源 --><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="user" value="root"></property> <property name="password" value="1234"></property></bean>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version></dependency><dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.4</version></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version></dependency><dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version></dependency><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version></dependency>
1.8.纯注解配置
1.8.1.配置类
- Configuration
作用:指定当前类是一个配置类
细节:当配置类作为AnnotationConfigApplicationContext
对象创建的参数时,该注解可以不写。
-
ComponentScan
作用:用于通过注解指定spring在创建容器时要扫描的包
属性:
value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。我们使用此注解就等同于在xml中配置了:
<context:component-scan base-package="com.itheima"></context:component-scan>
-
Bean
作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:
name:用于指定bean的id。当不写时,默认值是当前方法的名称
细节:当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。查找的方式和Autowired注解的作用是一样的 -
import
作用:用于导入其他的配置类
属性:
value:用于指定其他配置类的字节码。
当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类 -
PropertySource
作用:用于指定properties文件的位置
属性:
value:指定文件的名称和路径。
关键字:classpath,表示类路径下
//@Configuration@ComponentScan("com.itheima")@Import(JdbcConfig.class)@PropertySource("classpath:jdbcConfig.properties")public class SpringConfiguration {}/** * 和spring连接数据库相关的配置类 */public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** * 用于创建一个QueryRunner对象 * @param dataSource * @return */ @Bean(name="runner") @Scope("prototype") public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){ return new QueryRunner(dataSource); } /** * 创建数据源对象 * @return */ @Bean(name="ds2") public DataSource createDataSource(){ try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(username); ds.setPassword(password); return ds; }catch (Exception e){ throw new RuntimeException(e); } } @Bean(name="ds1") public DataSource createDataSource1(){ try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02"); ds.setUser(username); ds.setPassword(password); return ds; }catch (Exception e){ throw new RuntimeException(e); } }}
1.9.Spring整合junit
使用Junit单元测试:测试我们的配置
Spring整合junit的配置
1、导入spring整合junit的jar(坐标)
2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的@Runwith
3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置@ContextConfiguration
locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在地位置 当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = SpringConfiguration.class)public class AccountServiceTest { @Autowired private IAccountService as = null; @Test public void testFindAll() { //3.执行方法 List<Account> accounts = as.findAllAccount(); for(Account account : accounts){ System.out.println(account); } } @Test public void testFindOne() { //3.执行方法 Account account = as.findAccountById(1); System.out.println(account); } @Test public void testSave() { Account account = new Account(); account.setName("test anno"); account.setMoney(12345f); //3.执行方法 as.saveAccount(account); } @Test public void testUpdate() { //3.执行方法 Account account = as.findAccountById(4); account.setMoney(23456f); as.updateAccount(account); } @Test public void testDelete() { //3.执行方法 as.deleteAccount(4); }}
2.Spring AOP
2.1.Spring事务支持
-
依赖包引入
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version></dependency><dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.4</version></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version></dependency><dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version></dependency><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version></dependency>
-
事务代码编写
/** * 用于创建Service的代理对象的工厂 */public class BeanFactory { private IAccountService accountService; private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } public final void setAccountService(IAccountService accountService) { this.accountService = accountService; } /** * 获取Service代理对象 * @return */ public IAccountService getAccountService() { return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { /** * 添加事务的支持 * * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("test".equals(method.getName())){ return method.invoke(accountService,args); } Object rtValue = null; try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 rtValue = method.invoke(accountService, args); //3.提交事务 txManager.commit(); //4.返回结果 return rtValue; } catch (Exception e) { //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); } finally { //6.释放连接 txManager.release(); } } }); }}/** * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定 */public class ConnectionUtils { private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * 获取当前线程上的连接 * @return */ public Connection getThreadConnection() { try{ //1.先从ThreadLocal上获取 Connection conn = tl.get(); //2.判断当前线程上是否有连接 if (conn == null) { //3.从数据源中获取一个连接,并且存入ThreadLocal中 conn = dataSource.getConnection(); tl.set(conn); } //4.返回当前线程上的连接 return conn; }catch (Exception e){ throw new RuntimeException(e); } } /** * 把连接和线程解绑 */ public void removeConnection(){ tl.remove(); }}/** * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接 */public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 开启事务 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); }catch (Exception e){ e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 释放连接 */ public void release(){ try { connectionUtils.getThreadConnection().close();//还回连接池中 connectionUtils.removeConnection(); }catch (Exception e){ e.printStackTrace(); } }}
-
bean.xml
<!--配置代理的service--><bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean><!--配置beanfactory--><bean id="beanFactory" class="com.itheima.factory.BeanFactory"> <!-- 注入service --> <property name="accountService" ref="accountService"></property> <!-- 注入事务管理器 --> <property name="txManager" ref="txManager"></property></bean> <!-- 配置Service --><bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property></bean><!--配置Dao对象--><bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property></bean><!--配置QueryRunner--><bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean><!-- 配置数据源 --><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="user" value="root"></property> <property name="password" value="1234"></property></bean><!-- 配置Connection的工具类 ConnectionUtils --><bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils"> <!-- 注入数据源--> <property name="dataSource" ref="dataSource"></property></bean><!-- 配置事务管理器--><bean id="txManager" class="com.itheima.utils.TransactionManager"> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property></bean>
2.2.Spring AOP概述
AOP:面向切面编程,简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的 基础上,对我们的已有方法进行增强。
作用:在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码
提高开发效率
维护方便
AOP相关术语
- Joinpoint(连接点)
所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
- Pointcut(切入点)
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
- Advice(通知/增强)
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
- Introduction(引介)
引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
- Target(目标对象)
代理的目标对象。
- Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
- Proxy(代理)
一个类被 AOP 织入增强后,就产生一个结果代理类。
- Aspect(切面)
是切入点和通知(引介)的结合。
2.2.1.AOP使用
spring中基于XML的AOP配置步骤
1、把通知Bean也交给spring来管理
2、使用aop:config标签表明开始AOP的配置
3、使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识 ref属性:是指定通知类bean的Id。
4、在aop:aspect标签的内部使用对应标签来配置通知的类型
我们现在示例是让printLog方法在切入点方法执行之前:所以是前置通知
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强切入点表达式的写法:关键字:execution(表达式)表达式: 访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)标准的表达式写法: public void com.itheima.service.impl.AccountServiceImpl.saveAccount()访问修饰符可以省略 void com.itheima.service.impl.AccountServiceImpl.saveAccount()返回值可以使用通配符,表示任意返回值 * com.itheima.service.impl.AccountServiceImpl.saveAccount() 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*. * *.*.*.*.AccountServiceImpl.saveAccount()) 包名可以使用..表示当前包及其子包 * *..AccountServiceImpl.saveAccount() 类名和方法名都可以使用*来实现通配 * *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型全通配写法:
* *..*.*(..)实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.itheima.service.impl.*.*(..)
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean><!-- 配置Logger类 --><bean id="logger" class="com.itheima.utils.Logger"></bean><!--配置AOP--><aop:config> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联--> <aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before> </aop:aspect></aop:config>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency>
2.2.2.xml配置AOP
<!-- 配置srping的Ioc,把service对象配置进来--><bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean><!-- 配置Logger类 --><bean id="logger" class="com.itheima.utils.Logger"></bean><!--配置AOP--><aop:config> <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容 此标签写在aop:aspect标签内部只能当前切面使用。 它还可以写在aop:aspect外面,此时就变成了所有切面可用 --> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置前置通知:在切入点方法执行之前执行 <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>--> <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个 <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>--> <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个 <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>--> <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行 <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>--> <!-- 配置环绕通知 详细的注释请看Logger类中--> <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around> </aop:aspect></aop:config>
/** * 用于记录日志的工具类,它里面提供了公共的代码 */public class Logger { /** * 前置通知 */ public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。"); } /** * 后置通知 */ public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */ public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */ public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。"); } /** * 环绕通知 * 问题: * 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 * 分析: * 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决: * Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 * * spring中的环绕通知: * 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 */ public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } }}
2.2.3.注解配置AOP
1、xml配置文件
<!-- 配置spring创建容器时要扫描的包--><context:component-scan base-package="com.itheima"></context:component-scan><!-- 配置spring开启注解AOP的支持 --><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2、注解配置
** * 用于记录日志的工具类,它里面提供了公共的代码 */@Component("logger")@Aspect//表示当前类是一个切面类public class Logger { @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){} /** * 前置通知 */// @Before("pt1()") public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。"); } /** * 后置通知 */// @AfterReturning("pt1()") public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */// @AfterThrowing("pt1()") public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */// @After("pt1()") public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。"); } /** * 环绕通知 * 问题: * 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 * 分析: * 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决: * Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 * * spring中的环绕通知: * 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 */ @Around("pt1()") public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } }}
3.Spring JdbcTemplate的使用
3.1.基本使用
//准备数据源:spring的内置数据源DriverManagerDataSource ds = new DriverManagerDataSource();ds.setDriverClassName("com.mysql.jdbc.Driver");ds.setUrl("jdbc:mysql://localhost:3306/eesy");ds.setUsername("root");ds.setPassword("1234");//1.创建JdbcTemplate对象JdbcTemplate jt = new JdbcTemplate();//给jt设置数据源jt.setDataSource(ds);//2.执行操作jt.execute("insert into account(name,money)values('ccc',1000)");
3.2.依赖包
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency></dependencies>
3.3.配置文件
<!-- 配置账户的持久层--><bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <!--<property name="jdbcTemplate" ref="jdbcTemplate"></property>--> <property name="dataSource" ref="dataSource"></property></bean><!--配置JdbcTemplate<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property></bean>--><!-- 配置数据源--><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="username" value="root"></property> <property name="password" value="1234"></property></bean>
3.4.jdbcTemplate的CRUD操作
//1.获取容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.获取对象 JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class); //3.执行操作 //保存// jt.update("insert into account(name,money)values(?,?)","eee",3333f); //更新// jt.update("update account set name=?,money=? where id=?","test",4567,7); //删除// jt.update("delete from account where id=?",8); //查询所有// List<Account> accounts = jt.query("select * from account where money > ?",new AccountRowMapper(),1000f);// List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f);// for(Account account : accounts){// System.out.println(account);// } //查询一个// List<Account> accounts = jt.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),1);// System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0)); //查询返回一行一列(使用聚合函数,但不加group by子句) Long count = jt.queryForObject("select count(*) from account where money > ?",Long.class,1000f); System.out.println(count);
3.5.结果集自定义封装策略
/** * 定义Account的封装策略 */class AccountRowMapper implements RowMapper<Account>{ /** * 把结果集中的数据封装到Account中,然后由spring把每个Account加到集合中 * @param rs * @param rowNum * @return * @throws SQLException */ @Override public Account mapRow(ResultSet rs, int rowNum) throws SQLException { Account account = new Account(); account.setId(rs.getInt("id")); account.setName(rs.getString("name")); account.setMoney(rs.getFloat("money")); return account; }}
3.6.AOP事务控制
<!-- 配置事务管理器--><bean id="txManager" class="com.itheima.utils.TransactionManager"> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property></bean><!--配置aop--><aop:config> <!--配置通用切入点表达式--> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <aop:aspect id="txAdvice" ref="txManager"> <!--配置前置通知:开启事务--> <aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before> <!--配置后置通知:提交事务--> <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning> <!--配置异常通知:回滚事务--> <aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing> <!--配置最终通知:释放连接--> <aop:after method="release" pointcut-ref="pt1"></aop:after> </aop:aspect></aop:config>
4.Spring事务
4.1.基于XML
spring中基于XML的声明式事务控制配置步骤
1、配置事务管理器
2、配置事务的通知
此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
3、配置AOP中的通用切入点表达式
4、建立事务通知和切入点表达式的对应关系
5、配置事务的属性
是在事务的通知tx:advice标签的内部
<!-- 配置业务层--><bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property></bean><!-- 配置账户的持久层--><bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property></bean><!-- 配置数据源--><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="username" value="root"></property> <property name="password" value="1234"></property></bean><!-- spring中基于XML的声明式事务控制配置步骤 1、配置事务管理器 2、配置事务的通知 此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的 使用tx:advice标签配置事务通知 属性: id:给事务通知起一个唯一标识 transaction-manager:给事务通知提供一个事务管理器引用 3、配置AOP中的通用切入点表达式 4、建立事务通知和切入点表达式的对应关系 5、配置事务的属性 是在事务的通知tx:advice标签的内部 --><!-- 配置事务管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property></bean><!-- 配置事务的通知--><tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 配置事务的属性 isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。 propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。 read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。 timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。 rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。 --> <tx:attributes> <tx:method name="*" propagation="REQUIRED" read-only="false"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method> </tx:attributes></tx:advice><!-- 配置aop--><aop:config> <!-- 配置切入点表达式--> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--建立切入点表达式和事务通知的对应关系 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor></aop:config>
4.2.基于注解配置
xml配置
<!-- 配置spring创建容器时要扫描的包--><context:component-scan base-package="com.itheima"></context:component-scan><!-- 配置JdbcTemplate--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property></bean><!-- 配置数据源--><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="username" value="root"></property> <property name="password" value="1234"></property></bean><!-- spring中基于注解 的声明式事务控制配置步骤 1、配置事务管理器 2、开启spring对注解事务的支持 3、在需要事务支持的地方使用@Transactional注解 --><!-- 配置事务管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property></bean><!-- 开启spring对注解事务的支持--><tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
注解配置
@Service("accountService")@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)//只读型事务的配置public class AccountServiceImpl implements IAccountService{
5.JDK性能测试
/循环次数定义:10亿次private static final int loopCnt = 1000 * 1000 * 1000;public static void main(String[] args) throws Exception { //输出jdk的版本 System.out.println("java.version=" + System.getProperty("java.version")); t1(); t2(); t3();}// 每次重新生成对象public static void t1() { long s = System.currentTimeMillis(); for (int i = 0; i < loopCnt; i++) { Person p = new Person(); p.setAge(31); } long e = System.currentTimeMillis(); System.out.println("循环10亿次创建对象的时间:" + (e - s));}// 同一个对象public static void t2() { long s = System.currentTimeMillis(); Person p = new Person(); for (int i = 0; i < loopCnt; i++) { p.setAge(32); } long e = System.currentTimeMillis(); System.out.println("循环10亿次给同一对象赋值的时间: " + (e - s));}//使用反射创建对象public static void t3() throws Exception { long s = System.currentTimeMillis(); Class<Person> c = Person.class; Person p = c.newInstance(); Method m = c.getMethod("setAge", Integer.class); for (int i = 0; i < loopCnt; i++) { m.invoke(p, 33); } long e = System.currentTimeMillis(); System.out.println("循环10亿次反射创建对象的时间:" + (e - s));}static class Person { private int age = 20; public int getAge() { return age; } public void setAge(Integer age) { this.age = age; }}