Spring IOC和AOP理解,以及怎么解決循環依賴、用到了哪些設計模式


  • IOC

IOC,控制反轉(Inversion of Control),就是把對象的創建(即bean的new操作),交給Spring來實現。

通過XML配置:bean標簽是用於配置被spring容器管理的bean信息,我們可以通過bean標簽,完成IOC的配置。

  • 使用默認無參構造函數來創建類對象,並存入spring容器
<bean id="userService" class="com.xcj.spring.service.UserServiceImpl"></bean>
public class UserServiceImpl implements UserService {
    
    
}

 

  • 靜態工廠方式
<!-- 使用StaticFactory類中的靜態方法createDemoService創建對象,並存入spring容器(簡單了解下,實際中基本不用) -->
<bean id="demoService" class="com.xcj.spring.factory.StaticFactory" factory-method="createDemoService"></bean>
public class StaticFactory {
    
    public static DemoService createDemoService() {
        
        return new DemoServiceImpl();
    }

}
public class DemoServiceImpl implements DemoService {

    
}

 

  • 實例工廠方式
<!-- 先把工廠的創建交給spring來管理,再在使用工廠的bean來調用里面的方法 -->
<bean id="instanceFactory" class="com.xcj.spring.factory.InstanceFactory"></bean>
<bean id="beanService" factory-bean="instanceFactory" factory-method="createBeanService"></bean>
public class InstanceFactory {

    public static BeanService createBeanService() {
        
        return new BeanServiceImpl();
    }
}
public class BeanServiceImpl implements BeanService {

    
}

 

DI,依賴注入(Dependency Injection),是spring IOC的具體實現,做的工作是Spring實例化bean對象后,對bean對象的屬性信息進行賦值的操作。

依賴指的是bean實例中的屬性,分為簡單類型(8種基本類型和String類型)的屬性、POJO類型的屬性、集合數組類型的屬性。

注入方式:構造器,set方法,注解,(還有使用p名稱空間注入數據,本質上還是調用set方法)。

  • 構造器注入
<bean id="userDemoService" class="com.xcj.spring.service.UserDemoServiceImpl">
    <constructor-arg name="id" value="1"></constructor-arg>
    <constructor-arg name="name" value="zhangsan"></constructor-arg>
</bean>
public class UserDemoServiceImpl implements UserDemoService {
    
    private int id;
    
    private String name;
        
    public UserDemoServiceImpl(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

 

  • set方法注入(手動裝配,即XML方式)
<bean id="demoSetService" class="com.xcj.spring.service.DemoSetServiceImpl">
    <!-- List類型的屬性 -->
    <property name="lists">
        <list>
            <value>11</value>
            <value>22</value>
        </list>
    </property>
    <!-- Set類型的屬性 -->
    <property name="sets">
        <set>
            <value>111</value>
            <value>222</value>
        </set>
    </property>
    <!-- Map類型的屬性 -->
    <property name="maps">
        <map>
            <entry key="111" value="111-01"/>
            <entry key="222" value="222-01"/>
        </map>
    </property>
    <!-- Properties類型 -->
    <property name="props">
        <props>
            <prop key="name">root</prop>
            <prop key="password">123456</prop>
        </props>
    </property>
</bean>
public class DemoSetServiceImpl implements DemoSetService {

    private List<Object> lists;

    private Set<Object> sets;

    private Map<String, Object> maps;

    private Properties props;

    public void setLists(List<Object> lists) {
        this.lists = lists;
    }

    public void setSets(Set<Object> sets) {
        this.sets = sets;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public void setProps(Properties props) {
        this.props = props;
    }

}

 

  • 注解(自動裝配)

(1)@Autowired,按類型注入,要唯一;如果遇到相同的,則要加上@Qualifer,表示在類型注入基礎上按名稱注入。

(2)@Resource,按照bean的name/id注入。

@Service("userService")
public class UserServiceImpl implements UserSetService{
    
    @Autowired
    private UserDao userDao;

}

 

那么問題:Spring怎么解決循環依賴的?通過三級緩存。

循環依賴:兩個或多個類之間,形成的互相引用,就是循環依賴。

  • 通過構造方法的循環依賴

構造器的循環依賴,是在bean的實例化new對象時產生的,這里會發生死鎖,構造器循環依賴無法解決,只能避免這種方式,盡量在設計時不出現循環依賴的問題,或者改成setter方法。

  • 通過setter方法的循環依賴

setter方法的循環依賴,是在bean的實例化后,對bean屬性進行填充時出現。這里需要說明,Spring中Bean需要初始化完成,才能對外使用,創建bean的過程包括實例化、屬性填充、初始化、放入singletonObjects集合。

Spring解決setter方法的循環依賴是通過三級緩存解決:

查找單例bean順序singletonObjects -> earlySingletonObjects ->  singletonFactories:

先從一級緩存singletonObjects查找,如果沒有且在創建中,則在二級緩存earlySingletonObjects中查找,還是找不到,且允許循環引用,則進入三級緩存singletonFactories中查找,找到會將其插入二級緩存earlySingletonObjects中,同時將其從三級緩存刪除。

添加Bean順序:

(1)new實例化后,如果一級緩存沒有,則bean會存放在三級緩存singletonFactories中,並嘗試刪除二級緩存中可能存在的bean實例;

(2)new實例化后,如果一級和二級緩存都沒有,則在三級緩存中找,找到后會放入二級緩存,並刪除三級緩存存放的bean實例或代理對象。

(3)如果bean創建完成,則會直接存放在一級緩存singletonFactories,同時刪除二級緩存和三級緩存。

  • singletonObjects:存放完整的Bean實例。

 

  • earlySingletonObjects:存放半成品Bean實例。

 

  • singletonFactories:存放實例對象工廠ObjectFactory。

 

  • AOP

AOP,面向切面編程(Aspect Oriented Programming),是一種編程范式,是OOP(面向對象編程)的延續,采用橫向抽取機制,補充了OOP縱向繼承體系無法解決的重復代碼優化方式。

譬如,假設有A,B有相同的邏輯,我們可以橫向抽取相同的部分出來,通過AOP思想和業務串聯起來,同時這是可插拔式的。

AOP思想的實現一般都是基於代理模式,給業務代碼進行功能增強,將業務代碼和系統代碼解耦。

如果目標對象的實現類實現了接口,Spring AOP 將會采用 JDK 動態代理來生成 AOP 代理類;

如果目標對象的實現類沒有實現接口,Spring AOP 將會采用 CGLIB 來生成 AOP 代理類。

動態代理說明,動態代理是在運行期間,針對目標對象進行動態代理。

關於這里涉及到的代理模式,可以看https://www.cnblogs.com/scorpio-cat/p/12715222.html。

(1)使用XML方式實現

public class AopAdvice {
    
    public void saveLog() {
        System.out.println("保存日志");
    }

}
<bean name="aopAdvice" class="com.xcj.spring.advice.AopAdvice"></bean>

<aop:config>
    <aop:aspect ref="aopAdvice">
        <aop:before method="saveLog" pointcut="execution(* com.xcj.spring..*Service.do*(..))" />
    </aop:aspect>
</aop:config>

 

(2)使用注解實現

@Component("aopDemoAdvice")
@Aspect
public class AopDemoAdvice {
    
    @Before(value="execution(* com.xcj.spring..*Service.do*(..))")
    public void saveLog() {
        System.out.println("保存日志");
    }
}
<!-- Spring AOP,通過動態代理技術實現的,而動態代理是基於反射 -->
<!-- 開啟注解並掃描指定包中帶有注解的類 -->
<context:component-scan base-package="com.xcj.spring.service"></context:component-scan>

<!-- 開啟AOP自動代理 -->
<aop:aspectj-autoproxy />

使用純注解方式,@EnableAspectJAutoproxy代替 <aop:aspectj-autoproxy /> ,@Component-scan代替 <context:component-scan> 

@Configuration
@ComponentScan(basePackages = "com.xcj.spring.service")
@EnableAspectJAutoProxy
public class SpringAOPConfiguration {

    
}

Spring幫我們實現了事務管理的系統代碼,我們可以通過配置,將業務代碼與事務管理整合在一起。當然,我們也可以自己編寫事務管理的代碼,然后通過AOP思想組合在一起。

Spring AOP的事務支持:Spring提供了事務管理接口PlatformTransactionManager,JDBC、MyBatis等根據這個接口實現了對應的事務管理器,如DataSourceTransactionManager。

(1)XML方式

public void doSave(User user) {
    
    userDao.save(user);
    
    LogInfo logInfo = new LogInfo();
    logInfo.setContent("新增User:"+user.getName);
    logDao.saveLog(logInfo);
    
}

 

<!-- 事務管理器 -->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<!-- 事務通知,配置事務相關屬性:方法、隔離級別、傳播行為 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="find*"   read-only="true" />
        <tx:method name="get*"    read-only="true"   propagation="SUPPORTS" />
        <tx:method name="check*"  read-only="true"   propagation="SUPPORTS" />
        <tx:method name="delete*" propagation="REQUIRED"/>
        <tx:method name="do*"     propagation="REQUIRED" />
        <tx:method name="submit" propagation="REQUIRED"/>
        <tx:method name="create" propagation="REQUIRED"/>
        <tx:method name="ido*"    propagation="REQUIRES_NEW" />
        <!-- 必須在事務中運行 -->
    </tx:attributes>
</tx:advice>

<!-- AOP配置 -->
<aop:config proxy-target-class="true">
        <aop:pointcut id="txPointCut" expression="execution(* com.xcj.spring..*Service.*(..))" />
        <aop:advisor id="serviceTx"  pointcut-ref="txPointCut" advice-ref="txAdvice" order="1" />
</aop:config>

 (2)注解方式,XML配置開啟事務注解,注解@Transaction。如需使用純注解,使用@EnableTransactionManagement

<!-- 配置事務管理器 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="dataSource" ref="dataSource" />
</bean>

 

關於AOP的一些問題:

1.什么時候產生代理對象?

IOC容器在創建Bean對象的時候,會觸發AOP,產生代理對象並放入IOC容器中。

2.JDK代理方式中,什么時候調用代理對象?

第一次調用目標類接口的實例方法的時候,就會去調用代理對象。

3.有沒有可能產生了代理對象,但從沒有調用過?

可能。

4.JDK代理方式中,代理對象的處理邏輯是什么?

InvocationHandler的invoke()方法,就是組織增強代碼和目標代碼的結合。

 5.Spring 用到了哪些設計模式?

  • 簡單工廠模式(BeanFactory、ApplicationContext
  • 工廠方法模式(將對象的創建及初始化職責交給工廠對象)
  • 單例模式(單例對象,提供了全局的訪問點BeanFactory)
  • 適配器模式(AdvisorAdapter,AOP用到的,BeforeAdvice、AfterAdvice、AfterReturningAdvice,Spring預定義的通知要通過對應的適配器,適配成 MethodInterceptor接口(方法攔截器)類型的對象(如:MethodBeforeAdviceInterceptor 負責適配 MethodBeforeAdvice))
  • 裝飾者模式(數據庫連接sessionFactory,dataSource)
  • 代理模式(AOP用到的)
  • 策略模式(SimpleInstantiationStrategy,實例化對象的時候用到的)
  • 觀察者模式(Spring 事件驅動模型:ApplicationEvent事件、ApplicationListener事件監聽者、ApplicationEventPublisher事件發布者)
  • 模板方法模式(JdbcTemplate、hibernateTemplate)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM