SpringIOC的特點(diǎn)
在接觸Spring的過程中镰惦,聽到最多的無非兩個(gè)名詞靠欢,一個(gè)是控制反轉(zhuǎn)一個(gè)是依賴注入舵匾。實(shí)際這是一個(gè)意思飒炎,控制反轉(zhuǎn)代表原來由程序本身去控制對象之間的依賴關(guān)系的這種格局被反轉(zhuǎn)了诡曙,通過第三方容器(IOC)去完成控制這些對象的依賴的關(guān)系并對它們進(jìn)行集中管理臀叙。
依賴注入:獲得依賴對象的過程由自身管理變?yōu)榱擞蒊OC容器主動(dòng)注入,就是由IOC容器在運(yùn)行期間价卤,動(dòng)態(tài)地將某種依賴關(guān)系注入到對象之中劝萤。
SpringAOP的特點(diǎn)
AOP最多聽到的就是面向切面編程,那對于這個(gè)名詞慎璧,我第一次聽到的時(shí)候也是不能理解的。
下面用一個(gè)圖和語言來進(jìn)行描述:
在一個(gè)項(xiàng)目中和我們業(yè)務(wù)邏輯和通用的邏輯區(qū)分開來厌处,比如我們的一個(gè)系統(tǒng)需要記錄日志岁疼,記錄日志這個(gè)事情是通用的阔涉,不管你做什么系統(tǒng)一般都會(huì)涉及瑰排。那么這一塊就通過AOP來統(tǒng)一集中實(shí)現(xiàn),統(tǒng)一管理暖侨。
生活中的一個(gè)例子椭住,你去吃飯之前肯定要洗手函荣,飯后肯定要擦嘴扳肛。那么挖息,不管你吃什么飯?jiān)谀膫€(gè)地方吃。這些通用的過程你都要執(zhí)行绪抛。那么集中抽象出來這些方法幢码,也就形成了AOP症副。
SpringIOC容器加載Bean的過程
1.第一步 IOC容器
把xml文件位置信息保存贞铣,然后調(diào)用refresh方法去重新初始化一個(gè)新的IOC容器辕坝,Refresh方法中使用obtainFreshBeanFactory去獲取,后面的代碼是一些BeanFactory創(chuàng)建后的后處理過程
obtainFreshBeanFactory方法里面琳袄,我們看到第一行調(diào)用refreshBeanFactory的方法去創(chuàng)建窖逗。
在方法中去loadBeanDefintions(),使用XMLReader去解析我們的xml配置文件
后面省略一些源碼的步驟用含,主要做的就是對xml文件進(jìn)行解析成我們要的BeanDefinitions啄骇,處理每個(gè)Bean元素以及元素中的屬性值缸夹。最后把beanDefinition注冊進(jìn)我們的BeanFactory中虽惭,
2.注入依賴
AOP的兩種實(shí)現(xiàn)方式 以及小例子
主要是兩種芽唇,一種是JDK動(dòng)態(tài)代理匆笤,一種是Cglib代理炮捧。
兩者的區(qū)別:
1.JDK動(dòng)態(tài)代理只能代理實(shí)現(xiàn)了接口的類咆课,動(dòng)態(tài)代理類的字節(jié)碼在程序運(yùn)行時(shí)由Java反射機(jī)制動(dòng)態(tài)生成傀蚌。
2.Cglib是可以代理沒有實(shí)現(xiàn)接口的類,cglib是針對類來實(shí)現(xiàn)代理的撩幽,他的原理是對指定的目標(biāo)類生成一個(gè)子類窜醉,并覆蓋其中方法實(shí)現(xiàn)增強(qiáng)榨惰,所以不能對final修飾的類進(jìn)行代理静汤。底層采用ASM實(shí)現(xiàn)虫给。
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();
// 設(shè)置自己的父類
enhancer.setSuperclass(target.getClass());
// 關(guān)聯(lián)的要使用哪個(gè)對象的回調(diào)函數(shù) 這里指向自己這個(gè)對象的回調(diào) 那么就是下面這個(gè)方面了
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("事務(wù)開始...");
// 調(diào)用父類函數(shù)
arg3.invokeSuper(arg0, arg2);
System.out.println("事務(wù)結(jié)束....");
return null;
}
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
AddBookImp oBookImp = (AddBookImp) cglibProxy.getInstance(new AddBookImp());
oBookImp.addbook();
}
}
JDK動(dòng)態(tài)代理:
//操作定義
public interface SubjectOperations {
// 打印操作
public void print();
// 打印輸入字符串操作
public void printfStr(String string);
}
public class RealSubject implements SubjectOperations {
@Override
public void print() {
System.out.println("我實(shí)現(xiàn)了接口 完成這個(gè)打印操作");
}
@Override
public void printfStr(String string) {
// TODO Auto-generated method stub
System.out.println("打印輸入的內(nèi)容: " + 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("在目標(biāo)方法調(diào)用前執(zhí)行.....");
method.invoke(ImpClass, args);
System.out.println("在目標(biāo)方法調(diào)用后執(zhí)行....");
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);
// 轉(zhuǎn)化成接口 只能代理實(shí)現(xiàn)了接口的類
SubjectOperations pSubject1 = (SubjectOperations) handler.bind(subject, new LoggerImp());
System.out.println(pSubject1.getClass().getName());
pSubject1.print();
pSubject1.printfStr("YYYYY");
}
}
面試官說讓你講講IOC和AOP時(shí),他們想了解什么瓷式?我應(yīng)該怎么回答
這個(gè)問題我也很頭痛贸典,這里我找到一個(gè)模版瓤漏,我打算以后就這樣回答。大家也可以參考一下:
我的回答:
Spring是一套為了解決企業(yè)應(yīng)用開發(fā)的復(fù)雜性而創(chuàng)建的框架,特點(diǎn)是分層的架構(gòu)榨呆,允許用戶在不同層面使用不同的組件進(jìn)行組合庸队。同時(shí)通過IOC容器來降低耦合彻消,簡化開發(fā)宾尚。利用AOP來進(jìn)行切面編程統(tǒng)一管理通用模塊煌贴。
我在工作中用到過Spring的IOC容器和AOP面向切面編程,在項(xiàng)目中使用IOC容器幫我很好的理清各個(gè)業(yè)務(wù)對象的關(guān)系怠肋,同時(shí)方便不同組件的更替笙各。在做接口權(quán)限驗(yàn)證時(shí)杈抢,使用AOP切面編程幫助我能很快的對需要進(jìn)行驗(yàn)證的接口完成驗(yàn)證功能的實(shí)現(xiàn)春感。并且統(tǒng)一進(jìn)行管理,提高開發(fā)效率嫩实。
SpringMVC的大致實(shí)現(xiàn)過程
1.用戶發(fā)起一個(gè)request請求宰缤,如果有多個(gè)DispatcherServlet,則通過Servletmapping去指定執(zhí)行的DispatcherServlet朦乏。
2.DispatcherServlet把根據(jù)URL請求呻疹,去HandlerMapping中查找注冊了的URL映射刽锤,并返回相應(yīng)的Handler(一個(gè)Controller并思,多個(gè)攔截器)宋彼,給DispatcherServlet颅筋。
3.DispatcherServlet傳遞Handler給HandlerAdapter去執(zhí)行议泵,返回一個(gè)ModelAndView先口。
4.DispatcherServlet把ModelAndView傳遞給視圖解析器去解析碉京,返回一個(gè)視圖view谐宙。
5.組裝上Model數(shù)據(jù)后變成Response請求返回給客戶端凡蜻。
SpringIOC和AOP中用到的設(shè)計(jì)模式
簡單工廠
在Spring中經(jīng)常利用BeanFactory的getBean方法去獲取Bean就是一個(gè)簡單工廠的設(shè)計(jì)模式的實(shí)現(xiàn),通過Bean的ID去獲取這個(gè)對象的實(shí)例兑巾。Bean的ID一般配置在XML文件中
工廠方法
在工廠方法模式中蒋歌, Spring不會(huì)直接利用反射機(jī)制創(chuàng)建bean對象堂油, 而是會(huì)利用反射機(jī)制先找到Factory類府框,然后利用Factory再去生成bean對象寓免。
而Factory Mothod方式也分兩種袜香, 分別是靜態(tài)工廠方法 和 實(shí)例工廠方法蜈首。
靜態(tài)工廠方法
定義一個(gè)類
public class Car {
private int id;
private String name;
private int price;
//省略構(gòu)造函數(shù) getter和setter函數(shù)
}
定義一個(gè)靜態(tài)工廠類
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踩寇,而是生產(chǎn)Bean的工廠俺孙,然后通過工廠方法去創(chuàng)建,有一點(diǎn)不好的地方就是map的初始化在程序中進(jìn)行荣茫,耦合度相對高
<!--
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>
實(shí)例工廠
我們創(chuàng)建一個(gè)工廠,這次獲取car的方法不是靜態(tài)的了咧欣。
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
單例模式
保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)狸演。
spring中的單例模式完成了后半句話宵距,即提供了全局的訪問點(diǎn)BeanFactory吨拗。但沒有從構(gòu)造器級別去控制單例劝篷,這是因?yàn)閟pring管理的是是任意的java對象娇妓。
下面問答中會(huì)專門說一下Spring中的單例模式
核心提示點(diǎn):Spring下默認(rèn)的bean均為singleton哈恰,可以通過singleton=“true|false” 或者 scope=“蕊蝗?”來指定
代理模式
為其他對象提供一種代理以控制對這個(gè)對象的訪問蓬戚。 從結(jié)構(gòu)上來看和Decorator模式類似子漩,但Proxy是控制幢泼,更像是一種對功能的限制缕棵,而Decorator是增加職責(zé)。
spring的Proxy模式在aop中有體現(xiàn)篙程,比如JdkDynamicAopProxy和Cglib2AopProxy虱饿。
觀察者模式
定義對象間的一種一對多的依賴關(guān)系氮发,當(dāng)一個(gè)對象的狀態(tài)發(fā)生改變時(shí)宾娜,所有依賴于它的對象都得到通知并被自動(dòng)更新扇售。
spring中Observer模式常用的地方是listener的實(shí)現(xiàn)浓镜。如ApplicationListener困乒。ServletContextListener
servlet和Filter初始化前和銷毀后娜搂,都會(huì)給實(shí)現(xiàn)了servletContextListener接口的監(jiān)聽器發(fā)出相應(yīng)的通知百宇。
模板方法
定義一個(gè)操作中的算法的骨架携御,而將一些步驟延遲到子類中啄刹。Template Method使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟誓军。
Template Method模式一般是需要繼承的昵时。這里想要探討另一種對Template Method的理解。spring中的JdbcTemplate,在用這個(gè)類時(shí)并不想去繼承這個(gè)類征绸,因?yàn)檫@個(gè)類的方法太多管怠,但是我們還是想用到JdbcTemplate已有的穩(wěn)定的渤弛、公用的數(shù)據(jù)庫連接她肯,那么我們怎么辦呢晴氨?我們可以把變化的東西抽出來作為一個(gè)參數(shù)傳入JdbcTemplate的方法中籽前。但是變化的東西是一段代碼枝哄,而且這段代碼會(huì)用到JdbcTemplate中的變量挠锥。怎么辦瘪贱?那我們就用回調(diào)對象吧菜秦。在這個(gè)回調(diào)對象中定義一個(gè)操縱JdbcTemplate中變量的方法,我們?nèi)?shí)現(xiàn)這個(gè)方法眨攘,就把變化的東西集中到這里了。然后我們再傳入這個(gè)回調(diào)對象到JdbcTemplate该肴,從而完成了調(diào)用。這可能是Template Method不需要繼承的另一種實(shí)現(xiàn)方式吧涎嚼。
不用繼承的模版方法
繼承的模版方法立哑,比如常見
我們定義一個(gè)算法的執(zhí)行過程刁憋,里面會(huì)調(diào)用4個(gè)小步驟木蹬,具體的小步驟的實(shí)現(xiàn)我們交過實(shí)現(xiàn)的子類去完成至耻。然后用戶調(diào)用整個(gè)process方法就能實(shí)現(xiàn)功能。
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 的單例模式是怎么實(shí)現(xiàn)的
IOC的單例模式不是使用的懶漢式或者餓漢式镊叁,使用的是
單例注冊表尘颓,通過把Bean的名稱和對象組成的key-value注冊進(jìn)HashMap中。每次需要獲取相應(yīng)的類時(shí)晦譬,根據(jù)名稱去獲取疤苹,如果沒有這個(gè)bean就去讀取Bean的定義敛腌。估計(jì)type是需要單例還是多例卧土,如果是單例注冊進(jìn)入表并返回,如是多例不注冊像樊,創(chuàng)建一個(gè)Bean并返回尤莺。
public abstract class AbstractBeanFactory implements ConfigurableBeanFactory{
/**
* 充當(dāng)了Bean實(shí)例的緩存,實(shí)現(xiàn)方式和單例注冊表相同
*/
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名有非法字符(或則做轉(zhuǎn)碼)
String beanName=transformedBeanName(name);
Object bean=null;
//手工檢測單例注冊表
Object sharedInstance=null;
//使用了代碼鎖定同步塊颤霎,原理和同步方法相似,但是這種寫法效率更高
synchronized(this.singletonCache){
sharedInstance=this.singletonCache.get(beanName);
}
if(sharedInstance!=null){
...
//返回合適的緩存Bean實(shí)例
bean=getObjectForSharedInstance(name,sharedInstance);
}else{
...
//取得Bean的定義
RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);
...
//根據(jù)Bean定義判斷,此判斷依據(jù)通常來自于組件配置文件的單例屬性開關(guān)
//<bean id="date" class="java.util.Date" scope="singleton"/>
//如果是單例友酱,做如下處理
if(mergedBeanDefinition.isSingleton()){
synchronized(this.singletonCache){
//再次檢測單例注冊表
sharedInstance=this.singletonCache.get(beanName);
if(sharedInstance==null){
...
try {
//真正創(chuàng)建Bean實(shí)例
sharedInstance=createBean(beanName,mergedBeanDefinition,args);
//向單例注冊表注冊Bean實(shí)例
addSingleton(beanName,sharedInstance);
}catch (Exception ex) {
...
}finally{
...
}
}
}
bean=getObjectForSharedInstance(name,sharedInstance);
}
//如果是非單例晴音,即prototpye,每次都要新創(chuàng)建一個(gè)Bean實(shí)例
//<bean id="date" class="java.util.Date" scope="prototype"/>
else{
bean=createBean(beanName,mergedBeanDefinition,args);
}
}
...
return bean;
}
}
如何保證IOC中有狀態(tài)Bean的線程安全
有狀態(tài)Bean就是一些Bean實(shí)例中含有一些非線程安全的成員變量缔杉,那么當(dāng)多個(gè)線程去同時(shí)操作這個(gè)Bean時(shí)锤躁,就有可能導(dǎo)致對這些變量的操作出現(xiàn)問題。
使用ThreadLocal類可以做到或详,ThreadLocal會(huì)為變量在每個(gè)線程中創(chuàng)建本地變量副本系羞,那么每個(gè)線程可以訪問自己內(nèi)部的副本變量。大家互不干擾鸭叙,以此達(dá)到線程安全的目的觉啊。
最常見的數(shù)據(jù)庫連接管理類:
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個(gè)方法都沒有進(jìn)行同步沈贝,很可能在openConnection方法中會(huì)多次創(chuàng)建connect;第二勋乾,由于connect是共享變量宋下,那么必然在調(diào)用connect的地方需要使用到同步來保障線程安全,因?yàn)楹芸赡芤粋€(gè)線程在使用connect進(jìn)行數(shù)據(jù)庫操作辑莫,而另外一個(gè)線程調(diào)用closeConnection關(guān)閉鏈接学歧。
使用ThreadLocal后:
當(dāng)多個(gè)線程去取session的時(shí)候,都會(huì)去拿自己線程的本地變量副本各吨,沒有就創(chuàng)建一個(gè)注冊進(jìn)去并返回枝笨。
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文件進(jìn)進(jìn)行依賴配置。之后只需要在程序中創(chuàng)建一個(gè)BeanFactory屉更,加載XML文件徙融,getBean就能獲得我們要的實(shí)例了
AOP使用Aspectj:
定義一個(gè)Advice類,里面進(jìn)行一個(gè)或多個(gè)pointcut的定義瑰谜,以及在哪個(gè)pointcut下使用那些方法欺冀,
定義的pointcut方法本身只是用來標(biāo)記的,用于指定在哪里進(jìn)行一個(gè)切入萨脑,比如這里我使用的是within代表切入NeedLogService類中的所有方法
有執(zhí)行前的隐轩,有執(zhí)行后的,有異常時(shí)的
當(dāng)我們調(diào)用這個(gè)類的方法時(shí)渤早,就會(huì)發(fā)現(xiàn)被增強(qiáng)了
參考文章:
Spring IOC核心源碼學(xué)習(xí)
使用 IoC 反轉(zhuǎn)控制的三種設(shè)計(jì)模式
Spring的單例模式底層實(shí)現(xiàn)
Spring中Singleton模式的線程安全
【SSH進(jìn)階之路】Spring的AOP逐層深入——采用注解完成AOP(七)
深入解析spring中用到的九種設(shè)計(jì)模式
Spring 通過工廠方法(Factory Method)來配置bean