Mybatis是如何跟Spring整合的

寫在前面

Mybatis官網中,有這麼一節專門介紹如何注入一個mapperhtml

對於單個mapper,有兩種方式能夠注入,分別是xml和註解

其中,xml這種方式回味無窮。java

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
複製代碼

不妨咱們今天就手寫一個小框架,來實現mapper注入的功能。程序員

動態代理

咱們都知道,mybatis經過動態代理來實現將interface接口轉爲具體的類,來執行相應的mapper。具體是怎樣作的呢? SqlSession.javaspring

/**
   * Retrieves a mapper.
   * @param <T> the mapper type
   * @param type Mapper interface class
   * @return a mapper bound to this SqlSession
   */
  <T> T getMapper(Class<T> type);
複製代碼

一路追查下去 DefaultSqlSession.javasql

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
複製代碼

Configuration.javabash

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
複製代碼

MapperProxyFactory.javamybatis

@SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
複製代碼

這時咱們能夠看到真身了,mybatis正是使用了JDK的動態代理實現了對mapper的代理。那JDK的動態代理是怎麼回事呢? MapperProxy實現了InvocationHandler接口,須要實現invoke方法。這裏咱們來探究一下,爲何JDK的動態代理,實現InvocationHandler接口就能夠了? 先來一個接口app

public interface IHello {
    void sayHello();
}
複製代碼

被代理對象框架

public class RealSubject implements IHello {
    @Override
    public void sayHello() {
        System.out.println("我是被逼說hello的");
    }
}
複製代碼

InvocationHandler加強日誌dom

public class MyHandler implements InvocationHandler {
    private Object target;

    public MyHandler(Object subject) {
        this.target = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置日誌");
        Object res = method.invoke(target, args);
        System.out.println("後置日誌");
        return res;
    }
}
複製代碼

在client中跑一下

public static void main(String[] args) throws IOException {
        RealSubject realSubject = new RealSubject();
        MyHandler myHandler = new MyHandler(realSubject);

        IHello iHello = (IHello) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), new Class[]{IHello.class}, myHandler);
        iHello.sayHello();
    }
複製代碼

獲得輸出

前置日誌
我是被逼說hello的
後置日誌
複製代碼

Proxy.newProxyInstance能夠動態地獲取一個代理對象,代理對象調用接口中的方法時,會進入InvokationHandler裏的invoke方法,而咱們的實現是,先打印前置日誌,再調用被代理對象的方法,最後輸出後置日誌。這一切是怎麼運轉起來的呢? 這裏咱們須要藉助ProxyGenerator,來一窺代理的真容

private static void createProxyClass() throws IOException {
        byte[] proxyBytes = ProxyGenerator.generateProxyClass("IHello$Proxy", new Class[]{IHello.class});
        Files.write(new File("YOUR_PATH/IHello$Proxy.class").toPath(), proxyBytes);
    }
複製代碼

這時,咱們能夠獲得Proxy.class,這個代理對象繼承了Proxy並實現了IHello接口,因爲Java是單繼承且已經繼承自Proxy,因此JDK的動態代理是基於接口的

public final class IHello$Proxy extends Proxy implements IHello {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public IHello$Proxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sayHello() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("your.package.IHello").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

複製代碼

咱們看到sayHell()方法,好眼熟啊!這個h是父類Proxy中的protected InvocationHandler h;。那這個h是怎麼傳進來的呢?前方高能!前方高能!前方高能!

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
複製代碼

這裏會拿到代理類的構造方法,這個構造方法的參數是constructorParams,這個constructorParams是什麼呢?private static final Class<?>[] constructorParams = { InvocationHandler.class }; 那咱們就能夠從IHello$Proxy中找到這個構造函數:

public IHello$Proxy(InvocationHandler var1) throws  {
        super(var1);
    }
複製代碼

這個super(var1)就是

protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
複製代碼

對上了有木有!有木有!!激動不激動!!! 這樣,當咱們使用Proxy.newInstance()獲取到的代理對象調用相應方法時,就會跑到咱們本身寫的InvocationHandler實現類裏invoke方法!我無論,我要爲本身的牛逼點個贊👍

與Spring整合

好了,不要臉半天了,收! 咱們如今能夠經過一個UserMapper的接口來建立一個代理對象了,可是咱們怎麼把這個對象交給Spring託管呢? 注意,咱們這邊是已經new好了對象了,而後交給Spring去管理,而不是提供給Spring一個類,讓Spring幫咱們new一個對象是管理,這仍是有本質區別的! Spring會先使用BeanDefinition來保存Bean的信息,其中包含bean的scope、class相關信息、是否懶加載等等內容,當BeanDefinition對象建立好後,會先存入一個map。 爲何Spring不直接new呢? 直接new的話可能不符合條件,好比咱們提供的interface;另外,直接new的話,提供給程序員可自由發揮的空間就小了。 這裏,咱們要導出Spring的一個後置處理器了

BeanFactoryPostProcessor

實現BeanFactoryPostProcessor接口,咱們就能夠對BeanDefinition進行處理了。 假設有A、B兩個類,咱們在postProcessBeanFactory獲取到A這個BeanDefinition,而後將其BeanClass設置爲B,而後從ApplicationContext中獲取A

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		GenericBeanDefinition a = (GenericBeanDefinition) beanFactory.getBeanDefinition("a");
		a.setBeanClass(B.class);
	}
}
複製代碼

Spring會絕不留情地告訴咱們

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'your.package.domain.A' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:350)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:341)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)
	at com.meituan.Starter.main(Starter.java:22)
複製代碼

咱們能夠經過這個後置處理器來處理咱們的動態代理類,能夠彷佛仍是不夠

FactoryBean與ImportBeanDefinitionRegistrar

BeanFactoryPostProcessor的侷限是咱們只能從BeanFactory中獲取一個BeanDefinition,而後修改它的屬性,而不能直接往裏面添加一個新的Beand,這時咱們要再挖掘些新玩意兒。

將new出來的對象交給Spring託管有三種經常使用的方法:

  1. BeanFactory.registerSingleton(String beanName, Object singletonObject);
  2. 基於@Bean,本身在方法中new出一個對象來
  3. FactoryBean!

這裏咱們用第三種方法

public class MyFactoryBean implements FactoryBean {
	@Override
	public Object getObject() throws Exception {
		return Proxy.newProxyInstance(CityMapper.class.getClassLoader(), new Class[]{CityMapper.class}, new MyInvocationHandler());
	}

	@Override
	public Class<?> getObjectType() {
		return CityMapper.class;
	}
}
複製代碼

這樣能夠解決一個mapper的注入問題,能夠若是mybatis要提供一個框架,這樣寫可擴展性很差,這時咱們能夠把class做爲一個參數注入進來

public class MyFactoryBean implements FactoryBean {
	private Class mapperInterface;

	public void setMapperInterface(Class mapperInterface) {
		this.mapperInterface = mapperInterface;
	}

	@Override
	public Object getObject() throws Exception {
		return Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, new MyInvocationHandler());
	}

	@Override
	public Class<?> getObjectType() {
		return mapperInterface;
	}
}
複製代碼

這時咱們回看mybatis官網的那個xml配置,不由恍然大悟!把這裏的MyFactoryBean替換成MapperFactoryBean不就是了嘛!

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
複製代碼

那咱們如何將這個MyFactoryBean交給Spring呢?

這時咱們還須要寫一個類,實現ImportBeanDefinitionRegistrar,這個接口能夠幫咱們把一個BeanDefinition放入Spring的map

public class MyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class);
		GenericBeanDefinition beanDefinition = (GenericBeanDefinition) builder.getBeanDefinition();
		beanDefinition.getConstructorArgumentValues().addGenericArgumentValue("your.package.dao.CityMapper");
		registry.registerBeanDefinition("xxx", beanDefinition);
	}
}
複製代碼

這個類咱們經過一個註解引入,這時會用到@Import註解

@Retention(RetentionPolicy.RUNTIME)
@Import(MyBeanDefinitionRegistrar.class)
public @interface MyScan {
}
複製代碼

將這個註解放到

@ComponentScan("your.package")
@Configuration
@MyScan
public class AppConfig {
}
複製代碼

大功告成,撒花!

參考文檔

mybatis.org/spring/mapp…

相關文章
相關標籤/搜索