comi single blog(歡迎訪問我的個人博客)
Spring
優勢
方便解耦
AOP編程的支持
聲明式事務的支持
方便程序測試
耦合:程序間的依賴關係
包括:
類之間的依賴關係
方法之間的依賴
解耦:
降低程序間的 依賴關係
實際開發中:
應做到編譯器不依賴,運行時才依賴。
解耦思路:
第一步:使用反射來創建對象,而避免使用new 關鍵字
第二步:通過讀取配置文件來獲取創建對象的全限定類名。
bean的配置
bean:在計算機英語中,有可重用組件的含義
javaBean:用Java語言編寫的可重用組件
不等於實體類(即:person類、pet類等),且遠大於實體類
bean工廠的對象:用於創建service和dao對象的。
如何通過bean工程創建對象(解耦思路相同)?
-
需要一個配置文件來配置我們的service和dao。(配置的內容,唯一標識 = 全限定類名,通過key:value的方式可以實現)
# properties 文件 accountDao = club.twzw.myblog.dao accountService = club.twzw.myblog.service
-
通過讀取配置文件的內容,反射創建對象。
具體細節實現:
public class BeanFactory{ //定義一個Properties ; private static Properties props; //定義一個Map,用於存放我們要創建的對象,我們把它稱爲容器。 private statis Map<String,Objects> beans; //使用靜態代碼塊爲Properties對象賦值 //初始化,且該工廠模式爲單例模式 static{ try{ //實例化對象 props = new Properties(); //獲取properties文件的流對象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); props.load(in); //實例化容器 beans = new HashMap<String,Objects>(); //取出配置文件中所有key Enumeration keys = props.keys(); //遍歷枚舉 while(keys.hasMoreElements()){ String key = keys.nextElement().toString(); // 根據key獲取value String beanPath = props.getProperty(key); Object value = Class.forName(beanPath).newInstance(); beans.put(key,value); } }catch(Exception e){ throw new ExceptionInInitializerError("初始化properties錯誤!"); } } // 獲取對象 public static Obejct getBean(String beanName){ return beans.get(beanName); } }
其他耦合地方進行調用
之前
private AccountDao dao = new AccountDaoImpl();
之後
private AccountDao dao = (AccountDao)BeanFactory.getBean("accountDao");
-
配置文件可以爲xml和properties,甚至是yaml。
IOC (inversion of control)
思想:反轉資源獲取的方向
在*容器主動的將資源推送給他所管理的組件***,組件所要做的僅是選擇一種合適的方式來接收資源,降低程序間的依賴關係,並不能解耦
IOC分析:
**new **的方式 :(主動尋找資源,明顯的依賴關係,且無法消除,完全自主,獨立的控制權)
private AccountDao dao = new AccountDaoImpl();
工廠模式:(可降低程序間依賴關係,但無法得知獲取的資源是否可用)
private AccountDao dao = (AccountDao)BeanFactory.getBean("accountDao");
spring實現
-
創建xml文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <bean id="accountDao" class="club.twzw.dao.impl.accountDaoImpl"> </bean> </beans>
-
在視圖層(假裝是)調用
public class Client{ public static void main(String[] args){ //1. 獲取核心容器對象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2. 根據id獲取bean對象 AccountDao dao = ac.getBean("accountDao",AccountDao.class); //或 AccountDao dao = (AccountDao)ac.getBean("accountDao"); System.out.println(dao); } }
ApplicationContext的三個常用實現類:
- ClassPathXmlApplicationContext : 它可以加載==類路徑==下的配置文件,要求配置文件必須在類路徑下。
- FileSystemXmlApplicationContext : 它可以加載==磁盤任意路徑下==的配置文件。(必須要有==訪問權限==)
- AnnotationConfigApplicationContext : 它用於讀取註解創建容器的。
核心容器引發的問題
-
ApplicationContext:(單例對象適用)—實際開發更多采用ApplicationContext
它在構建核心容器時,創建對象採取的策略是立即加載的方式。只要一讀讀取完就創建配置文件中配置的對象
-
BeanFactory:(多例對象適用)
它在構建核心容器時,創建對象採取的策略是延遲加載的方式。也就是說,什麼時候根據id獲取對象了,什麼時候才真正的創建對象。
創建bean的三種方式
-
使用默認構造函數創建,在spring的配置文件中使用bean標籤,配置id和class屬性之後,且沒有其他屬性和標籤時,採用的是默認構造函數創建bean對象,此時如果類中沒有默認的構造函數,則對象無法創建。
<bean id="accountService" class="club.twzw.service.Impl.AccountServiceImpl"></bean>
該句主要是去尋找默認構造函數
public class AccountServiceImpl implements accountService{ public AccountServiceImpl (){ } }
-
使用普通工廠中的方法創建對象 (使用某個中的方法創建對象並存入spring容器)
模擬一個工廠類InstanceFactory(該類可能是存在於jar包中,我們無法通過修改源碼的方式來提供默認構造函數)
public class InstanceFactory{ public AccountService getAccountService(){ return new AccountServiceImpl(); }
<bean id="InstanceFactory" class="club.twzw.factory.InstanceFactory"></bean> <bean id="AccountService" factory-bean="InstanceFactory" factory-method="getAccountService"></bean>
-
使用靜態工廠中的靜態方法創建對象
public class InstanceFactory{ public static AccountService getAccountService(){ return new AccountServiceImpl(); }
<bean id="accountService" class="club.twzw.factory.InstanceFactory" factory-method="getAccountService"></bean>
bean標籤的作用範圍
bean標籤的scope屬性:
-
作用:指定bean標籤的作用範圍。
-
取值:
- singleton 單例(默認值)
- prototype 多例
- request 作用於web應用的請求範圍
- session 作用與web應用的繪畫範圍
- global-session 作用於集羣環節的會話範圍(負載均衡)
bean的生命週期
- 單例
- 出生:當容器創建時,對象創建
- 活着:只要容器還在,對象一直都在
- 銷燬:容器銷燬,對象消亡。
- 總結:單例對象的生命週期和容器相同
- 多例
- 出生: 當我們使用對象時spring框架爲我們創建
- 活着:在使用過程中
- 銷燬:當對象長時間不用且沒有別的對象引用時,由java垃圾回收器回收
DI (dependency injection)
組件以先定義好的方式(例如:setter)來接受來自容器的資源注入
依賴關係:在當前類需要調用其他類的對象,由spring爲我們提供,我們只需要在配置文件中說明依賴關係的維護。
依賴關係的管理:以後都交給spring來維護
依賴注入數據類型:
- 基本類型和string
- 其他bean類型(在配置文件中或者註解配置過的bean)
- 複雜類型/集合類型
注入方式:
- 使用構造函數提供
- 使用set方法提供
- 使用註解提供
一個bean組件
public class AccountServiceImpl implements accountService{
private String name;
private Integer age;
private Date date;
public AccountServiceImpl (String name,Integer age,Date date){
this.name = name;
this.age = age;
this.date = date;
}
}
構造器注入
使用標籤constructor-arg,位置:bean標籤的內部
標籤屬性:
- type:用於指定要注入的數據的數據類型
- index:用於指定要注入的數據給構造函數中指定索引位置的參數賦值。索引位置從0開始
- name:用於指定給構造函數中指定名稱參數賦值
- value:用於提供基本類型和String類型的數據
- ref:用於指定其他的bean類型數據,它指的就是Spring的Ioc核心容器出現過的bean對象
<bean id="accountService" class="club.twzw.service.Impl.AccountServiceImpl">
<constructor-arg name="name" value="comi"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="name" ref="currentTime"></constructor-arg>
</bean>
<!--處理特殊類型如Date,可以使用ref的方式-->
<bean id="currentTime" class="java.util.Date"></bean>
優勢:
在獲取bean對象是注入的數據是必須的操作,否則對象無法創建成功。
弊端:
改變了bena對象的實例化的方式,是我們在創建對象時,如果不使用這些數據,也必須提供數據。
set方法注入(更常用)
使用標籤property,位置:bean標籤的內部
標籤屬性:
- name:找指定注入時所調用的set方法名稱
- value:用於提供基本類型和String類型的數據
- ref:用於指定其他的bean類型數據,它指的就是Spring的Ioc核心容器出現過的bean對象
<bean id="accountService" class="club.twzw.service.Impl.AccountServiceImpl">
<property name="name" value="comi"></property>
<property name="age" value="18"></property>
<property name="date" value="currentTime"></property>
</bean>
<!--處理特殊類型如Date,可以使用ref的方式-->
<bean id="currentTime" class="java.util.Date"></bean>
優勢:
創建對象時沒有明確的限制
弊端:
如果有一個成員必須有值,set方法無法保證一定注入,即未使用property標籤
複雜類型注入
public class AccountServiceImpl implements accountService{
private Map<String,String> myMap;
private List<String> myList;
private Set<String> mySet;
private Properties myProps;
private String[] mystrs;
//省略set方法
}
集合類型注入
用於給List結構集合注入的標籤:
- list
- array
- set
用於給map結構集合注入的標籤:
- map
- props
結構相同,標籤可以互換
<bean id="accountService" class="club.twzw.service.Impl.AccountServiceImpl">
<property name="mystrs">
<array>
<value>a</value>
<value>b</value>
<value>c</value>
</array>
</property>
<property name="myMap">
<map>
<entry key="testA" value="11"></entry>
<entry key="testB">
<value>bb</value>
</entry>
</map>
</property>
<property name="mySet">
<set>
<value>a</value>
<value>b</value>
<value>c</value>
</set>
</property>
<property name="myList">
<list>
<value>a</value>
<value>b</value>
<value>c</value>
</list>
</property>
<property name="myList">
<props>
<prop key="testA">a</prop>
<prop key="testB">b</prop>
<prop key="testC">c</prop>
</props>
</property>
</bean>
![3d7dc754.png](D:\notes\3d7dc754.png)
spring基於註解的Ioc及Ioc案例
Ioc常用註解
使用前需要配置xml的包掃描功能
<!-- 需要添加xmlns:context等約束-->
<context:component-scan base-package="club.twzw"></context:component-scan>
-
用於創建對象的註解 == 作用相同
-
@Component:用於把當前對象存入spring容器中
- @Component(value=“account”):指定了Component的id,如果不指定默認爲該類的首字母小寫
-
@Controller:表現層
-
@Service:業務層
-
@Repository:持久層
-
-
用於注入數據 == 標籤內部的
-
@Autowired:只要容器中有唯一的一個bean對象類型和要注入的變量類型匹配,就可以注入成功
-
@Qualifier:按照類中注入的基礎之上再按照名稱注入。他給類成員注入時不能單獨使用(需要和@Autowired一起使用),但是再給方法參數注入時,可以單獨使用。
-
//假設有兩個accountDao的實現類account1,account2 @Autowired @Qualifier("account1") private accountDao dao;
-
@Qualifier("account1”):用於指定注入bean的id。
-
-
@Resource(name="account1”):@Autowired和@Qualifier的組合版
-
@Value:注入基本類型和String類型的值。支持SPEL語法:${表達式}
-
複雜數據類型無法通過註解實現注入,只能使用xml實現。
-
-
用於改變作用範圍 == 標籤內部的scope
-
@scope:用於指定bean的作用範圍
-
@scope(”singleton“)
- 常用範圍:singleton、prototype
-
@Scope("prototype") public class AccountServiceImpl implements AccountService{ }
-
-
-
和生命週期相關 == 標籤內部的init-method和destory-method相同(瞭解即可)
- @PreDestory:設置銷燬方法
- @PostConstruct:設置初始化方法
配置applicationContext.xml的註解版
註解:
-
@Configuration:指定當前類是一個配置類
-
@ConponentScan:通過註解指定spring在創建容器時要掃描的包
- @ConponentScan("club.twzw”)
-
@Bean:用於把當前方法的返回值作爲苯胺對象存入spring容器中。
- @Bean(name="runner”):指定bean的id,不寫默認爲是當前方法的名稱。
- 當我們使用註解配置方法時,如果方法有參數,Spring框架會從容器中查找有沒有可用的bean對象。
-
由@Bean配置的文件時默認是單例的,我們需要添加@scope註解修改bena的作用範圍。
-
@Import:導入其他配置類,其他配置類上便無需配置@Configuration,並且可表示配置文件的大小關係
-
@Configuration @ComponentScan("club.twzw") //配置包掃描 @Import(JdbcConfig.class) //引入其他配置文件類 public class SpringConfigration{ }
-
-
@PropertySource:用於指定properties文件的位置
- value:指定文件的名稱和路徑
- eg:@PropertySource("classpath:jdbcConfig.properties”)
- classpath:表示類路徑下
- eg:@PropertySource("classpath:jdbcConfig.properties”)
- value:指定文件的名稱和路徑
Spring 整合Junit
- 在pom.xml添加依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version> <!--需要和Spring版本一致-->
</dependency>
-
使用==@RunWith(SpringJUnit4ClassRunner.class)==替換原有運行啓動器
-
告知spring運行器,springIoc創建是基於什麼創建的xml或註解,並說明位置
@ContextConfiguration:
- locations:指定xml文件所在位置,加上classpath關鍵字表示在類路徑下
- classes:指定註解類所在位置
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfigutation(classes = SpringConfiguration.class) public class AccountServiceTest{ @Autowired private AccountService as; }
注:當我們使用spring5.x版本時,junit要求的版本在4.12以上
AOP
動態代理(方法增強)
特點:字節碼隨用隨創建,隨用隨加載。
作用:不修改源碼的基礎上對方法增強。
分類:
-
基於接口的動態代理
-
涉及類:proxy
-
創建代理對象:
- 使用proxy類中的newProxyInstance方法
-
創建代理對象要求:
- 被代理類至少實現一個接口,如果沒有則不能實現
-
newProxyInstance參數:
- ClassLoader:類加載器,用於加載代理對象字節碼,代理誰就寫誰的classloader。
- class[] :字節碼數組。用於讓代理對象和被代理對象有相同的方法。代理誰就寫誰的getInterfaces。
- InvocationHandler:用於增強的代碼。一般寫該接口的實現類。通常是匿名內部類。
- eg:
//Iproducer 是接口,方法有saleProduct //producer 是實現類 Iproducer proxyProducer = (Iproducer)Proxy.newProxyInstance(producer.getClass().getClassLoader(),producer.getClass().getInterfaces(),new InvocationHandler(){ @Override public Object invoke(Object proxy,Method method,Object[] args[])throws Throwable{ // 這裏假設經銷商售出一臺電腦只能拿到15%,剩下的錢需要給生產商。 // 實現代理增強 Object returnValue = null; Float money = (Float)args[0]; //producer 要想在動態代理中調用,必須是final if("saleProduct".equals(method.getName())){ returnValue = method.invoke(producer,money * 0.75f); return returnValue; } } }); proxyProducer.saleProduct(10000f);
-
-
基於子類的動態代理
添加依賴
<dependencies> <dependency> <groudId>cglib</groudId> <artifactId>cglib</artifactId> <version>2.1_3</version> </dependency> </dependencies>
-
涉及類:Enhancer
-
創建代理對象:
- 使用Enhancer類中的create方法
-
創建代理對象要求:
- 被代理類並不是最終類即不是被final所定義的
-
create參數:
- class:字節碼,用於指定被代理對象的字節碼。代理誰就寫誰的getClass
- callback:用於增強的代碼。一般寫該接口的子接口的實現類:MethodInterceptor。
-
eg:
Producer producer = (Producer)Enhancer.create(producer.getClass(),new MethodInterceptor(){ //被代理對象的所有方法都會經過該方法 @Override public Object intercept(Object o ,Method method,Object[] args,MethodProxy methodProxy)throws Throwable{ Object returnValue = null; Float money = (Float)args[0]; //producer 要想在動態代理中調用,必須是final if("saleProduct".equals(method.getName())){ returnValue = method.invoke(producer,money * 0.75f); return returnValue; } }); proxyProducer.saleProduct(10000f);
-
spring中的AOP
概念:aspect oriented programming,面向切面編程。
通過預編譯方式和動態代理實現程序功能的統一維護。
作用:
在不修改源碼對已有方法進行增強。
優勢:
- 減少重複代買
- 提高開發效率
- 維護方便
AOP相關術語
-
連接點:
service中所有方法
-
切入點:
service中被增強的方法
-
Advice(通知/增強):
所謂通知,之攔截到Joinpoint連接點之後要做的事情。
通知類型:前置通知,後置通知,異常通知,最終通知,環繞通知。
-
前置通知
invoke()方法執行前
-
後置通知
invoke()方法執行後
-
異常通知
catch中
-
最終通知
finally中
-
環繞通知
整個invoke方法在執行
-
-
Target(對象)
被代理對象
-
Weaving(織入):
指把增強應用到目標對象來創建新的對象的過程。
-
Proxy(代理):
一個類被AOP織入增前後,就產生一個結果代理類,
-
Aspect(切面):
切入點和通知的結合。
spring 基於xml的AOP增強
advice(通知)
aop配置
<!--未加入約束-->
<beans>
<!--配置spring的Ioc,把service對象配置進來-->
<bean id="accountSerivce" class="club.twzw.service.
impl.accountService"></bean>
<!--配置工具類-->
<bean id="logger" class="club.twzw.utils.logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知類型爲前置通知,並建立通知方法和切入點的聯繫-->
<aop:before method="printLog" pointcut="execution(public vlid club.twzw.service.impl.accountServiceImpl.saceAccount())"> </aop:before>
</aop:aspect>
</aop:config>
</beans>
通常情況下會對業務層(service)所有方法進行增強。
實際開發中切入點表達式寫法:
業務層實現類下的所有寫法:
* club.twzw.service.impl.*.*(..)
<!--任意返回值 club.twzw.service.impl包下的任意class的任意方法,無論是否帶參-->
配置各種通知
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知類型爲前置通知,在切入點方法執行之前執行-->
<aop:before method="printLog" pointcut="execution(* club.twzw.service.impl.*.*(..))">
</aop:before>
<!--配置通知類型爲後置通知,與異常通知不共存同時只能有一個-->
<aop:after-returning method="after_returningPrintLog" pointcut="execution(public vlid club.twzw.service.impl.accountServiceImpl.saceAccount())"> </aop:after-returning>
<!--配置通知類型爲異常通知,與後置通知不共存同時只能有一個-->
<aop:after-throwing method="after_throwingprintLog" pointcut="execution(* club.twzw.service.impl.*.*(..))"> </aop:after-throwing>
<!--配置通知類型爲最終通知,在切入點方法執行之前執行-->
<aop:after method="afterPrintLog" pointcut="execution(* club.twzw.service.impl.*.*(..))">
</aop:after>
</aop:aspect>
</aop:config>
環繞通知(使用代碼配置方式實現前置,後置,異常,最終通知)
純註解實現開啓通知
@Congiguration //配置類
@ComponentScan(basePackage="club.twzw") //包掃描
@EnableAspectJautoProxy //能夠切面自動代理
public class SpringConfiguration{
}
spring基於註解的AOP配置
<!--配置spring創建容器時要掃描的包-->
<context:component-scan base-package="club.twzw"></context:component-scan>
<!--配置spring開啓註解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@Conponent("logger")
@Aspect //表示當前是一個切面類
public class Logger{
//切入點表達式
@Pointcut("execution(* club.twzw,service.Impl.*.*(..))")
private void pC(){}
// 前置通知
@Before("pC()")
public void beforeLogger(){
System.out.print("前置通知");
}
// 後置通知
@AfterReturning("pC()")
public void afterReturningLogger(){
System.out.print("後置通知");
}
// 異常通知
@AfterThrowing("pC()")
public void afterThrowingLogger(){
System.out.print("異常通知");
}
// 最終通知
@After("pC()")
public void afterLogger(){
System.out.print("最終通知");
}
// 環繞通知,推薦使用環繞通知,開啓環繞通知,其通知應該關閉
@Around("pC()")
public void afterLogger(){
System.out.print("環繞通知");
// 使用註解時會出現,調用順序問題,所以可以在環繞通知內寫出正確的調用順序
}
}
事務控制案例
spring 事務控制
事務控制基於AOP
基於xml的事務控制
-
配置事務管理器
-
配置事務的通知
-
配置AOP這個i部分通用切入點表達式
-
建立事務通知和切入點表達式的對應關係
-
配置事務的屬性在tx:attrubutes的內部
基於註解的事務控制
-
配置事務管理器
-
開啓Spring對註解事務的支持
-
在需要事務支持的地方使用==@Transactional==註解
bean引用
直接引用
屬性內引用
內部bean引用
級聯屬性賦值
自動裝配的方式
1. 屬性名自動裝配
<!--byName 根據屬性名自動裝配 -->
<bean id="man" class="com.oak.entity.Man" autowire="byName"></bean>
2. 類型自動裝配
<!--byType 根據類型名自動裝配 -->
<bean id="man" class="com.oak.entity.Man" autowire="byType"></bean>
3. constructor自動裝配
實體類中需要帶有bean參數類型的構造方法
<!-- constructor 裝配 -->
<bean id="man" class="com.oak.entity.Man" autowire="constructor">
4. autodetect自動裝配
Spring首先嚐試通過 constructor 使用自動裝配來連接,如果它不執行,Spring 嘗試通過 byType 來自動裝配
<!-- constructor 裝配 -->
<bean id="man" class="com.oak.entity.Man" autowire="autodetect">
5. setter注入
<!-- setter注入 -->
<bean id="person" class="com.oak.entity.Person">
<property name="age" value="18"/>
<property name="name" value="二蛋"/>
</bean>