1. 程式人生 > >SpringAOP,IOC, DI

SpringAOP,IOC, DI

  1. 和mybatis整合 XML 管理sql --> xml 對映 用介面配合註解 管理SQL --> 介面對映

@Insert(“sql”) @Update(“sql”) @Delete(“sql”) @Select(“sql”)

@Insert(“insert into user(username,password) values(#{屬性名}, #{屬性名})”) public void insert(User user);

  1. 介面mapper的名稱 要和 xml 檔名稱一致 src/java/UserMapper.java src/resources/UserMapper.xml
  2. 包名和介面類名要與 xml namespace
  3. 介面的方法名對應 xml中的id
  4. 介面方法名不能重複
  1. 宣告式事務管理
<tx:annotation-driven/>  啟用宣告式事務管理和 @Transactional 

配置事務管理器, 完成事務的具體操作(開始事務,提交,操作)

<bean id="transationManager" class="DataSourceTransationManager">
   <property name="dataSource" ref="連線池的id"/>
</bean>

把@Transactional註解加在需要事務控制方法上 方法正常,事務提交 方法出現執行時異常,事務回滾 方法出現了Exception或子類(檢查異常)事務仍提交 @Transactional(rollbackFor=異常.class) 控制哪類異常回滾

@Transactional(noRollbackFor=異常.class) 控制哪類異常不回滾 @Transactional(timeout=超時時間) 超過了時間,事務回滾 @Transactional(readOnly=true|false) 如果事務內僅有查詢,設為readOnly=true能夠提高一些效率 @Transactional(isolation=隔離級別) 髒讀,不可重複讀,幻讀 mysql預設 (可重複讀隔離級別)能夠避免髒讀,不可重複讀,部分幻讀 oracle預設 (提交讀) 只能避免髒讀 @Transactional(propagation=傳播行為) (預設)required 必須的(必須有一個事務,原來沒有,開始新的;原來有,就用原來的) supports 支援的(可選的,原來沒有,不用新的;原來有,就用原來的) requries_new 需要新的, 每次執行此方法時都開始新事務

反射知識

  1. 用另外的手段來建立物件、呼叫物件方法、使用物件的屬性,常用在框架程式設計

  2. 獲取類物件(*) Class<?> c = Class.forName(“包.類”); // 不需要事先import Class<?> c = 類.class; // 需要事先import

  3. 建立物件例項(*) c.newInstance(); // 呼叫無參構造,建立物件 c.getConstructor(構造方法的引數型別).newInstance(構造方法引數)

  4. 獲取方法(*) c.getMethods(); // 得到的是本類以及繼承的所有public的方法 c.getDeclaredMethods(); // 得到本類的所有方法(不考慮訪問修飾符) c.getMethod(方法名字, 方法的引數型別…) c.getDeclaredMethod(方法名字, 方法的引數型別…)

  5. 獲取屬性 c.getFields(); // 得到的是本類以及繼承的所有public的屬性 c.getDeclaredFields(); // 得到本類的所有屬性 c.getField(“屬性名”); c.getDeclaredField(“屬性名”);

  6. 方法物件 Method(*) 物件.方法名(引數); // 正常呼叫方法 方法.invoke(物件, 引數); // 反射呼叫方法

  7. 屬性物件 Field 物件.屬性 = 值; // 正常賦值 物件.屬性; // 正常取值

    屬性.set(物件, 值); // 反射賦值 屬性.get(物件); // 反射取值

  8. 突破private的限制 方法物件 Method.setAccessible(true); // 允許私有方法可以訪問 屬性物件 Field.setAccessible(true); // 允許私有屬性可以訪問

=========================================================================

1.實現事務程式碼重用

思路: 1) 提供了一個代理類(Proxy) 呼叫通知類的invoke方法 獲取方法物件和方法實際引數 與目標要實現相同的介面(目的是讓使用者察覺不出是代理替換了原來的目標) 2) 提供了一個通知類(Advice) 實現了重複程式碼(事務的重複程式碼) 反射呼叫了目標物件的方法 把重複程式碼和目標方法聯絡在了一起

jdk提供了一個通用的介面 InvocationHandler (通知介面)

2.動態代理

剛才自己實現的代理類稱之為靜態代理, jdk 還提供了動態代理技術, 它是指在程式執行期間, 由jdk生成這個代理類和他的例項物件.

正常使用類:*.java -> javac -> .class -> java -> 載入該class到虛擬機器 動態代理 直接生成了.class位元組碼, 載入該class到虛擬機器

Proxy.newProxyInstance(類載入器, 要實現的介面陣列, InvocationHandler);

3. spring的aop

3.1 pom.xml 新增依賴

<!-- spring aop -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aop</artifactId>
	<version>4.3.17.RELEASE</version>
</dependency>

<!-- 第三方 aop依賴 aspectJ -->
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.8.9</version>
</dependency>
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjrt</artifactId>
	<version>1.8.9</version>
</dependency>

3.2 在spring的配置檔案中加入aop的相關標籤

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.xsd
 http://www.springframework.org/schema/tx
 http://www.springframework.org/schema/tx/spring-tx.xsd
 http://www.springframework.org/schema/aop
 http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- 目標類 -->
    <bean id="userService" class="spring.service.UserServiceTarget">
    </bean>

    <!-- 通知類 -->
    <bean id="transactionAdvice" class="spring.advice.TransactionAdvice">
    </bean>

    <!-- 讓相關注解生效, 還能夠幫我們生成底層的代理物件 -->
    <aop:aspectj-autoproxy/>
</beans>

3.3 實現通知類

// 切面
@Aspect
public class TransactionAdvice {

    @Around("within(spring.service.UserServiceTarget)")
    // 通過切點表示式,把通知和目標結合在一起
    // 作用當UserServiceTarget類中任意一個方法執行時(biz1,biz2) 就會和下面的transaction方法結合在一起了


    // 通知型別:環繞通知,決定了這個通知方法會和哪些目標方法結合
    // 這個方法稱為通知方法,它其中包含了一些重複邏輯,另外它負責呼叫目標方法
    public Object transaction(ProceedingJoinPoint pjp) { // pjp 內部去呼叫目標的方法
        System.out.println("開始事務");
        Object r = null;
        try {
            // 呼叫目標
            r = pjp.proceed(); // 目標  biz1,  biz2...

            System.out.println("提交事務");
        } catch (Throwable e) {
            System.out.println("回滾事務");
        }
        return r;
    }
}

3.4 使用

使用上,儘量實現了無侵入的效果,原來的程式碼不受影響

ClassPathXmlApplicationContext context
		= new ClassPathXmlApplicationContext("spring.xml");

// getBean方法返回的不是target物件,而是由spring容器生成的代理物件,底層產生代理的技術就是:jdk動態代理技術
UserService service = context.getBean(UserService.class);

service.biz1();
// 代理物件內部呼叫了通知TransactionAdvice物件, 檢查是否和通知方法上的切點表示式符合
// 如果符合,就執行該通知方法, 通知方法內部再呼叫目標

System.out.println("service真正型別是:"+service.getClass());

aop 的概念

代理 proxy 目標 target 被代理的物件 通知 advice 包含了可重用程式碼(例如事務程式碼),並呼叫目標 切點 pointcut 把通知和目標結合一起,它定義的是一種匹配規則 切面 aspect 切面=通知+切點

aop (aspect切面 oriented 面向 programming 程式設計) 面向切面程式設計 oop 面向物件程式設計

切點 pointcut

aspectj中定義的一些切點表示式

  • within(包名.類名) 這個類中所有方法執行時,都會應用通知方法
  • execution(訪問修飾符 返回值型別 包名.類名.方法名( 引數型別… )) 注意* 可以匹配任意型別, 可以出現在方法返回值,方法名,類名當中 注意.. 可以匹配方法引數,表示引數的個數和型別都是任意的
  • @annotation(包名.註解名) 它是根據方法上的註解進行匹配, 如果有註解則匹配

通知型別

環繞通知: @Around(切點表示式) 加在通知方法上(功能最全的), 通知方法環繞著目標方法 前置通知: @Before 通知程式碼只會出現在目標方法之前 正常結束通知: @AfterReturning 在目標方法正常結束後,呼叫通知方法 異常通知: @AfterThrowing 在目標方法出現異常後,呼叫通知方法 結束通知: @After 在目標方法結束後(正常或異常),總會呼叫的通知

統計每個業務方法的執行時間

呼叫流程

容器啟動時

  1. spring會檢查容器,看是否需要為這些bean建立代理物件
  2. 檢查所有的切點表示式,如果匹配到了目標,就為該目標建立代理物件

獲取物件時(getBean, 依賴注入) 會檢查這個型別是否有代理物件,如果有,優先返回代理物件

呼叫方法時 呼叫了代理物件的方法,代理物件會首先經過通知類(多個)

檢查切點,如果切點匹配,再進行下面的通知呼叫

根據不同的通知型別進行呼叫: 例如前置通知先調通知,再調目標 如果是環繞通知,先呼叫通知,通知內部呼叫目標 …

概念

面向切面程式設計:就是把重複的邏輯抽取為一個通知方法, 然後通過切點匹配來決定哪些目標要應用這些通知。 其中利用了代理的技術,在代理中檢查切點是否匹配,呼叫通知(多個),最後呼叫目標

生成代理的常見技術

jdk的動態代理, 只能針對介面實現代理, jdk動態代理在高版本中效能優於cglib cglib動態代理, 既可以針對介面實現代理,也可以生成子類充當目標父類的代理

spring預設使用jdk動態代理,如果沒有實現介面,就使用cglib代理

回顧

  1. 代理技術 Proxy.newProxyInstance(類載入器, 介面陣列, InvocationHandler);

// 代理類中任何方法呼叫,都會進入InvocationHandler的invoke方法 class 通知類 implements InvocationHandler { public Object invoke(代理物件, 方法物件, 方法實際引數陣列){ // 事務開始 反射呼叫目標方法 // 事務提交,回滾 } } // 程式碼得到重用

  1. spring的aop 代理proxy 通知advice 切點pointcut 目標target 切面aspect = 通知+切點

@Aspect public class 切面類(通知類) { @Around(“切點表示式–匹配哪些目標方法要執行下面的通知”) public Object 通知方法名(ProceedingJoinPoint pjp) { 重複邏輯(例如事務、統計執行時間、許可權控制…); if(許可權檢查通過) return pjp.proceed(); // 執行了目標方法 else { throw 沒有許可權; } } }

在配置檔案中啟用aop程式設計 <aop:aspectj-autoproxy/>

  1. 切點表示式 within(包名.類名) 匹配此類中所有的方法 execution(public 返回型別 包名.類名.方法名(引數型別)) 匹配方法的各個要素

    • 表示匹配任意的返回型別、類名、方法名… … 加在方法引數上,表示引數的型別、個數任意 @annotation(包名.註解類名) 匹配方法上是否有這個註解
  2. 通知型別 @Around 環繞通知 @Before 前置通知 @After 最終通知 @AfterReturning 正常結束通知 @AfterThrowing 異常通知

  3. 代理建立方式 jdk動態代理 – 限制:只能針對介面生成代理 cglib代理 – 既可以針對介面生成代理,也可以生成一個子類充當代理 spring會優先使用jdk動態代理, 不能用jdk動態代理的,再用cglib

===============================================================

spring-mvc

基於spring的, ioc控制反轉, di依賴注入, aop面向切面程式設計

model 模型 - 資料和操作資料的邏輯(狹義的就是資料) 包括了實體類和業務類(例如 User,UserService) view 檢視 - 資料展現, 包括(jsp, jstl, el) controller 控制器 把模型和檢視關聯在一起, 包括servlet

讓程式的各個部分分工清晰,各司其職。讓程式的可維護性提高。

使用步驟:

  1. 在pom.xml檔案中新增 spring-webmvc等6個依賴 注意pom.xml檔案中需要新增<packaging>war</packaging> (決定了是maven的web專案) 然後手動補充一個src/main/webapp目錄, src/main/webapp/WEB-INF/web.xml 在配置tomcat時選擇 專案名:war exploded

  2. 在spring-mvc.xml中新增mvc的xml名稱空間

  3. 前控制器 – 統一的入口 它是由spring-mvc 提供的的一個servlet: DispatcherServlet 在web.xml 對它進行配置, maven 的目錄結構中,該檔案的路徑是 src/main/webapp/WEB-INF/web.xml

<!-- 職責: 
   1.作為統一入口
   2.建立spring容器
   3.在tomcat啟動時,就把spring容器建立好
-->
<servlet>
	<servlet-name>springmvc</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<!-- 指明瞭spring配置檔案的位置 -->
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring-mvc.xml</param-value>
	</init-param>
	<!-- 在tomcat啟動時,就建立這個servlet,並初始化該servlet -->
	<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
	<servlet-name>springmvc</servlet-name>
	<url-pattern>/</url-pattern> 
	<!-- 給 DispatcherServlet 指定路徑 
	假設瀏覽器  /hello  沒有,會找 / 這個路徑
			   /s1     沒有,會找 / 這個路徑
			   
			 只要沒有其他servlet能夠精確匹配這個請求路徑,這個請求都會被 / 的這個servlet來處理
	-->
</servlet-mapping>
  1. 控制器 @Controller public class 控制器類{

    @RequestMapping("/請求路徑") public String 控制器方法(){

    } } // 控制器類要交給spring容器管理 並spring-mvc中加入:<mvc:annotation-driven/> 來啟用mvc的相關功能

  2. 處理檢視 控制器方法的返回值是檢視名 字首+檢視名+字尾就構成了最終jsp檢視路徑, 控制器會根據檢視路徑用請求轉發跳轉過去

<mvc:view-resolvers>
	<mvc:jsp prefix="/" suffix=".jsp"/>
</mvc:view-resolvers>
  1. 請求被處理的流程

    1. tomcat先將該路徑與servlet進行匹配,結果沒有精確的地址與之匹配,就找到了 / 這個地址 / 地址對應的是DispatcherServlet,由它來處理請求
    2. DispatcherServlet 再到spring容器中找控制器類 把每個控制器中帶有@RequestMapping的方法路徑與請求路徑進行匹配,
    3. 匹配到了就執行該方法, 如果匹配不到報404錯誤
    4. 根據方法的返回結果找到jsp檢視
    5. 由jsp檢視生成html程式碼作為響應返回給瀏覽器
  2. 以maven外掛方式執行tomcat 在pom.xml中加入外掛資訊:

<plugin> <!-- 配置一個內嵌的tomcat -->
	<groupId>org.apache.tomcat.maven</groupId>
	<artifactId>tomcat7-maven-plugin</artifactId>
	<version>2.2</version>
	<configuration>
		<port>8080</port> <!-- 埠號 -->
		<path>/</path> <!-- 專案在tomcat中的訪問路徑 -->
		<uriEncoding>utf-8</uriEncoding> <!-- 設定get請求中解碼的字符集,解決get請求中的中文亂碼問題 -->
	</configuration>
</plugin>

然後在右側的maven選單中找到tomcat7外掛,執行tomcat7:run這個命令

  1. 接收請求引數
  1. 把方法引數和請求引數直接對應

  2. 寫一個實體型別,把實體的屬性與請求引數對應

  3. 路徑引數(把引數值不是寫在?之後,而是包含在路徑當中) /deleteUser?id=1 傳統寫法 /deleteUser/1 路徑引數 要刪除1號使用者 /deleteUser/2 要刪除2號使用者

    路徑引數可以通過@PathVariable 與方法引數一一對應

  1. 使用模型資料 Model 介面, 代表了模型資料, 它也是加在控制器方法之上 可以直接使用它的實現類ModelMap或Map 向模型新增資料: model.addAttribute(“變數名”, 值); model中的資料在轉發之前,都會被存入request作用域

springmvc中 常見錯誤: 400 錯誤:發生在請求引數賦值給方法引數時,發生了型別轉換問題 例如:字串轉整數 字串轉日期 對於日期需要加 @DateTimeFormat(pattern=“日期格式”) 中文亂碼問題: 可以配置spring提供的字元編碼過濾器

<!-- spring 提供的字元編碼過濾器 -->
<filter>
	<filter-name>encoding</filter-name>
	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
	<init-param>
		<param-name>encoding</param-name>
		<param-value>utf-8</param-value>
	</init-param>
</filter>

<filter-mapping>
	<filter-name>encoding</filter-name>
	<url-pattern>/ *</url-pattern>
</filter-mapping>
靜態資源的 404 錯誤
	靜態資源是指 圖片、css、html、js
	原因是DispatcherServlet的路徑是/ 跟tomcat中處理圖片的Servlet路徑衝突了
	所以所有圖片的請求被DispatcherServlet所處理,把圖片的路徑當做了控制器路徑
解決辦法:	
	在spring配置檔案中加入:`<mvc:default-servlet-handler/>`

讓tomcat能夠熱部署,讓類的修改不用重啟伺服器就可以生效

  1. 首先保證 打包方式是 war exploded
  2. 其次,啟動tomcat使用debug方式啟動
  3. 在tomcat設定時,on frame deactivation 選中 update classes and resources 選項 當游標從idea切換至瀏覽器時,就會更新修改 限制:只能針對當前存在的類生效,如果是新增的類檢測不到 配置檔案的改動檢測不到
  1. json格式的響應 js 代表javascript object 物件, notation 標記

之前返回的大部分響應型別 text/html json也是一種響應格式:也是文字格式的,更類似於javascript的物件寫法 { “username”:“張三”, “age”:18 } 如果轉換json時出現了錯誤: No converter found for return value of type: 是因為json轉換的jar包沒有加, 可以在pom檔案中加入相應的依賴:

<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.9.1</version>
</dependency>  

在控制器方法上新增@ResponseBody註解,它可以把方法的返回值轉換成json字串

java中 map以及實體類 會被轉換為json中的物件 {屬性名:屬性值} java中 list或陣列 會被轉換為json中的陣列 [元素1, 元素2, …]

jackson常見註解: @JsonFormat 可以對日期型別進行格式化 @JsonFormat(pattern = “yyyy-MM-dd HH:mm:ss”, timezone = “GMT+8”) 注意加8小時才是北京時間 @JsonIgnore 可以在轉換時忽略加了@JsonIgnore的屬性 @JsonProperty(“新的屬性名”) 可以在轉換時改變屬性的名字

  1. 把springmvc的控制器和之前的 service, mapper 結合起來 UserController @Autowired private UserService service; UserService @Autowired private UserMapper mapper; UserMapper 介面 @Insert public void insert(User user);

做一個查詢所有使用者的例子,把流程調通.

  1. 用aop的思想完成方法的記時 UserService foo 要求記時 bar insert 要求記時 update 如何完成某幾個方法的記時