- 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)