SpringIOC和AOP原理 设计模式

SpringIOC的特点

在接触Spring的过程中,听到最多的无非两个名词,一个是控制反转一个是依赖注入。实际这是一个意思,控制反转代表原来由程序本身去控制对象之间的依赖关系的这种格局被反转了,通过第三方容器(IOC)去完成控制这些对象的依赖的关系并对它们进行集中管理。

依赖注入:获得依赖对象的过程由自身管理变为了由IOC容器主动注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

SpringAOP的特点
AOP最多听到的就是面向切面编程,那对于这个名词,我第一次听到的时候也是不能理解的。

下面用一个图和语言来进行描述:

切面

在一个项目中和我们业务逻辑和通用的逻辑区分开来,比如我们的一个系统需要记录日志,记录日志这个事情是通用的,不管你做什么系统一般都会涉及。那么这一块就通过AOP来统一集中实现,统一管理。

生活中的一个例子,你去吃饭之前肯定要洗手,饭后肯定要擦嘴。那么,不管你吃什么饭在哪个地方吃。这些通用的过程你都要执行。那么集中抽象出来这些方法,也就形成了AOP。

SpringIOC容器加载Bean的过程

1.第一步 IOC容器


初始化

把xml文件位置信息保存,然后调用refresh方法去重新初始化一个新的IOC容器,Refresh方法中使用obtainFreshBeanFactory去获取,后面的代码是一些BeanFactory创建后的后处理过程


1.1

obtainFreshBeanFactory方法里面,我们看到第一行调用refreshBeanFactory的方法去创建。


1.2

在方法中去loadBeanDefintions(),使用XMLReader去解析我们的xml配置文件


1.3

后面省略一些源码的步骤,主要做的就是对xml文件进行解析成我们要的BeanDefinitions,处理每个Bean元素以及元素中的属性值。最后把beanDefinition注册进我们的BeanFactory中,

1.4

2.注入依赖

2.1
2.2

AOP的两种实现方式 以及小例子

主要是两种,一种是JDK动态代理,一种是Cglib代理。

两者的区别:
1.JDK动态代理只能代理实现了接口的类,动态代理类的字节码在程序运行时由Java反射机制动态生成。
2.Cglib是可以代理没有实现接口的类,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,所以不能对final修饰的类进行代理。底层采用ASM实现。

Cglib的例子:

package com.Model.CGlibProxy;

public interface AddBook {
    public void addbook();
}

package com.Model.CGlibProxy;

public class AddBookImp implements AddBook {

    @Override
    public void addbook() {
        // TODO Auto-generated method stub
        System.out.println("添加书籍....");
    }

}

package com.Model.CGlibProxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {

    private Object target;

    public Object getInstance(Object target) {
        this.target = target;

        Enhancer enhancer = new Enhancer();
        // 设置自己的父类
        enhancer.setSuperclass(target.getClass());
        // 关联的要使用哪个对象的回调函数 这里指向自己这个对象的回调 那么就是下面这个方面了
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
        // TODO Auto-generated method stub

        System.out.println("事务开始...");
        // 调用父类函数
        arg3.invokeSuper(arg0, arg2);

        System.out.println("事务结束....");

        return null;
    }

    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        AddBookImp oBookImp = (AddBookImp) cglibProxy.getInstance(new AddBookImp());
        oBookImp.addbook();
    }

}

JDK动态代理:

//操作定义
public interface SubjectOperations {
    // 打印操作
    public void print();

    // 打印输入字符串操作
    public void printfStr(String string);
}

public class RealSubject implements SubjectOperations {

    @Override
    public void print() {
        System.out.println("我实现了接口 完成这个打印操作");

    }

    @Override
    public void printfStr(String string) {
        // TODO Auto-generated method stub
        System.out.println("打印输入的内容:  " + string);
    }

}

public class LogHandler implements InvocationHandler {

    private Object ImpClass;
    private Object lObject;

    public LogHandler(Object realObject) {
        this.ImpClass = realObject;
    }

    public Object bind(Object impclass, Object iObject) {
        this.ImpClass = impclass;
        this.lObject = iObject;
        return Proxy.newProxyInstance(impclass.getClass().getClassLoader(), impclass.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("在目标方法调用前执行.....");
        method.invoke(ImpClass, args);
        System.out.println("在目标方法调用后执行....");
        return null;
    }

    public static void main(String[] args) {

    }
}

class Client {
    public static void main(String[] args) {
        RealSubject subject = new RealSubject();
        LogHandler handler = new LogHandler(subject);

        // 转化成接口 只能代理实现了接口的类
        SubjectOperations pSubject1 = (SubjectOperations) handler.bind(subject, new LoggerImp());
        System.out.println(pSubject1.getClass().getName());

        pSubject1.print();
        pSubject1.printfStr("YYYYY");
    }
}

面试官说让你讲讲IOC和AOP时,他们想了解什么?我应该怎么回答

这个问题我也很头痛,这里我找到一个模版,我打算以后就这样回答。大家也可以参考一下:

知乎

我的回答:
Spring是一套为了解决企业应用开发的复杂性而创建的框架,特点是分层的架构,允许用户在不同层面使用不同的组件进行组合。同时通过IOC容器来降低耦合,简化开发。利用AOP来进行切面编程统一管理通用模块。

我在工作中用到过Spring的IOC容器和AOP面向切面编程,在项目中使用IOC容器帮我很好的理清各个业务对象的关系,同时方便不同组件的更替。在做接口权限验证时,使用AOP切面编程帮助我能很快的对需要进行验证的接口完成验证功能的实现。并且统一进行管理,提高开发效率。

SpringMVC的大致实现过程

SpringMVC请求过程

1.用户发起一个request请求,如果有多个DispatcherServlet,则通过Servletmapping去指定执行的DispatcherServlet。
2.DispatcherServlet把根据URL请求,去HandlerMapping中查找注册了的URL映射,并返回相应的Handler(一个Controller,多个拦截器),给DispatcherServlet。
3.DispatcherServlet传递Handler给HandlerAdapter去执行,返回一个ModelAndView。
4.DispatcherServlet把ModelAndView传递给视图解析器去解析,返回一个视图view。
5.组装上Model数据后变成Response请求返回给客户端。

SpringIOC和AOP中用到的设计模式

简单工厂

在Spring中经常利用BeanFactory的getBean方法去获取Bean就是一个简单工厂的设计模式的实现,通过Bean的ID去获取这个对象的实例。Bean的ID一般配置在XML文件中

工厂方法

在工厂方法模式中, Spring不会直接利用反射机制创建bean对象, 而是会利用反射机制先找到Factory类,然后利用Factory再去生成bean对象。

而Factory Mothod方式也分两种, 分别是静态工厂方法 和 实例工厂方法。

静态工厂方法

定义一个类

public class Car {
    private int id;
    private String name;
    private int price;

//省略构造函数 getter和setter函数
}

定义一个静态工厂类

public class CarStaticFactory {
    private static Map<Integer, Car> map = new HashMap<Integer,Car>();

    static{
        map.put(1, new Car(1,"Honda",300000));
        map.put(2, new Car(2,"Audi",440000));
        map.put(3, new Car(3,"BMW",540000));
    }

    public static Car getCar(int id){
        return map.get(id);
    }
}

XML中的配置文件,可以看到我们指定的class不再是具体的Bean,而是生产Bean的工厂,然后通过工厂方法去创建,有一点不好的地方就是map的初始化在程序中进行,耦合度相对高

  <!-- 
        Static Factory method:
        class: the class of Factory
        factory-method: method of get Bean Object
        constructor-arg: parameters of factory-method
     -->
    <bean id="bmwCar" class="com.home.factoryMethod.CarStaticFactory" factory-method="getCar">
        <constructor-arg value="3"></constructor-arg>           
    </bean>

    <bean id="audiCar" class="com.home.factoryMethod.CarStaticFactory" factory-method="getCar">
        <constructor-arg value="2"></constructor-arg>           
    </bean>

实例工厂

我们创建一个工厂,这次获取car的方法不是静态的了。

public class CarInstanceFactory {
    private Map<Integer, Car> map = new HashMap<Integer,Car>();

    public void setMap(Map<Integer, Car> map) {
        this.map = map;
    }

    public CarInstanceFactory(){
    }

    public Car getCar(int id){
        return map.get(id);
    }
}

XML配置文件,把工厂的初始化在文件中配置完成。不用在代码中编写。

<!-- Instance Factory Method:
         1.must create a bean for the Instance Factroy First
     -->
     <bean id="carFactory" class="com.home.factoryMethod.CarInstanceFactory">
        <property name="map">
            <map>
                <entry key="4">
                        <bean class="com.home.factoryMethod.Car">
                            <property name="id" value="4"></property>   
                            <property name="name" value="Honda"></property> 
                            <property name="price" value="300000"></property>   
                        </bean>
                </entry>    

                <entry key="6">
                        <bean class="com.home.factoryMethod.Car">
                            <property name="id" value="6"></property>   
                            <property name="name" value="ford"></property>  
                            <property name="price" value="500000"></property>   
                        </bean>
                </entry>
            </map>  
        </property>
     </bean>

     <!-- 2.use Factory bean to get bean objectr 
        factory-bean : the bean define above
        factory-method: method of get Bean Object
        constructor-arg: parameters of factory-method
     -->
     <bean id="car4" factory-bean="carFactory" factory-method="getCar">
        <constructor-arg value="4"></constructor-arg>           
     </bean>

     <bean id="car6" factory-bean="carFactory" factory-method="getCar">
        <constructor-arg value="6"></constructor-arg>           
     </bean

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。
spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是是任意的java对象。

下面问答中会专门说一下Spring中的单例模式

核心提示点:Spring下默认的bean均为singleton,可以通过singleton=“true|false” 或者 scope=“?”来指定

代理模式

为其他对象提供一种代理以控制对这个对象的访问。 从结构上来看和Decorator模式类似,但Proxy是控制,更像是一种对功能的限制,而Decorator是增加职责。
spring的Proxy模式在aop中有体现,比如JdkDynamicAopProxy和Cglib2AopProxy。

观察者模式

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
spring中Observer模式常用的地方是listener的实现。如ApplicationListener。ServletContextListener

servlet和Filter初始化前和销毁后,都会给实现了servletContextListener接口的监听器发出相应的通知。

模板方法

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
Template Method模式一般是需要继承的。这里想要探讨另一种对Template Method的理解。spring中的JdbcTemplate,在用这个类时并不想去继承这个类,因为这个类的方法太多,但是我们还是想用到JdbcTemplate已有的稳定的、公用的数据库连接,那么我们怎么办呢?我们可以把变化的东西抽出来作为一个参数传入JdbcTemplate的方法中。但是变化的东西是一段代码,而且这段代码会用到JdbcTemplate中的变量。怎么办?那我们就用回调对象吧。在这个回调对象中定义一个操纵JdbcTemplate中变量的方法,我们去实现这个方法,就把变化的东西集中到这里了。然后我们再传入这个回调对象到JdbcTemplate,从而完成了调用。这可能是Template Method不需要继承的另一种实现方式吧。

不用继承的模版方法
我们把想要的connection连接赋值到回调对象中
获取从JDBCTemplate中传入的稳定的Connection对象,进行自己定义的操作
继承的模版方法,比如常见

我们定义一个算法的执行过程,里面会调用4个小步骤,具体的小步骤的实现我们交过实现的子类去完成。然后用户调用整个process方法就能实现功能。

public abstract class EntityProcessor {
 
    public final void processEntity() {
        getEntityData();
        createEntity();
        validateEntity();
        persistEntity();
    }
 
    protected abstract void getEntityData();
    protected abstract void createEntity();
    protected abstract void validateEntity();
    protected abstract void persistEntity();
 
}

IOC 的单例模式是怎么实现的

IOC的单例模式不是使用的懒汉式或者饿汉式,使用的是

单例注册表,通过把Bean的名称和对象组成的key-value注册进HashMap中。每次需要获取相应的类时,根据名称去获取,如果没有这个bean就去读取Bean的定义。估计type是需要单例还是多例,如果是单例注册进入表并返回,如是多例不注册,创建一个Bean并返回。

public abstract class AbstractBeanFactory implements ConfigurableBeanFactory{    
   /**  
    * 充当了Bean实例的缓存,实现方式和单例注册表相同  
    */    
   private final Map singletonCache=new HashMap();    
   public Object getBean(String name)throws BeansException{    
       return getBean(name,null,null);    
   }    
...    
   public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{    
      //对传入的Bean name稍做处理,防止传入的Bean name名有非法字符(或则做转码)    
      String beanName=transformedBeanName(name);    
      Object bean=null;    
      //手工检测单例注册表    
      Object sharedInstance=null;    
      //使用了代码锁定同步块,原理和同步方法相似,但是这种写法效率更高    
      synchronized(this.singletonCache){    
         sharedInstance=this.singletonCache.get(beanName);    
       }    
      if(sharedInstance!=null){    
         ...    
         //返回合适的缓存Bean实例    
         bean=getObjectForSharedInstance(name,sharedInstance);    
      }else{    
        ...    
        //取得Bean的定义    
        RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);    
         ...    
        //根据Bean定义判断,此判断依据通常来自于组件配置文件的单例属性开关    
        //<bean id="date" class="java.util.Date" scope="singleton"/>    
        //如果是单例,做如下处理    
        if(mergedBeanDefinition.isSingleton()){    
           synchronized(this.singletonCache){    
            //再次检测单例注册表    
             sharedInstance=this.singletonCache.get(beanName);    
             if(sharedInstance==null){    
                ...    
               try {    
                  //真正创建Bean实例    
                  sharedInstance=createBean(beanName,mergedBeanDefinition,args);    
                  //向单例注册表注册Bean实例    
                   addSingleton(beanName,sharedInstance);    
               }catch (Exception ex) {    
                  ...    
               }finally{    
                  ...    
              }    
             }    
           }    
          bean=getObjectForSharedInstance(name,sharedInstance);    
        }    
       //如果是非单例,即prototpye,每次都要新创建一个Bean实例    
       //<bean id="date" class="java.util.Date" scope="prototype"/>    
       else{    
          bean=createBean(beanName,mergedBeanDefinition,args);    
       }    
}    
...    
   return bean;    
}    
} 

如何保证IOC中有状态Bean的线程安全

有状态Bean就是一些Bean实例中含有一些非线程安全的成员变量,那么当多个线程去同时操作这个Bean时,就有可能导致对这些变量的操作出现问题。

使用ThreadLocal类可以做到,ThreadLocal会为变量在每个线程中创建本地变量副本,那么每个线程可以访问自己内部的副本变量。大家互不干扰,以此达到线程安全的目的。

最常见的数据库连接管理类:

class ConnectionManager {
     
    private static Connection connect = null;
     
    public static Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }
     
    public static void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}

第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;第二,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。

使用ThreadLocal后:

当多个线程去取session的时候,都会去拿自己线程的本地变量副本,没有就创建一个注册进去并返回。

private static final ThreadLocal threadSession = new ThreadLocal();
 
public static Session getSession() throws InfrastructureException {
    Session s = (Session) threadSession.get();
    try {
        if (s == null) {
            s = getSessionFactory().openSession();
            threadSession.set(s);
        }
    } catch (HibernateException ex) {
        throw new InfrastructureException(ex);
    }
    return s;
}

举例说明IOC如何使用,AOP如何使用

IOC的使用:
首先定义你需要的Bean的类以及成员变量,然后将这些类全部注册到Spring的ApplicationContext.xml文件中,然后在xml文件进进行依赖配置。之后只需要在程序中创建一个BeanFactory,加载XML文件,getBean就能获得我们要的实例了

AOP使用Aspectj:
定义一个Advice类,里面进行一个或多个pointcut的定义,以及在哪个pointcut下使用那些方法,

定义的pointcut方法本身只是用来标记的,用于指定在哪里进行一个切入,比如这里我使用的是within代表切入NeedLogService类中的所有方法


定义一个pointcut

有执行前的,有执行后的,有异常时的


定义切面方法

当我们调用这个类的方法时,就会发现被增强了


被代理类定义
测试调用
输出

参考文章:
Spring IOC核心源码学习
使用 IoC 反转控制的三种设计模式
Spring的单例模式底层实现
Spring中Singleton模式的线程安全
【SSH进阶之路】Spring的AOP逐层深入——采用注解完成AOP(七)
深入解析spring中用到的九种设计模式
Spring 通过工厂方法(Factory Method)来配置bean

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 131,216评论 18 138
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 30,458评论 18 399
  • 什么是Spring Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Jav...
    jemmm阅读 16,067评论 1 134
  • 道左红似火, 红似六月花; 路右绿婆娑 , 绿如朦胧纱; 曲径通幽处, 宛若一幅画; 小溪哼着歌, 前方是我家……
    天马行空我也阅读 164评论 6 4
  • 婚礼邀请函还在发纸质的吗?还在发那种毫无创意的卡片吗?土! 让来画君教你如何制作创意满满的婚礼邀请函视频, 让你的...
    爱画画的臭皮匠阅读 1,401评论 2 0
  • 长到这么大 我说不出来我最爱的一部电影 说不出来我最爱的一首歌 说不出来我最爱的一个人 时常觉得人生其实没那么有趣...
    1006f091c759阅读 166评论 0 2