一、java代理模式

  java代理模式是ioc的前置知识。代理模式非常简单,看代码就一目了然了。

\"\"\"\"
public interface role {
    public void makeMoney();
}
role
\"\"\"\"
public class Boss implements role {
    @Override
    public void makeMoney() {
        System.out.println(\"老板正在谈项目...\");
    }
}
boss
\"\"\"\"
public class Secretary implements Role {

    private Role subject = new Boss();

    @Override
    public void makeMoney() {
        before();
        subject.makeMoney();
        after();
    }

    private void before(){
        System.out.println(\"你可以先和秘书谈一下合作意向...\");
    }
    private void after(){
        System.out.println(\"秘书把老板签过的合同给你...\");
    }
}
secretary

  上面是极简的java静态代理(也是接口代理模式),定义了接口、接口的实现类和代理类。或者称实现类为目标类,代理类为封装类。

  一般我们先找老板秘书,说一下合作意向(Secretary的makeMoney),可以的话(before)调用老板真正谈项目Boss的makeMoney),谈妥了就可以签合同了(after)。可以做一下测试:

\"\"\"\"
public class Test {

    public static void main(String[] args){
        // 测试静态代理
        /*
            所谓静态代理,是对接口实现类的某些方法进行了一次封装
            这个代理类需要实现接口的同样方法,并在内部初始化一个实现类
            在该方法中调用了实现类的方法
         */
        Secretary proxy = new Secretary();
        proxy.makeMoney();
    }
}
Test

  可见,与其直接访问一个目标类,更多的是访问封装类。这样做的好处在于:

    1.不操作目标类,不对目标类造成破坏;

    2.自定义封装类,具有充分的控制权;

    3.可以增强目标类的特定方法,达到增强和扩展的目的;

  但是,目标类和封装类必须实现接口定义的同一个方法

  更多地,java使用动态代理来实现功能扩展。举个例子:

\"\"\"\"
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        final Role role = new Boss();
        Role secretary = (Role) Proxy.newProxyInstance(
                role.getClass().getClassLoader(), // 目标类的类加载器
                role.getClass().getInterfaces(),  // 目标类所实现的所有接口
                new InvocationHandler() {         // 内部匿名类 -- InvocationHandler接口类的实现类,必须重写接口方法invoke
                    @Override
                    public   invoke(  proxy, Method method,  [] args) throws Throwable {
                        // proxy: 代理对象
                        // method: 目标方法
                        // args: 目标方法的参数列表
                        before();
                          result = method.invoke(role, args);
                        after();
                        return result;
                    }
                    private void before(){
                        System.out.println(\"先和秘书接洽约时间...\");
                    }
                    private void after(){
                        System.out.println(\"秘书送来合同书...\");
                    }
                }
        );
        secretary.makeMoney();
    }
}
jdk动态代理

  jdk动态代理同样是接口代理模式,new InvocationHandler()实现类中的invoke方法被Proxy.newProxyInstance自动调用。secretary对象相当于上面的secretary对象,只不过不需要我们自己手动创建了。jdk动态代理帮助我们创建了一个代理对象,我们只需写接口和实现类即可

  当然,上面的new InvocarionHandler()实现类可以单独拿出来重写:

\"\"\"\"
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JdkProxy implements InvocationHandler {

    private   impl;
    JdkProxy(  impl){
        super();
        this.impl = impl;
    }

    @Override
    public   invoke(  proxy, Method method,  [] args) throws Throwable {
        before();
          result = method.invoke(impl, args);
        after();
        return result;
    }

    private void before(){
        System.out.println(\"先和秘书接洽约时间...\");
    }
    private void after(){
        System.out.println(\"秘书送来合同书...\");
    }
}
JdkProxy

  做一下测试:

\"\"\"\"
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class JdkProxyTest {
    public static void main(String[] args) {
        Role role = new Boss();
        InvocationHandler invocationHandler = new JdkProxy(role);
        Role boss = (Role) Proxy.newProxyInstance(
                role.getClass().getClassLoader(),
                role.getClass().getInterfaces(),
                invocationHandler
                );
        boss.makeMoney();
    }
}
test

  除了jdk动态代理外,还有cglib动态代理。与jdk动态代理不同的是,cglib动态代理没有接口类,它是通过子类继承目标类,在子类同名方法内部调用目标类方法来实现的。这种实现方法的性能要优于jdk动态代理。

  总结一下,jdk代理帮助我们在目标类的基础上做了一次功能扩展,因此,对于日志、事务等系统级业务,可以通过动态代理交给别的组件管理,自己则专注写应用级业务,也就是上面的实现类(对应bean)和Test类(对应service)。这就是IOC控制反转。

 二、applicationContext. 和ApplicationContext

  “解耦(业务逻辑代码和实体类分离)”、“功能扩展和托管(系统级业务交给框架处理,开发者只专注应用级业务处理)”是理解spring容器的两个重要思想。

  动态代理解决了“功能扩展和托管”这个问题,通过 解析,spring容器(读取applicationContext. 生成的ApplicationContext实例)对目标类和封装类、业务代码两者进行了拆解。

  我们可以将目标类注入(DI)到applicationContetx. 文件中,由spring容器(ApplicationContext实例)读取applicationContext. 来自动生成目标类对应的接口类和封装类。在业务代码中,只需获取ApplicationContext实例,并从中获取响应的封装类即可。这个过程实现即是IOC控制反转

\"\"

  假设项目环境(IDEA)已经搭建完成(主要是JDK配置、maven配置、tomcat-->local服务器配置、项目Open Modules Settings配置、以及maven下文件添加及属性配置Mark Directory as)。

\"\"\"\"
D:.
├─src
│  ├─main
│  │  ├─java               // Sources Root
│  │  │  └─com
│  │  │      └─boke
│  │  ├─resources          // Resources Root,可以手动添加并右键设置Mark Directory as --> Resources Root
│  │  │  ├─applicationContext. 
│  │  │  └─log4j.properties
│  │  └─webapp             // Sources Root,可以手动添加并右键设置Mark Directory as --> Sources Root
│  │      └─WEB-INF
│  └─test                 
│      └─java             // Test Resources Root,可以手动添加并右键设置Mark Directory as --> Test Resources Root
│          └─com
│              └─node
│                  ├─boke
└─target                   // Excluded,自动生成
备注:这些都可以在项目配置中设置。IDEA右上角\"搜索\"图标左侧第一个图标,或者项目文件目录上右键Open Module Settings设置。
maven项目文件配置

  pom. 需要引入的依赖:junit4.11、spring-core、spring-beans、spring-context、spring-aop、spring- 、log4j1.2.17、spring-test、commons-logging1.1.1。spring work版本为4.2.1.RELEASE。

  现在跑一遍applicationContext. -->ApplicationContext的整个流程。

  定义接口类和实现类。

\"\"\"\"
// MyInterface类
public interface MyInterface {
    public void doSome();
}

// MyImplement类
public class MyImplement implements MyInterface {
    public MyImplement(){
        super();
    }
    public void doSome() {
        System.out.println(\"--------执行doSome()方法...----------\");
    }
}
接口类和实现类

  将实现类注册到applicationContext. 文件中。通常,实现类是一个POJO(plain old java ),对应applicationCOntext. 的某个bean标签,因此也可称为bean对象。

\"\"\"\"
<?  version=\"1.0\" encoding=\"UTF-8\"?>
<beans  ns=\"http://www.spring work.org/schema/beans\"
        ns:xsi=\"http://www.w3.org/2001/ Schema-instance\"
       xsi:schemaLocation=\"http://www.spring work.org/schema/beans http://www.spring work.org/schema/beans/spring-beans.xsd\">

    <!--
        注册MyImplement
        它相当于:MyInterface implement = (MyInterface) new MyImplement();
    -->
    <bean id=\"implement\" class=\"com.boke.MyImplement\" />
</beans>
依赖注入

  测试。

\"\"\"\"
package com.node.boke;

import com.boke.MyInterface;
import com.boke.MyImplement;
import org.junit.Test;
import org.spring work.beans.factory.BeanFactory;
import org.spring work.beans.factory. . BeanFactory;
import org.spring work.context.ApplicationContext;
import org.spring work.context.support.ClassPath ApplicationContext;
import org.spring work.context.support.FileSystem ApplicationContext;
import org.spring work.core.io.ClassPathResource;

public class MyTest {

    /**
     * 示例一:传统的开发模式:接口-->实现-->调用,都绑定在了一起,耦合度比较高
     */
    @Test
    public void test01(){
        MyInterface implement = new MyImplement();
        implement.doSome();
    }

    /**
     * 示例二:spring容器解耦
     */
    @Test
    public void test02(){
        /*
            ClassPath ApplicationContext:类路径
            把resources设置为source root,然后直接从其文件夹下读取applicationContext. 即可
            解耦:看不到MyInterface实现类
         */
        ApplicationContext ac = new ClassPath ApplicationContext(\"applicationContext. \");
        MyInterface implement = (MyInterface)ac.getBean(\"implement\");
        implement.doSome();
    }
    /**
     * 示例三:文件路径
     */
    @Test
    public void test03(){
        /*
            FileSystem ApplicationContext:文件路径,默认是项目根路径下,也可以从其他路径读取applicationContext. 文件
            解耦:看不到ISomeimplementImpl实现类
         */
        ApplicationContext ac = new FileSystem ApplicationContext(\"applicationContext. \");
        MyInterface implement = (MyInterface)ac.getBean(\"implement\");
        implement.doSome();
    }

    /* applicationContext与BeanFactory的区别:
        - applicationContext容器在进行初始化时,会将其中的所有Bean(对象)创建;
            缺点:占用系统资源(内存,CPU等)
            优点:响应速度快
        - BeanFactory容器中的对象在容器初始化不会创建Bean对象,而是在真正获取该对象时才创建;
            缺点:响应速度慢
            优点:占用系统资源相对较小
    */
    /**
     * 示例四: BeanFactory接口类的实现
     */
    @SuppressWarnings(\"deprecation\")
    @Test
    public void test04(){
        BeanFactory ac = new  BeanFactory(new ClassPathResource(\"applicationContext. \"));
        MyInterface implement = (MyInterface)ac.getBean(\"implement\");
        implement.doSome();
    }
}
测试

  总结:

  Spring的主要作用就是为代码\"解耦\",降低代码间的耦合度。根据功能的不同,可以将一个系统中的代码分为主业务逻辑和系统级业务逻辑两类。主业务代码逻辑联系紧密,有具体的专业业务应用场景,复用性相对较低;系统级业务逻辑相对功能独立,没有具体的专业业务应用场景,为主业务提供系统级服务,复用性强。

  Spring根据代码的功能特点,将降低耦合度的方式分为两类:IOC和AOP。IOC使得主业务逻辑在相互调用的过程中不用自己创建对象和调用,而是由Spring容器统一管理,自动\"注入\"。AOP使得系统级服务得到了服用,由Spring容器统一完成\"织入\"。

  Spring的特点:
            - 1.非入侵:spring的api不会混入到应用业务开发中。
            - 2.容器:Spring可以管理对象的生命周期以及对象间的依赖关系,并且可以通过配置文件来定义对象,以及设置与其它对象的依赖关系。
            - 3.IOC: 控制反转(inversion of control),即创建调用者的实例不是由调用者完成的,而是由Spring容器完成,并注入调用者。
                            即不是对象从容器中查找对象,这个事情是容器在对象初始化时不等对象请求就主动将依赖传递给它。
            - 4.AOP:面向切面编程(Aspect Orient Programming),是一种编程思想,是面向对象编程的OOP的补充。可以把日志、安全、事务管理等服务理解成一个\"切面\"。

三、ApplicationContext在bean注入时增扩了哪些功能--bean的生命始末

   IOC(Inversion of Control)是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式有两种:依赖查找、依赖注入。依赖查找(Dependency Lookup DL):容器提供回调接口和上下文环境给组件,程序代码则需要提供具体的查找方式。 依赖注入(Dependecy Injection DI):程序代码不做定位查找,这些工作由容器自行完成。依赖注入是目前最优秀的解耦方式。依赖注入让Spring的Bean之间以配置文件的方式组织在一起,而不是以硬编码的方式耦合在一起。

  通过以下代码查看bean注入和销毁的整个流程。

\"\"\"\"
// 接口类
package com.boke;

public interface MyInterface {
    public void doSome();
}

// 实现类
package com.boke;

import org.spring work.beans.BeansException;
import org.spring work.beans.factory.*;
import org.spring work.beans.factory.config.BeanPostProcessor;

public class MyImplement implements MyInterface, BeanNameAware, BeanFactoryAware, InitializingBean, DisposableBean, BeanPostProcessor {

    private String aDao;
    private String bDao;

    public MyImplement(){
        System.out.println(\"Step1.执行构造方法...\");
    }

    @Override
    public void doSome() {
        System.out.println(\"Step9:执行真正的业务处理逻辑doSome()方法...\");
    }

    public void setUp(){
        System.out.println(\"Step7:bean生命开始...\");
    }
    public void tearDown(){
        System.out.println(\"Step11:Bean销毁之前...\");
    }

    public String getaDao() {
        return aDao;
    }

    public void setaDao(String aDao) {
        System.out.println(\"Step2:执行aDao的setter方法...\");
        this.aDao = aDao;
    }

    public String getbDao() {
        return bDao;
    }

    public void setbDao(String bDao) {
        System.out.println(\"Step2:执行bDao的setter方法...\");
        this.bDao = bDao;
    }

    @Override
    public void setBeanName(String name) {
        System.out.println(\"Step3:获取bean的id = \" + name);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println(\"Step4:获取到BeanFactory容器...\");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println(\"Step6:标志着bean已经初始化完毕...\");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println(\"Step10:实现接口的销毁之前...\");
    }

    @Override
    public   postProcessBeforeInitialization(  bean, String s) throws BeansException {
        System.out.println(\"Step5:初始化完毕之前,执行before方法...\");
        return bean;
    }

    @Override
    public   postProcessAfterInitialization(  bean, String s) throws BeansException {
        System.out.println(\"Step8:初始化完毕之后,执行after方法...\");
        return bean;
    }
}
接口类和实现类

  applicationContext. 注入。

\"\"\"\"
<?  version=\"1.0\" encoding=\"UTF-8\"?>
<beans  ns=\"http://www.spring work.org/schema/beans\"
        ns:xsi=\"http://www.w3.org/2001/ Schema-instance\"
       xsi:schemaLocation=\"http://www.spring work.org/schema/beans http://www.spring work.org/schema/beans/spring-beans.xsd\">

    <!--测试生命bean-->
    <bean
            id=\"implement\"
            class=\"com.boke.MyImplement\"
            init-method=\"setUp\"
            destroy-method=\"tearDown\"
    >
        <!--初始化ISomeServiceImpl的成员变量-->
        <property name=\"aDao\" value=\"aaa\" />
        <property name=\"bDao\" value=\"bbb\" />
    </bean>
</beans>
applicationContext.

  测试类。

\"\"\"\"
package com.node.boke;

import com.boke.MyInterface;
import org.junit.Test;
import org.spring work.context.ApplicationContext;
import org.spring work.context.support.ClassPath ApplicationContext;

public class MyTest {
    @Test
    public void test01(){
        ApplicationContext ac = new ClassPath ApplicationContext(\"applicationContext. \");
        MyInterface service = (MyInterface)ac.getBean(\"implement\");
        service.doSome();
        /*
            要执行tearDown,需要两个条件:
               - scope=singleton
               - 手动关闭ac
        */
        ((ClassPath ApplicationContext)ac).close();
    }
}
测试类

  执行test,打印结果如下:

\"\"\"\"
INFO [AbstractApplicationContext.java:prepareRefresh:573]- Refreshing org.spring work.context.support.ClassPath ApplicationContext@ba4d54: startup date [Fri Dec 14 11:41:09 CST 2018]; root of context hierarchy
INFO [ BeanDefinitionReader.java:loadBeanDefinitions:317]- Loading   bean definitions from class path resource [applicationContext. ]
Step1.执行构造方法...
Step2:执行aDao的setter方法...
Step2:执行bDao的setter方法...
Step3:获取bean的id = implement
Step4:获取到BeanFactory容器...
Step5:初始化完毕之前,执行before方法...
Step6:标志着bean已经初始化完毕...
Step7:bean生命开始...
Step8:初始化完毕之后,执行after方法...
Step9:执行真正的业务处理逻辑doSome()方法...
INFO [AbstractApplicationContext.java:doClose:951]- Closing org.spring work.context.support.ClassPath ApplicationContext@ba4d54: startup date [Fri Dec 14 11:41:09 CST 2018]; root of context hierarchy
Step10:实现接口的销毁之前...
Step11:Bean销毁之前...
打印结果

  分析:

  在注入一个bean时,大致经过了11个步骤。这个流程对每一个注入的bean都适用。BeanNameAware, BeanFactoryAware, InitializingBean, DisposableBean, BeanPostProcessor是spring中自带的bean见名知意,在bean初始化、调用和销毁之际被spring自动调用。

  我们可以通过实现(如上面的MyImplement)这些容器中自动调用的bean接口类中的抽象方法来实现功能的扩展,并交由spring自动处理。可见,spring在注入一个我们自写的bean时,创建了一个动态代理对象,这个对象不仅仅封装了我们自己的接口实现类,也封装了spring默认的这些接口实现类,从而完成了一个bean注入的整个生命周期。

  当然,我们可以把Implement实现的这些接口单独拿出来写一个实现类,注入到spring容器中。以BeanPostProcessor为例。

\"\"\"\"
package com.boke;

import org.spring work.beans.BeansException;
import org.spring work.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public   postProcessBeforeInitialization(  bean, String s) throws BeansException {
        System.out.println(\"Step5:初始化完毕之前,执行before方法...\");
        return bean;     // 要返回这个bean,它表示当前正在初始化的bean对象
    }

    @Override
    public   postProcessAfterInitialization(  bean, String s) throws BeansException {
        System.out.println(\"Step8:初始化完毕之后,执行after方法...\");
        return bean;     // 要返回这个bean,它表示当前正在初始化的bean对象
    }
}
MyBeanPostProcessor

  将其注入到applicationContext. 。

\"\"\"\"
<?  version=\"1.0\" encoding=\"UTF-8\"?>
<beans  ns=\"http://www.spring work.org/schema/beans\"
        ns:xsi=\"http://www.w3.org/2001/ Schema-instance\"
       xsi:schemaLocation=\"http://www.spring work.org/schema/beans http://www.spring work.org/schema/beans/spring-beans.xsd\">

    <!--测试生命bean-->
    <bean
            id=\"implement\"
            class=\"com.boke.MyImplement\"
            init-method=\"setUp\"
            destroy-method=\"tearDown\"
    >
        <!--初始化ISomeServiceImpl的成员变量-->
        <property name=\"aDao\" value=\"aaa\" />
        <property name=\"bDao\" value=\"bbb\" />
    </bean>

    <bean class=\"com.node.service.beanlife.MyBeanPostProcessor\" />
</beans>
applicationContext.

  效果和直接继承实现是一样的。

  在这些步骤中,我们可以定制一些针对所有bean都适用的功能来进行扩展。

  BeanPostProcessor说明:

  Bean后处理器是一种特殊的Bean,容器中所有的Bean在初始化时均会自动执行该类的两个方法。由于该Bean是由其它Bean自动调用执行,不是程序员手工调用,故此Bean无须id属性。Bean后处理器是org.spring work.beans.factory.config.BeanPostProcessor,它会被自动加载,并执行它的实现类的两个方法:

  public postProcessBeforeInitialization( bean, String beanId) throws BeanException,该方法会在目标Bean初始化完毕之前由容器自动调用。

  public postProcessAfterInitialization( bean, String BeanId) throws BeansException,该方法会在目标Bean初始化完毕之后由容器自动调用。

  它们的参数是:第一个参数是系统即将初始化的Bean实例,第二个参数是该Bean实例的id属性值,若Bean没有id就是name属性值。实现类需要重写这两个方法,并注册到bean中,spring容器会在每个bean调用之前(之后)自动执行者两个方法。

四、基于 的DI

  applicationContext. 中以bean标签的方式注入属于典型的 方式的注入。上节梳理了bean注入的生命始末,本节记录注入bean的一些方式。

  1.无参构造注入

\"\"\"\"
// Student类
package com.boke;

public class Student {
    private String name;
    private int age;

    private School school;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public School getSchool() {
        return school;
    }

    public void setSchool(School school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return \"Student{\" +
                \"name=\'\" + name + \'\\\'\' +
                \", age=\" + age +
                \", school=\" + school +
                \'}\';
    }
}

// School类
package com.boke;

public class School {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return \"School{\" +
                \"name=\'\" + name + \'\\\'\' +
                \'}\';
    }
}
实体类
\"\"\"\"
<?  version=\"1.0\" encoding=\"UTF-8\"?>
<beans  ns=\"http://www.spring work.org/schema/beans\"
        ns:xsi=\"http://www.w3.org/2001/ Schema-instance\"
       xsi:schemaLocation=\"http://www.spring work.org/schema/beans http://www.spring work.org/schema/beans/spring-beans.xsd\">

    <!--注册无参构造的bean并注入成员变量-->
    <bean id=\"school\" class=\"com.boke.School\">
        <property name=\"name\" value=\"清华大学\" />
    </bean>

    <bean id=\"student\" class=\"com.boke.Student\">
        <!--注入成员变量的值-->
        <property name=\"name\" value=\"Alex\" />
        <property name=\"age\" value=\"20\" />
        <!--对于一个对象,称为域属性,需要用ref来注入-->
        <property name=\"school\" ref=\"school\" />
    </bean>
</beans>
applicationContext.
\"\"
收藏 打印
您的足迹: