美文网首页扩展技术我爱编程
Spring Ioc (反射) 精华一页纸

Spring Ioc (反射) 精华一页纸

作者: 轩居晨风 | 来源:发表于2017-04-11 22:15 被阅读391次

    反射是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 来管理对象的生命周期。这种方式,对象间的依赖关系还是硬编码到了代码中。

    相关文章

      网友评论

        本文标题:Spring Ioc (反射) 精华一页纸

        本文链接:https://www.haomeiwen.com/subject/nnfpattx.html