Spring Ioc (反射) 精华一页纸

反射是Java实现模块化的一个非常基础的功能,通过加载类的字节码,然后动态的在内存中生成对象。也是深入Java 研究的第一个高级主题。关于加载器和字节码部分的内容,可以参见本博的 《java Class和加载机制精华一页纸》

Spring 框架基础的Ioc就是采用了反射的功能,实现了框架。

1、反射

I、反射操作经典步骤

一、获取 Class对象

a、最常用的就是 Class.forName(className)

b、如果知道类名字,直接通过类获取 String.class

c、如果已有一个对象 object.getClass

二、获取 Method对象

a、通过Class对象的getdeclaredMethods 获取所有方法

b、通过名字和参数类型列表,获取具体的方法getdeclaredMethod

三、实例化该Class的对象

Class.newInstance

四、调用方法

Method.invoke(newobject,new Object[]{parmalist}

II、反射的作用

反射是实现抽象的一个基础设施。单个应用内的模块化和解耦, 大家都比较熟悉, 比如 面向接口编程, 工厂模式等等。

iterface a = Factory.create;

在Factory 里面,我们是知道这个具体的实现类的。

但如果是应用模块之间呢, 不同人或者团队开发的, 商量好名字? 如果 名字改变后呢?

这样耦合性太强, 每次修改都会要带来代码重新修改和编译。

反射正是可以解决这个问题的工具。静态编译时, 并不需要知道具体的名字;在加载时, 通过传入名称参数, 获取到这个类

比如, 配置文件中配置了 具体实现类的名字, 只要在一个ClassPath下,就可以加载到具体的实现类。

Class c = Class.forName( param ); // 此处param可以是加载文件\其他应用传入的参数等等

iterface a = c.newInstance();

这个解耦套路,就是 传统的框架 套路

2、传统模块间解耦框架 - 依赖查找(DL)

依赖查找, 有个最经典的例子就是 JNDI , JavaEE 就是通过这个实现模块间对象的访问, 比如EJB, 下面是 tomcat下一个依赖查找的例子

I、context or server 配置文件

type="javax.sql.DataSource" auth="Container"

driverClassName="com.mysql.jdbc.Driver"

maxActive="4000"

maxIdle="60"

maxWait="15000"

url="jdbc:mysql://localhost:3306/mysql?useUnicode=true&characterEncoding=UTF-8"

username="root"

password="root"/>

II、代码中依赖查找

Context ctx = new InitialContext();

DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/DefaultDS");

III、依赖查找的问题

依赖查找的关键问题是 对代码侵入性强, 带来的结果就是 模块集成、单元测试等等工作很难操作, 比如测试一个EJB调用的代码, 必须要有完整的 Web框架, 要配置好基础设施;而 这段代码只是要测试自己的逻辑和接口。

3、轻量级模块间解耦框架 - 依赖注入(DI)/控制反转(Ioc)

这两个概念自从Spring横空出世以后, 一直抄的非常火热。先解释一下两个名词

依赖注入:是从应用角度出发, 需要的对象是从 外面注入进来的, 属于被动接受对象;而不像传统的 依赖查找, 主动的去查找对象。

控制反转: 是从框架和容器的角度出发, 创建对象的工作, 由应用 让渡给 容器来完成, 对象间的依赖, 也都由容器完成。

依赖注入/控制反转,看起来很神奇, 其实,如果遵循 开发的几大原则, 面向接口、职责单一、接口隔离、开放封闭等(可以参照本博《设计模式 精华一页纸》),就会发现, 这是一种比较自然和优雅的架构设计。

传统的依赖查找,虽然解开了模块间的耦合,但他违背了职责单一的要求,对于 应用而言, 只需要了解和调用 接口中的方法, 而查找这个工作不应该放在应用中。所以,可以对查找这个过程进行封装。

Object o = Lookup.get(xxx);

-- 这里的 Lookup 封装了对象的查找过程

再进一步封装和解耦,查找对象的过程对应用彻底屏蔽隔离、在应用的代码中不再出现 查找的代码。要完成这个工作

a、 首先,查找获取的对象 要设置到 使用该对象的目标对象的应用代码中, 也就是所谓的 注入工作

b、 其次,要完成注入工作,要么 把目标对象的引用传递给框架, 要么目标对象本身就是框架创建的

c、 从解耦、隔离的角度看, 框架创建管理对象更符合要求。

框架管理对象的生命周期、提供对象的注入工作。

......

Spring Ioc 框架就是在这个基础上产生了。

4、Spring Ioc 框架

从上面的讨论, 可以了解, 对象都交由框架管理和构造, 所以、首先要有对象的管理容器;其次要有注入的接口,实现装配工作。

I、Bean 工厂/容器

某种角度上,Spring Ioc就是一个对象容器, 依赖注入这些只是提供的功能而已

public interface BeanFactory{

Object getBean(String name) throws BeansException

Object getBean(String name, Class requiredType)throws BeansException

boolean containsBean(String name)

boolean isSingleton(String name)throws NoSuchBeanDefinitionException

String[] getAliases(String name)throws NoSuchBeanDefinitionException

}

四级接口

BeanFactory作为最基础的接口,只提供了基本功能。

秉着 接口隔离的设计原则, 从BeanFactory开始的继承体系

二级接口 AutowireCapableBeanFactory ListableBeanFactory HierarchicalBeanFactory

分别对应 自动装配 Bean工厂 : 作用是不在Spring(主要是 ApplicationContext)中管理的对象, 如果在应用中用到了,Spring 无法注入,比如如果用到Tomcat已存在的对象,通过这个工厂把 这些对象引入并注入应用对象。

迭代Bean的 Bean工厂 : 提供对容器中的Bean访问功能

访问父接口的 Bean工厂 : 提供对父容器的访问功能

三级接口 ConfigurableBeanFactory :叠加配置功能(是否单例、范围、Bean依赖等等)

四级接口 ConfigurableListBeanFactory : 大合集功能的 接口, 继承之前面的接口

第一个默认的实现类 DefaultListableBeanFactory

一个比较有意思的问题: BeanFactory 和 FactoryBean 的区别?

这其实是两个完全不同层次的内容

BeanFactory 是 Ioc 容器的接口,管理Bean的核心接口

FactoryBean 则是 适配 第三方应用的一个接口, 提供了对第三方Bean的适配, 以便更好的集成到Spring中来

通过工厂Bean,应用不需要自己写适配类去装配其他应用

org.springframework.jndi.JndiObjectFactoryBean -- 提供JNDI查找的对象

org.springframework.orm.hibernate.LocalSessionFactoryBean -- 提供Hibernate SessionFactory

org.springframework.orm.jdo.LocalPersistenceManagerFactoryBean -- 提供JDO PersistenceManagerFactory的

org.springframework.aop.framework.ProxyFactoryBean -- 获取AOP的动态代理,实现AOP切面功能

org.springframework.transaction.interceptor.TransactionProxyFactoryBean -- 创建事务代理

org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean -- EJB业务接口

...

org.springframework.remoting.caucho.HessianProxyFactoryBean -- Hessian 远程协议的代理

org.springframework.remoting.caucho.BurlapProxyFactoryBean -- Burlap远程协议的代理

II、Bean的生命周期

容器托管了 Bean的创建, 所以容器需要负责管理 Bean的生命周期。

a、生命周期

实例化 -> 设值注入 -> 设置Bean ID -> 设置工厂 -> 设置上下文 -> 初始化(开始\初始化\结束)

正常构建Bean的这些过程, 不需要应用介入。如果有特殊需要介入的地方。Spring开放了二次接口。

如果需要在构造对象的时候提供 初始化和 销毁时 额外处理的能力

方法一:Spring提供了回调接口 BeanNameAware| ApplicationContextAware | BeanPostProcessor | InitializingBean | BeanPostProcessor | DisposableBean 等等对应不同的构造阶段二次接口

org.springframework.beans.factory.InitializingBean 该接口提供了对象构造后 afterProperiesSet() throws Exception 方法

org.springframework.beans.fatory.Disposable 该接口提供了一个对象销毁后调用的 destory() throws Exception 方法

@PostConstruct 注解 | @PreDestory 初始化调用和销毁调用

方法二:Spring 可以指定属性配置

这样,在引入第三方组件时,可以不用依赖Spring容器,第三方组件不需要修改代码,或者为Spring写适配器

也可以配置全局的 init-method/destroy-method 方法

方法三:Spring提供的Bean工厂接口,Bean实现该接口,可以获取Bean工厂的引用,可以获取对其他Bean的引用,实现生命周期干预

org.springframework.beans.factory.BeanFactoryAware 该接口提供一个 setBeanFactory(BeanFactory beanFactory) throws BeanException

如何选择?

如果希望解耦Spring 框架, 则可以使用 方法二 指定属性, 这样配置方法干预初始化和销毁;否则建议使用 注解

b、作用域

singlton - 一个Spring容器对应一个 对象

prototype - 每获取一个对象

request | session | gloabl - Web应用的作用域,每作用域一个对象

默认是 singlton 作用域

Web应用 DispatchServlet 会默认管理作用域,默认是request

c、创建和销毁

何时被创建?

默认是随容器启动创建

可以配置为 lazy-init="true" 获取时创建

何时被销毁?

singlton, 在容器关闭时销毁,平时一直驻留

prototype 销毁由应用管理

- 因为只有 singlton的 对象才会进入 Bean容器工厂的ConcurrentHashMap 缓存。这也是为什么 prototype 类型的对象, 无法进行销毁回调, 因为对象的控制权交给了应用

III、 应用上下文(org.springframework.context.ApplicationContext)

工厂接口提供Bean管理的核心功能, 如果要把这个工厂应用到具体项目中, 还需要很多基础设施, ApplicationContext就是这个功能合集。

a、继承了Bean工厂的功能,继承了 ListableBeanFactory | HierarchicalBeanFactory

b、提供资源的管理,主要是加载各种配置文件

c、国际化信息,主要是各种信息的国际化

通过委托给代理类 ResourceBundleMessageSource实现国际化

d、提供事件管理

继承自Java自带的事件分发

事件ApplicationEvent -> 继承 EventObject

监听者 ApplicationListener -> 继承 EventListener

提供了 ApplicationEventPublisher 事件管理器(分发)

具体参见本博 《java 观察者、事件机制 到Spring 事件分发机制》

e、lifycycle 生命周期管理

容器的生命周期管理提供 Lifecycle 接口, 提供给任何实现 该接口的Bean, 通过LifecycleProcessor 执行回调接口, 可以和容器的生命周期管理同步。

提供 start | stop | isRunning | onRefresh 等回调接口

常用的容器实现对象

ClassPathXmlApplicationContext

FileSystemXmlApplicationContext

XmlWebApplicationContext

5、Spring Ioc实例

I、基本使用 设值和构造子

undefinedundefined

undefinedundefined

设值是通过 setter 方式注入;构造子按照顺序注入

II、集合装配

子节点有 (可嵌套)

成员有

成员比较简单,就是

value a

value b

III、工厂装配

-- 静态工厂 static

-- 动态工厂 new

IV、SPEL表达式

#{xxx} 其实是一种占位替换表达式语法, 类似的有很多比如 Freemarker 的${}, angular JS的 {{}}, 支持对内存对象的访问和简单表达式操作, 这些语法也很类似

常量 #{xx} 等同于 xx 常量一般直接用的很少

引用 #{xxx.xxx} -- 属性 #{xxx.getxxx()} -- 方法

静态属性 #{T(ClassXXX).xxx}

各种运算(算术|逻辑|正则) #{1+2} #{a == b && b == c}

V、自动装配

byName -- 根据Bean名称和属性名称进行匹配 缺点是名称要一致,如果多个名称类似,就要避开重复

byType -- 根据Bean类型和属性类型进行装配 缺点是不能存在相同类型的多个bean(解决方法,首选bean,排除其他bean)

constructor -- 把具有相同类型的 type 构造到属性中

autodetect -- 首先尝试 constuctor 装配,失败采用 byType

指定单个Bean autowire="byName"

指定全局 default-autowire

开启自动装配

VI、注解

a、注入

@Autowired 实现 构造和设置注入

@Qualifier("guitar") 指定Bean注入,甚至可以自定义 注解

@Inject -- 使用JCP的Inject注解

b、bean定义

@Component -- 通用构造性注解

@Controller -- Spring MVC Controller

@Repository -- 标记为数据仓库

@Service -- 标记为服务

经过测试发现,XML手工配置的 注入,会覆盖 注解注入的值,应该Spring的顺序最后是手工

c、用Spring配置类来替代注入的工作

// 定义全局文件的 Beans 测试的时候发现,SpringConfig 类,Spring使用了CGlib(asm) 技术重新处理了字节码

// 主要原因是,Spring 并不是直接 调用方法返回对象的,比如如下 duke() 方法,Spring会拦截,针对单例的情况

// Spring 会从自己的上下文返回一个已经存在的对象

@Configuration

public class SpringConfig {

// 定义一个名为 duke 的Bean

@Bean

public Performer duke(){

return new Juggler();

}

@Bean

public Instrument guitar(){

return new Guitar(0);

}

@Bean

public Performer kenny(){

Instrumentalist kenny = new Instrumentalist();

kenny.setSong("aaa");

kenny.setInstrument(guitar());

return kenny;

}

}

使用 Java 配置的问题是,SpringConfig 就相当于facade 门面的实现,使用了 Spring的 Context 来管理对象的生命周期。这种方式,对象间的依赖关系还是硬编码到了代码中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容