IOC的分析:
IOC---控制反轉,也稱依賴倒置。
如何去理解控制反轉呢?
反轉:依賴物件的獲得被反轉,變為由自己建立,反轉為從IOC容器中獲取。
複製程式碼
帶來的好處:
1.程式碼更為簡潔,不需要再去new需要的物件
2.面向介面程式設計,使用類和具體類解耦,易擴充套件,替換實現者
3.方便進行AOP增強(沒有IOC就無法AOP)
複製程式碼
IOC容器做什麼工作?
負責建立,管理類例項,向使用者提供例項
複製程式碼
IOC容器就是工廠模式的例項,IOC容器也被稱為Bean工廠
IOC設計實現
IOC容器的工作:建立和管理Bean,它是一個工廠,負責對外提供Bean例項
複製程式碼
Bean:元件,類的物件
Q1:它應該具備什麼行為(對外介面)?
A:對外提供Bean例項,getBean()方法
複製程式碼
Q2:這個getBean()方法是否需要引數?需要幾個,又為什麼型別?
A:簡單工廠模式中,當工廠能建立很多類產品,如果需要某類產品,需要告訴工廠
複製程式碼
Q3:這個getBean()方法的返回值的型別?
A:各種型別的bean只能為Object<br><br>
複製程式碼
此時我們可以編出BeanFactory介面
public interface BeanFactory { Object getBean(String name) throws Exception;}
複製程式碼
Bean工廠如何知道如何建立Bean?
就是一個定義註冊,我們可以給它定義一個定義註冊介面,讓bean定義傳入bean工廠,告知工廠建立何種型別的Bean
複製程式碼
Bean定義註冊介面
public interface BeanDefinitionRegistry {}
複製程式碼
Q1:bean定義註冊介面中應定義些什麼方法?
註冊,獲取bean定義
複製程式碼
Q2:註冊的bean定義資訊如何區分?
每個Bean要有一個獨立的名稱
複製程式碼
此時我們可以編出BeanDefinitionRegistry介面
public interface BeanDefinitionRegistry {
/**
* 註冊Bean
* @param beanName
* @param beanDefinition
* @throws Exception
*/
void registerBeanDefinition(String beanName,BeanDefinition beanDefinition) throws Exception;
/**
* 獲取Bean
* @param beanName
* @return
*/
BeanDefinition getBeanDefinition(String beanName);
/**
* 判斷Bean是否已經被註冊
* @param beanName
* @return
*/
Boolean containsBeanDefinition(String beanName);
}
複製程式碼
bean定義
Q1:bean定義的用途是什麼?
告訴bean工廠該如何建立某類bean
複製程式碼
Q2:獲得類的例項的方式有哪些?
1.new 構造方法
2.工廠方法(靜態,成員方法)
複製程式碼
Q3:bean工廠幫我們建立bean時需要獲取哪些資訊?
1.new 構造方法(需要知道類名)
2.靜態工廠方法(需要知道工廠類名,工廠方法名)
3.成員工廠方法(需要知道工廠類名--->工廠bean名,工廠方法名)
複製程式碼
Q4:每次從bean工廠獲取bean例項時是否都要建立新的例項
否,因為有需要單例的情況
複製程式碼
Q5:bean定義是給bean工廠建立bean用的,那bean定義介面應向bean工廠提供哪些方法?
1.獲取bean的類名:getBeanClass()
2.獲取工廠方法名:getFactoryMethodName()
3.獲取工廠bean名:getFactoryBeanName()
4.是否是單例:getScope(){isSingleton(); isPrototype();}
複製程式碼
Q6:類物件交給IOC容器來管理,類物件的生命週期中還可能有什麼生命週期階段事情要做嗎?
建立後可能需要的初始化
銷燬時有可能出現的某些銷燬邏輯(比如釋放資源)
在bean定義提供讓使用者定製的初始化和銷燬方法即可(getInitMethodName(),getDestroyMethodName())
複製程式碼
此時可寫出bean定義介面程式碼:
public interface BeanDefinition {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
Class<?> getBeanClass();
String getScope();
boolean isSingleton();
boolean isPrototype();
String getFactoryBeanName();
String getFactoryMethodName();
String getInitMethodName();
String getDestoryMethodName();
//tips:java8開始就可以直接寫介面預設方法了
default boolean validate(){
//class沒指定,工廠bean或工廠method不指定皆為不合法情況
if (this.getBeanClass()==null){
if(StringUtils.isBlank(getFactoryBeanName())||StringUtils.isBlank(getFactoryMethodName())){
return false;
}
}
//class和工廠bean同時存在
if (this.getBeanClass()!=null && StringUtils.isNotBlank(getFactoryBeanName())){
return false;
}
return true;
}
}
複製程式碼
介面有了,現在我們來編寫一個通用的bean定義(這裡使用lombok外掛)
import lombok.Data;
import org.apache.commons.lang.StringUtils;
@Data
public class GeneralBeanDefinition implements BeanDefinition{
private Class<?> beanClass;
private String scope = BeanDefinition.SCOPE_SINGLETON;
private String factoryBeanName;
private String factoryMethodName;
private String initMethodName;
private String destroyMethodName;
public void setScope(String scope) {
if (StringUtils.isNotBlank(scope)){
this.scope = scope;
}
}
@Override
public Class<?> getBeanClass() {
return this.beanClass;
}
@Override
public String getScope() {
return this.scope;
}
@Override
public boolean isSingleton() {
return BeanDefinition.SCOPE_SINGLETON.equals(this.scope);
}
@Override
public boolean isPrototype() {
return BeanDefinition.SCOPE_PROTOTYPE.equals(this.scope);
}
@Override
public String getFactoryBeanName() {
return factoryBeanName;
}
@Override
public String getFactoryMethodName() {
return factoryMethodName;
}
@Override
public String getInitMethodName() {
return this.initMethodName;
}
@Override
public String getDestoryMethodName() {
return this.destroyMethodName;
}
}
複製程式碼
接下來該實現一個最基礎的DefaultBeanFactory讓它初步能工作起來
1.實現定義資訊註冊
Q1:bean定義資訊如何存放?
A:Map
複製程式碼
Q2:bean定義是否可以重名,重名時如何解決?
A:直接設計為不能重名
複製程式碼
2.實現bean工廠
Q1:建立的bean使用什麼存放,方便下次獲取?
Map
複製程式碼
Q2:在getBean方法中需要做什麼事情
建立bean例項,進行初始化
複製程式碼
知道這些之後,此時我們簡單完成一個DefaultBeanFactory
public class DefaultBeanFactory implements BeanFactory,BeanDefinitionRegistry, Closeable {
//common-logging包和log4j-api包配合即可
private final Log logger = LogFactory.getLog(getClass());
//考慮併發情況,256個前不需要進行擴容
private Map<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
private Map<String,Object> beanMap = new ConcurrentHashMap<>(256);
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws Exception {
//引數檢查
Objects.requireNonNull(beanName,"註冊bean需要輸入beanName");
Objects.requireNonNull(beanDefinition,"註冊bean需要輸入beanDefinition");
//檢驗給入的bean是否合法
if (!beanDefinition.validate()){
throw new Exception("名字為["+beanName+"]的bean定義不合法,"+beanDefinition);
}
if (this.containsBeanDefinition(beanName)){
throw new Exception("名字為["+beanName+"]的bean定義已經存在,"+this.getBeanDefinition(beanName));
}
this.beanDefinitionMap.put(beanName,beanDefinition);
}
@Override
public BeanDefinition getBeanDefinition(String beanName) {
return this.beanDefinitionMap.get(beanName);
}
@Override
public Boolean containsBeanDefinition(String beanName) {
return this.beanDefinitionMap.containsKey(beanName);
}
@Override
public Object getBean(String name) throws Exception {
return this.doGetBean(name);
}
//不需要判斷scope,因為只有單例bean才需要放入map中
//使用protected保證只有DefaultBeanFactory的子類可以呼叫該方法
protected Object doGetBean(String beanName) throws Exception{
Objects.requireNonNull(beanName,"beanName不能為空");
Object instance = beanMap.get(beanName);
if (instance != null){
return instance;
}
BeanDefinition beanDefinition = this.getBeanDefinition(beanName);
Objects.requireNonNull(beanDefinition,"beanDefinition不能為空");
Class<?> type = beanDefinition.getBeanClass();
//因為總共就只有3種方式,也不需要擴充或者是修改程式碼了,所以就不需要考慮使用策略模式了
if (type != null){
if (StringUtils.isBlank(beanDefinition.getFactoryMethodName())){
instance = this.createInstanceByConstructor(beanDefinition);
} else {
instance = this.createInstanceByStaticFactoryMethod(beanDefinition);
}
}else {
instance = this.createInstanceByFactoryBean(beanDefinition);
}
this.doInit(beanDefinition,instance);
if (beanDefinition.isSingleton()){
beanMap.put(beanName,instance);
}
return instance;
}
//構造方法來建立物件
private Object createInstanceByConstructor(BeanDefinition beanDefinition) throws IllegalAccessException, InstantiationException {
try{
return beanDefinition.getBeanClass().newInstance();
} catch (SecurityException e){
logger.error("建立bean的例項異常,beanDefinition"+beanDefinition,e);
throw e;
}
}
//靜態工廠方法(暫時不考慮帶引數)
private Object createInstanceByStaticFactoryMethod(BeanDefinition beanDefinition) throws Exception{
Class<?> type = beanDefinition.getBeanClass();
Method method = type.getMethod(beanDefinition.getFactoryMethodName(),null);
return method.invoke(type,null);
}
//工廠bean方法來建立物件(暫時不考慮帶引數)
private Object createInstanceByFactoryBean(BeanDefinition beanDefinition) throws Exception{
Object factoryBean = this.doGetBean(beanDefinition.getFactoryBeanName());
Method method = factoryBean.getClass().getMethod(beanDefinition.getFactoryMethodName(),null);
return method.invoke(factoryBean,null);
}
//初始化方法
private void doInit(BeanDefinition beanDefinition, Object instance) throws Exception{
if (StringUtils.isNotBlank(beanDefinition.getInitMethodName())){
Method method = instance.getClass().getMethod(beanDefinition.getInitMethodName(),null);
method.invoke(instance,null);
}
}
@Override
public void close() throws IOException {
//執行單例例項的銷燬方法
//遍歷map把bean都取出來然後呼叫每個bean的銷燬方法
for (Map.Entry<String,BeanDefinition> entry : this.beanDefinitionMap.entrySet()){
String beanName = entry.getKey();
BeanDefinition beanDefinition = entry.getValue();
if (beanDefinition.isSingleton() && StringUtils.isNotBlank(beanDefinition.getDestoryMethodName())){
Object instance = this.beanMap.get(beanName);
try {
Method method = instance.getClass().getMethod(beanDefinition.getDestoryMethodName(),null);
method.invoke(instance,null);
}catch (NoSuchMethodException|SecurityException|IllegalAccessException|IllegalArgumentException|InvocationTargetException e){
logger.error("執行bean["+beanName+"] "+beanDefinition+"的銷燬方法異常",e);
}
}
}
}
}
複製程式碼
tips:此時單例的執行緒安全還無法保證!!!
擴充套件DefaultBeanFactory
Thinking:對於單例bean我們是否可以提前例項化,這有什麼好處?
A:可以提前例項化,空間換時間的方法,啟動慢使用快併執行緒安全
複製程式碼
如果要實現提前例項化單例bean的功能,程式碼如下
public class PreBuildBeanFactory extends DefaultBeanFactory{
private Log logger = LogFactory.getLog(getClass());
private List<String> beanNames = new ArrayList<>();
@Override
public void registerBeanDefinition(String beanName,BeanDefinition beanDefinition) throws Exception{
super.registerBeanDefinition(beanName,beanDefinition);
synchronized (beanNames){
beanNames.add(beanName);
}
}
//使用synchronized解決執行緒安全問題
public void preInstantiateSingletons() throws Exception{
synchronized (beanNames){
for (String name : beanNames){
BeanDefinition beanDefinition = this.getBeanDefinition(name);
if (beanDefinition.isSingleton()){
this.doGetBean(name);
if (logger.isDebugEnabled()){
logger.debug("preInstantiate:name="+name+" "+beanDefinition);
}
}
}
}
}
}
複製程式碼
此時我們已經初步完成了一個IOC容器,可以來個簡單的測試
編寫一個bean1和一個bean1的工廠,還有一個test方法
Bean1.java
public class Bean1 {
public void doSomething(){
System.out.println(System.currentTimeMillis()+" "+this);
}
public void init(){
System.out.println("bean1的init已執行");
}
public void destroy(){
System.out.println("bean1的destroy已執行");
}
}
複製程式碼
Bean1Factory.java
public class Bean1Factory {
public static Bean1 getBean1(){
return new Bean1();
}
public Bean1 getOtherBean1(){
return new Bean1();
}
}
複製程式碼
測試用例DefaultBeanFactoryTest
public class DefaultBeanFactoryTest {
static DefaultBeanFactory defaultBeanFactory = new DefaultBeanFactory();
@Test
public void testRegist() throws Exception{
GeneralBeanDefinition generalBeanDefinition = new GeneralBeanDefinition();
generalBeanDefinition.setBeanClass(Bean1.class);
generalBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
generalBeanDefinition.setInitMethodName("init");
generalBeanDefinition.setDestroyMethodName("destroy");
defaultBeanFactory.registerBeanDefinition("bean1",generalBeanDefinition);
}
@Test
public void testRegistStaticFactoryMethod() throws Exception{
GeneralBeanDefinition generalBeanDefinition = new GeneralBeanDefinition();
generalBeanDefinition.setBeanClass(Bean1Factory.class);
generalBeanDefinition.setFactoryMethodName("getBean1");
defaultBeanFactory.registerBeanDefinition("staticBean1",generalBeanDefinition);
}
@Test
public void testRegistFactoryMethod() throws Exception{
GeneralBeanDefinition generalBeanDefinition = new GeneralBeanDefinition();
generalBeanDefinition.setBeanClass(Bean1Factory.class);
String factoryBeanName = "factory";
defaultBeanFactory.registerBeanDefinition(factoryBeanName,generalBeanDefinition);
generalBeanDefinition = new GeneralBeanDefinition();
generalBeanDefinition.setFactoryBeanName(factoryBeanName);
generalBeanDefinition.setFactoryMethodName("getOtherBean1");
generalBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
defaultBeanFactory.registerBeanDefinition("factoryBean",generalBeanDefinition);
}
@AfterClass
public static void testGetBean() throws Exception{
System.out.println("構造方法方式···");
for (int i = 0;i<3;i++){
Bean1 bean1 = (Bean1) defaultBeanFactory.getBean("bean1");
bean1.doSomething();
}
System.out.println("靜態工廠方法方式···");
for (int i = 0;i<3;i++){
Bean1 bean1 = (Bean1) defaultBeanFactory.getBean("staticBean1");
bean1.doSomething();
}
System.out.println("工廠方法方式···");
for (int i = 0;i<3;i++){
Bean1 bean1 = (Bean1) defaultBeanFactory.getBean("factoryBean");
bean1.doSomething();
}
defaultBeanFactory.close();
}
}
複製程式碼
此測試用例的執行結果為(注意工廠方法時我們設定了多例,所以bean應該每個都不同):
構造方法方式···
bean1的init已執行
1555370012087 MySpring.Bean1@5e8c92f4
1555370012088 MySpring.Bean1@5e8c92f4
1555370012088 MySpring.Bean1@5e8c92f4
靜態工廠方法方式···
1555370012088 MySpring.Bean1@50134894
1555370012088 MySpring.Bean1@50134894
1555370012088 MySpring.Bean1@50134894
工廠方法方式···
1555370012089 MySpring.Bean1@2957fcb0
1555370012089 MySpring.Bean1@1376c05c
1555370012090 MySpring.Bean1@51521cc1
bean1的destroy已執行複製程式碼