您好,登錄后才能下訂單哦!
如何將動態代理對象交由Spring容器管理,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
我們平時工作日常開發中,要講類的實例對象注入ioc容器,只需要在類上加上相應的注解即可實現。一般用的多的就是@Controller,@Service,@Component等注解,那么用都是這么用,其中的原理倒是不被人理解。先過一下spring bean的加載過程。
說到這里,我們先看看java中一個實例是如何描述的。
java中通過類來描述對象實例的,一個對象具有什么屬性,方法,都是在類中進行定義。然后將其變異為class字節碼文件,通過new關鍵字就能得到一個實例對象。
那么在spring中的bean是如何描述呢?
前部分相同,通過Registrar注冊到beanDefinition中,在spring中用beanDefinition對象來描述需要實例化的對象,包含了這個bean的名稱,class,是否是抽象,是否為單例等信息,然后通過preInstantiateSingletons實例化到ioc容器中。我們要使用的對象的時候,就從ioc容器中去獲取相應的對象即可。
在spring bean的實例化過程中,spring設計了很多攔截點,可以在動態的改變實例化對象的相關信息。達到在ioc容器中的對象和最開始注冊到beanDefinition中的信息不同。
現在來看看看FactoryBean。FactoryBean從名字來看以bean結尾那應該就是一個bean吧,沒錯它確實是一個bean,不同于普通Bean的是:它是實現了FactoryBean<T>接口的Bean。
特殊性質:
根據該Bean的ID從BeanFactory中獲取的實際上是FactoryBean的getObject()返回的對象,而不是FactoryBean本身,如果要獲取FactoryBean對象,請在id前面加一個&符號來獲取。
public interface FactoryBean<T> { //獲取bean對應的實例對象 @Nullable T getObject() throws Exception; //獲取factoryBean獲取到的實例類型 @Nullable Class<?> getObjectType(); //FactoryBean創建的實例是否是單實例 default boolean isSingleton() { return true; } }
下面我們通過自定義一個FactoryBean來驗證一下這個bean的特殊性質。先準備一個測試bean
public class TestBean { }
編寫自定義FactoryBean
@Component("myFactoryBean") public class MyFactoryBean implements FactoryBean { @Override public Object getObject() throws Exception { return new TestBean(); } @Override public Class<?> getObjectType() { return TestBean.class; } }
編寫測試類。從springioc容器中獲取myFactoryBean實例。
Object bean = SpringContextUtils.getBean("myFactoryBean"); System.out.println(bean); //com.wj.factorybean.TestBean@2d459bda Object bean = SpringContextUtils.getBean("&myFactoryBean"); System.out.println(bean); //com.wj.factorybean.MyFactoryBean@60ab895f
看到打印結果,驗證了上述的性質。
BeanFactoryPostProcessor可以在對象實例化到ioc容器之前對原有的beanDefinition的一些屬性進行設置更新。
先來看例子。準備連個bean,其中TestBean我們@omponent注解,而TestBean1不做處理
@Component("testBean") public class TestBean { } public class TestBean1 { }
編寫BeanFactoryPostProcessor實現類
@Component public class MyFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { //獲取testBean的beanDefinition GenericBeanDefinition beanDefinition = (GenericBeanDefinition)beanFactory.getBeanDefinition("testBean"); //更改class為TestBean1 beanDefinition.setBeanClass(TestBean1.class); } }
編寫測試類進行測試
Object bean = SpringContextUtils.getBean("testBean"); // com.wj.factorybean.TestBean1@45dc7be System.out.println(bean);
我分明獲取的是testBean的實例,結果盡然獲取到的是TestBean1實例。說明我們通過BeanFactoryPostProcessor在對象實例化之前將對象更改了。所以我們獲取到的是TestBean1。我們有了BeanFactoryPostProcessor,就可以在對象實例化到ioc容器之前對即將要實例化的對象做一些手腳。但是這僅僅是更新。那么我們要手動將對象注冊到BeanDefinition呢。下面的ImportBeanDefinitionRegistrar就發揮用處了
貌似說了這么多,都沒扯到今天的主題上,動態代理對象????,壓根沒有提到啊。不急,待會就來,我們是一步步慢慢接近主題了。
ImportBeanDefinitionRegistrar可以動態將自己的對象注冊到BeanDefinition,然后會spring的bean實例化流程,生成實例對象到ioc容器。
編寫測試Dao接口,為什么要是接口呢?因為我們要利用代理生成Dao的實例對象啊
public interface MyDao { void query(); }
編寫自定義Registrar
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { 這里用到前面定義的MyFactoryBean BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class); //生成beanDefinition GenericBeanDefinition beanDefinition = (GenericBeanDefinition)builder.getBeanDefinition(); //將beanDefinition注冊 beanDefinitionRegistry.registerBeanDefinition(MyDao.class.getName(),beanDefinition); } }
更改MyFactoryBean,動態代理生成接口MyDao對象
public class MyFactoryBean implements FactoryBean { @Override public Object getObject() throws Exception { //利用動態代理生成MyDao的實例對象 Object instance = Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class[]{MyDao.class}, new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(”執行業務邏輯“); return null; } }); return instance; } @Override public Class<?> getObjectType() { return MyDao.class; } }
自定義注解@MyScan,并通過@Import導入MyImportBeanDefinitionRegistrar。這樣就會被spring掃描到
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({MyImportBeanDefinitionRegistrar.class}) public @interface MyScan { }
最后一步,在項目啟動類加上@MyScan。并編寫測試。調用Mydao
MyDao myDao = SpringContextUtils.getBean(MyDao.class); myDao.execute(); //打印出”執行業務邏輯“
到這里我們就將動態代理的類交由ioc管理起來了。
我們引申一下。我們不是有一個MyDao嗎?并且在MyFactoryBean中代理實現的時候也是講其硬編碼寫死的。MyImportBeanDefinitionRegistrar中也是寫死的,這樣可不行,那么我們要怎么將其寫活呢。
在MyFactoryBean定義變量來接受class,并通過構造函數設置值。最后修改后的MyFactoryBean如下
public class MyFactoryBean implements FactoryBean { private Class classzz; public MyFactoryBean(Class classzz){ this.classzz = classzz; } @Override public Object getObject() throws Exception { //利用動態代理生成實例對象 Object instance = Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class[]{classzz.class}, new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(”執行業務邏輯“); return null; } }); return instance; } @Override public Class<?> getObjectType() { return this.classzz; } }
更改MyImportBeanDefinitionRegistrar邏輯,我們定義一個Class數據來模擬多個class。通過beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass.getTypeName());調用MyFactoryBean的有參構造函數生成MyFactoryBean。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { //這里將數組寫死了。我們可以定義一個包,掃描包下的所有接口class,這里就不做實現了,這里為了演示效果,多定義了一個接口MyDao1,跟MyDao定義相同的,代碼就不貼出來了。 Class[] classes = {MyDao.class,MyDao1.class}; for (Class aClass : classes) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class); GenericBeanDefinition beanDefinition = (GenericBeanDefinition)builder.getBeanDefinition(); //調用剛剛定義的MyFactoryBean有參構造函數 beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass.getTypeName()); beanDefinitionRegistry.registerBeanDefinition(aClass.getName(),beanDefinition); } } }
測試實例
MyDao myDao = SpringContextUtils.getBean(MyDao.class); myDao.query(); //執行業務邏輯 MyDao1 myDao1 = SpringContextUtils.getBean(MyDao1.class); myDao1.query(); //執行業務邏輯
有沒有感覺到有點類似mybatis了,接口Mapper,沒有任何實現,但是可以直接@Autowired進行調用,沒錯,就是在模擬Mybatis。不過,我們自己定義的@MyScan注解,它的是@MapperScan注解,后面參數為Mapper的包路徑,我們這里就沒有實現類,因為我們在MyImportBeanDefinitionRegistrar中定義數組來模擬包路徑掃描class了。下面再完善一下,我們調用了連個Dao都執行了相同的邏輯。應該執行不同的sql查詢才對啊。我們就來實現這點。
自定義@Select注解,這個注解就是用在Dao接口方法定義上的,value為sql語句
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Select { String value() default ""; }
在Dao接口中使用@Select注解
public interface MyDao { @Select("SELECT * FROM T1") void query(); } public interface MyDao1 { @Select("SELECT * FROM T2") void query(); }
在動態代理生成代理對象的InvocationHandler編寫具體獲取sql邏輯。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String value = method.getDeclaredAnnotation(Select.class).value(); System.out.println(value); return null; }
調用剛剛剛剛的測試方法,打印sql語句。
MyDao myDao = SpringContextUtils.getBean(MyDao.class); myDao.query(); //SELECT * FROM T1 MyDao1 myDao1 = SpringContextUtils.getBean(MyDao1.class); myDao1.query(); //SELECT * FROM T2
spring真是在高擴展這方面做得很好,第三方插件可以實現不同的接口,實現對spring進行擴展。就像Mybatis整合到Spring。我們在日常開發中,為了應付多變的需求,設計出易擴展的程序是非常有必要的。一味的按照需求實現硬編碼,后面業務變更,只有加班的份,然后就天天改,自己技術也局限了。整天被產品拖著鼻子走。天天加班,時間都花在重復的工作上了,對自己的成長沒有啥好處。
看完上述內容,你們掌握如何將動態代理對象交由Spring容器管理的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。