事务之五:Spring @Transactional工作原理
javax.inject
包 javax.inject 指定了获取对象的一种方法,该方法与构造器、工厂以及服务定位器(例如 JNDI))这些传统方法相比可以获得更好的可重用性、可测试性以及可维护性。此方法的处理过程就是大家熟知的依赖注入,它对于大多数应用是非常有价值的。
javax.inject包里的几个类:

在我们的程序中,很多类型依赖于其他类型。例如,一个 Stopwatch 可能依赖于一个 TimeSource。一些类型被另一个类型依赖,我们就把这些类型叫做这个类型的依赖(物)。在运行时查找一个依赖实例的过程叫做解析依赖。如果找不到依赖的实例,那我们称该依赖为不能满足的,并导致应用运行失败。
在不使用依赖注入时,对象的依赖解析有几种方式。最常见的就是通过编写直接调用构造器的代码完成:
class Stopwatch {
final TimeSource timeSource;
Stopwatch () {
timeSource = new TimeSource(…);
}
void start() { … }
long stop() { … }
}
如果需要更有弹性一点,那么我们可以通过工厂或服务定位器实现:
class Stopwatch {
final TimeSource timeSource;
Stopwatch () {
timeSource = DefaultTimeSource.getInstance();
}
void start() { … }
long stop() { … }
}
在使用这些传统方式进行依赖解析时,程序员必须做出适当权衡。构造器非常简洁,但却有一些限制(对象生存期,对象复用)。工厂确实解耦了客户与实现,但却需要样本式的代码。服务定位器更进一步地解耦了客户与实现,但却降低了编译时的类型安全。并且,这三个方式都不适合进行单元测试。例如,当程序员使用工厂时,该工厂的每一个产品都必须模拟出来,测试完后还要得记得清理:
void testStopwatch() {
TimeSource original = DefaultTimeSource.getInstance();
DefaultTimeSource.setInstance(new MockTimeSource());
try {
// Now, we can actually test Stopwatch.
Stopwatch sw = new Stopwatch();
//…
} finally {
DefaultTimeSource.setInstance(original);
}
}
现实中,要模拟工厂将导致更多的样本式代码。测试模拟出的产品并清理它们在依赖多的情况下很快就控制不了了。更糟的是,程序员必须精确地预测未来到底需要多少这样的弹性,并为他做的“弹性选择”负责。如果程序员开始时选择了构造器方式,但后来需要一个更有弹性的方式,那他就不得不替换所有调用构造器的代码。如果程序员一开始过于谨慎地选择了工厂方式,结果可能导致要编写很多额外的样本式代码,引入了不必要的复杂度,潜在的问题比比皆是。
依赖注入就是为了解决这些问题。代替程序员调用构造器或工厂,一个称作依赖注入器的工具将把依赖传递给对象:
class Stopwatch {
final TimeSource timeSource;
@Inject Stopwatch(TimeSource timeSource) {
this.TimeSource = timeSource;
}
void start() { … }
long stop() { … }
}
注入器将更进一步地传递依赖给其他的依赖,直到它构造出整个对象图。例如,假设一个程序员需要注入器创建一个 StopwatchWidget 实例:
/** GUI for a Stopwatch */
class StopwatchWidget { @Inject StopwatchWidget(Stopwatch sw) { … } … }
注入器可能会:
查找一个 TimeSource 实例
使用找到的 TimeSource 实例构造一个 Stopwatch
使用构造的 Stopwatch 实例构造一个 StopwatchWidget
这使得代码保持干净,使得程序员感到使用依赖(物)的基础设施非常容易。
现在,在单元测试中,程序员可以直接构造对象(不使用注入器)并将该对象以模拟依赖的方式直接传入待测对象的构造中。程序员再也不需要为每一次测试都配置并清理工厂或服务定位器。这大大简化了我们的单元测试:
void testStopwatch() {
Stopwatch sw = new Stopwatch(new MockTimeSource());
//…
}
完全降低了单元测试的复杂度,降低的复杂程度与待测对象的数目及其依赖成正比。
包 javax.inject 为使用这样的轻便类提供了依赖注入注解,但没有引入依赖配置方式。依赖配置方式取决于注入器的实现。程序员只需要标注了构造器、方法或字段来说明它们的可注入性(上面的例子就是构造器注入)。依赖注入器通过这些注解来识别一个类的依赖,并在运行时注入这些依赖。此外,注入器要能够在构建时验证所有的依赖是否满足。相比之下,服务定位器在构建时是不能检测到依赖不满足情况的,直到运行时才能发现。
注入器实现有很多形式。一个注入器可以通过 XML、注解、DSL(领域规约语言),或是普通 Java代码来进行配置。注入器实现可以使用反射或代码生成。使用编译时代码生成的注入器甚至可能没有它自己的运行时描述。而其他注入器实现无论在编译时还是运行时可能都不使用代码生成。一个“容器”,其实可以把它定义为一个注入器,不过包 javax.inject不涉及非常大概念,旨在最小化注入器实现的限制。
@Inject支持构造函数、方法和字段注解,也可能使用于静态实例成员。可注解成员可以是任意修饰符(private,package-private,protected,public)。注入顺序:构造函数、字段,然后是方法。父类的字段和方法注入优先于子类的字段和方法,同一类中的字段和方法是没有顺序的。
@Inject注解的构造函数可以是无参或多个参数的构造函数。@Inject每个类中最多注解一个构造函数。
在字段注解:
用@Inject注解
字段不能是final的
拥有一个合法的名称
在方法上注解:
用@Inject注解
不能是抽象方法
不能声明自身参数类型
可以有返回结果
拥有一个合法的名称
可以有0个或多个参数
@Inject MethodModirers ResultType Identifier(FormalParameterList ) Throws MethodBody
[上述翻译:inject的doc文档,翻译不好敬请谅解]
构造函数注解:
@Inject
public House(Person owner) {
System.out.println("---这是房屋构造函数---");
this.owner = owner;
}
字段注解:
@Inject private Person owner;
方法注解:
@Inject
public void setOwner(Person owner) {
this.owner = owner;
}
@Inject注解和Spring的@Autoware注解都是根据类型对其进行自动装配。
SpringUtil类:
public class SpringUtil {
private static ApplicationContext context = null;
public static ApplicationContext getApplicationContext() {
if (context == null) {
context = new ClassPathXmlApplicationContext("spring.xml");
}
return context;
}
public static ApplicationContext getApplicationContext(String path) {
return new ClassPathXmlApplicationContext(path);
}
public static ApplicationContext getAnnotationConfigApplicationContext(String basePackages) {
return new AnnotationConfigApplicationContext(basePackages);
}
}
Person类:
import javax.inject.Named;
@Named
public class Person {
private String name;
public Person() {
System.out.println("---这是人的构造函数---");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
House类:
@Named
public class House {
@Inject private Person owner;
public House() {
System.out.println("---这是房屋构造函数---");
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
}
测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext context = SpringUtil.getApplicationContext(
"test/spring/inject/bean-inject.xml");
House house = (House)context.getBean("house");
Person p = house.getOwner();
p.setName("张三");
System.out.println(house.getOwner().getName());
}
}
输出结果:
---这是房屋构造函数---
---这是人的构造函数---
张三
上述例子在Spring3.1下测试成功,在Spring3.1下,每个构造函数只初始化一次及默认的单例形式,个人感觉如果脱离Spring环境应该每次用都会实例化新的对象,当然根据实现的jar包不同而不同,要不javax.inject下的@Singleton注解就没有什么用途了。
@Named
@Named和Spring的@Component功能相同。@Named可以有值,如果没有值生成的Bean名称默认和类名相同。
例如:
@Named public class Person
该bean的名称就是person。
@Named("p") public class Person
如果指定名称,那么就是指定的名称喽。
@Qualifier
任何人都可以定义一个新的修饰语,一个qualifier注解应该满足如下条件:
定义的注解类有@Qualifier,@Retention(RUNTIME)和@Documented。
可以有属性
可以是公共API的一部分
可以用@Target注解限定使用范围
下面是Qualifier的例子:
Genre注解类:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
@Target(value = {ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
public @interface Genre {
User user() default User.STUDENT;
public enum User {STUDENT, TEACHER}
}
用户接口:(对个数进行统计)
public interface IUserDAO {
int count();
}
StudentDAO:
@Named
@Genre(user = User.STUDENT)
public class StudentDAO implements IUserDAO{
@Override
public int count() {
System.out.println("----StudentDAO----");
return 0;
}
}
TeacherDAO:
@Named
@Genre(user = User.TEACHER)
public class TeacherDAO implements IUserDAO {
@Override
public int count() {
System.out.println("--TeacherDAO--");
return 0;
}
}
UserDAOProcessor:
@Named
public class UserDAOProcessor {
/*对TeacherDAO类的注入,如果对StudentDAO类注入应该是:@Genre(user = User.STUDENT)或@Genre,因为@Genre默认的是STUDENT*/
@Inject
private @Genre(user = User.TEACHER) IUserDAO userDAO;
public int count() {
return userDAO.count();
}
public IUserDAO getUserDAO() {
return userDAO;
}
public void setUserDAO(IUserDAO userDAO) {
this.userDAO = userDAO;
}
}
测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext context = SpringUtil.getApplicationContext(
"test/spring/inject/bean-inject.xml");
UserDAOProcessor processor = (UserDAOProcessor)context.getBean("userDAOProcessor");
System.out.println(processor.count());
}
}
输出结果:
--TeacherDAO--
0
个人对@Qualifier的理解:
和Spring的@Qualifier大致相同
单独用@Inject无法满足对接口的注入,无法找到哪个具体类,所以用@Qualifier来确定注入的具体类
用到@Qualifier的注解中可以有值、无值和用枚举类型
@Singleton
使用该注解标记该类只创建一次,不能被继承。一般在类上用该注解。
事务之五:Spring @Transactional工作原理的更多相关文章
- Java:Spring @Transactional工作原理
本文将深入研究Spring的事务管理.主要介绍@Transactional在底层是如何工作的.之后的文章将介绍: propagation(事务传播)和isolation(隔离性)等属性的使用 事务使用 ...
- 25.Spring @Transactional工作原理
转自:http://www.importnew.com/12300.html 本文将深入研究Spring的事务管理.主要介绍@Transactional在底层是如何工作的.之后的文章将介绍: prop ...
- Spring MVC工作原理(好用版)
Spring MVC工作原理 参考: SpringMVC工作原理 - 平凡希 - 博客园https://www.cnblogs.com/xiaoxi/p/6164383.html SpringMVC的 ...
- Spring Session工作原理
本文首发于 vivo互联网技术 微信公众号 https://mp.weixin.qq.com/s/KCOFv0nRuymkX79-RZi9eg 作者:张正林 目录:1.引入背景2.使用方法3.工作流程 ...
- Struts1、Struts2、Hibernate、Spring框架工作原理介绍
Struts1工作原理 Struts1工作原理图 1.初始化:struts框架的总控制器ActionServlet是一个Servlet,它在web.xml中配置成自动启动的Servlet,在启动时总控 ...
- Spring MVC工作原理及源码解析(三) HandlerMapping和HandlerAdapter实现原理及源码解析
1.HandlerMapping实现原理及源码解析 在前面讲解Spring MVC工作流程的时候我们说过,前端控制器收到请求后会调⽤处理器映射器(HandlerMapping),处理器映射器根据请求U ...
- Spring MVC工作原理及源码解析(一) MVC原理介绍、与IOC容器整合原理
MVC原理介绍 Spring MVC原理图 上图是Spring MVC工作原理图(图片来自网上搜索),根据上图,我们可以得知Spring MVC的工作流程如下: 1.用户(客户端,即浏览器)发送请求至 ...
- “Ceph浅析”系列之五——Ceph的工作原理及流程
本文将对Ceph的工作原理和若干关键工作流程进行扼要介绍.如前所述,由于Ceph的功能实现本质上依托于RADOS,因而,此处的介绍事实上也是针对RADOS进行.对于上层的部分,特别是RADOS GW和 ...
- Spring MVC工作原理 及注解说明
SpringMVC框架介绍 1) spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面. Spring 框架提供了构建 Web 应用程序的全功 ...
随机推荐
- Orthogonal Least Squares Learning Algorithm for Radial Basis Function Networks
Orthogonal Least Squares Learning Algorithm for Radial Basis Function Networks S. Chen, C. F. N. Cow ...
- 【译】理解Spring MVC Model Attribute 和 Session Attribute
作为一名 Java Web 应用开发者,你已经快速学习了 request(HttpServletRequest)和 session(HttpSession)作用域.在设计和构建 Java Web 应用 ...
- linux 10 -Bash Shell编程
二十三. Bash Shell编程: 1. 读取用户变量: read命令是用于从终端或者文件中读取输入的内建命令,read命令读取整行输入,每行末尾的换行符不被读入.在read命令后 ...
- ARDUINO使用GPRS发送GPS数据到OneNet测试
功能: 测试把固定的GPS数据发送到OneNet平台 调试途中碰到的问题 ARDUINO不支持sprintf的double打印,只能转换为char字符串然后再%s打印 #include <Tim ...
- mssql-在一个特定的会话停止出发器
用SET CONTEXT_INFO来实现 --在某个会话里设置 SET CONTEXT_INFO 0x8888 --在触发器里判断 ) SELECT @Cinfo = Context_Info() 原 ...
- PAT 天梯赛 L2-007. 家庭房产 【并查集】
题目链接 https://www.patest.cn/contests/gplt/L2-007 思路 将一个家庭里的所有人都并进去 然后最后查找的时候 找到所有同一个家庭的人,计算出人数,人均房产套数 ...
- NSAttributedStringKey
NSFontAttributeName; //字体,value是UIFont对象 NSParagraphStyleAttributeName;//绘图的风格(居中,换行模式,间距等诸多风格),valu ...
- zookeeper学习与实战(二)集群部署
上一篇介绍了单机版zookeeper安装,这种情况一般用于开发测试.如果是生产环境建议用分布式集群部署,防止单点故障,增加zookeeper服务的高可用. [环境介绍] 三台机器:192. ...
- 【九】MongoDB管理之安全性
要保证一个安全的MongoDB运行环境,DBA需要实施一些控制保证用户或应用程序仅仅访问它们需要的数据.这些措施包括但不限于: 认证机制 基于角色的访问控制 加密 审计 一.认证机制 认证是验证客户端 ...
- 【八】MongoDB管理之分片集群实践
MongoDB中集群有三种:主从复制.副本集.分片集群.目前副本集已经替代主从复制架构,成为官方建议采用的架构,而分片集群相较于前两种,更加复杂. 下面是生产环境中常用的分片集群架构: 我们知道,分片 ...