1. 程式人生 > 其它 >Spring入門(二)

Spring入門(二)

10. 代理模式

為什麼要學習代理模式?
因為這就是SpringAOP的底層!【 SpringAOP 和 SpringMVC 】

10.1 靜態代理

【租房,中介公司】

靜態代理模式的好處:

  • 可以使真實角色的操作更加純粹!不用去關注一些公共的業務

  • 公共也就交給代理角色!實現了業務的分工!

  • 公共業務發生擴充套件的時候,方便集中管理!

缺點:

  • 一個真實角色就會產生一個代理角色;

  • 程式碼量會翻倍-開發效率會變低

我們在不改變原來的程式碼的情況下,實現了對原有功能的增強,這是AOP中最核心的思想

10.2 動態代理

  1. 動態代理分為兩大類:基於介面的動態代理、基於類的動態代理
  • 基於介面:JDK動態代理【我們在這裡使用】

  • 基於類:cglib

  • java位元組碼實現:javasist

  1. 需要了解1個類1個介面:Proxy:代理,InvocationHandler:呼叫處理程式

  2. 中介看房的例子

【租房介面】

public interface Rent {
    public void rent();
}

【房東出租房子】

public class Host implements Rent{
    @Override
    public void rent() {
        System.out.println("房東出租房子");
    }
}

【InvocationHandler動態代理】

public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的介面
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    //生成得到代理類
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this);
    }
    //處理代理例項,並返回結果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        Object result = method.invoke(rent, args);
        pay();
        return result;
    }
    public void seeHouse() {
        System.out.println("中介帶去看房");
    }
    public void pay() {
        System.out.println("付錢");
    }
}

【客戶租房】

public class Client {
    public static void main(String[] args) {
        //真實角色
        Host host = new Host();
        //代理角色:現在沒有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //通過呼叫程式處理角色,來處理我們要呼叫的介面物件
        pih.setRent(host);
        Rent proxy = (Rent) pih.getProxy();//動態生成的
        proxy.rent();
    }
}
  1. 整理成工具類
//公共方法類
public class ProxyInvocationHandlerBase implements InvocationHandler {

    //被代理的介面
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //生成得到代理類
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
    }
    //處理代理例項,並返回結果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target, args);
        return result;
    }
}

11. AOP

11.1 什麼是AOP

  • AOP(Aspect Oriented Programming)意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。

  • AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。

  • 利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。

11.2 Aop在Spring中的作用

  • 提供宣告式事務;見 13

  • 允許使用者自定義切面 見 11.3.2

【以下名詞需要了解下:】

橫切關注點:跨越應用程式多個模組的方法或功能。即是,與我們業務邏輯無關的,但是我們需要關注的部分,就是橫切關注點。如日誌 , 安全 , 快取 , 事務等等 ....

切面(ASPECT):橫切關注點 被模組化 的特殊物件。即,它是一個類。Log

通知(Advice):切面必須要完成的工作。即,它是類中的一個方法。Log方法

目標(Target):被通知物件。介面

代理(Proxy):向目標物件應用通知之後建立的物件。代理類

切入點(PointCut):切面通知 執行的 “地點”的定義。method

連線點(JointPoint):與切入點匹配的執行點。invoke

11.3 使用Spring實現Aop

【重點】使用AOP織入,需要匯入一個依賴包!

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

11.3.1 方式一:使用Spring的API介面【主要SpringAPI介面實現】

  1. 首先編寫我們的業務介面和實現類
public interface UserService {

   public void add();

   public void delete();

   public void update();

   public void select();
}
public class UserServiceImpl implements UserService{

   @Override
   public void add() {
       System.out.println("增加使用者");
  }

   @Override
   public void delete() {
       System.out.println("刪除使用者");
  }

   @Override
   public void update() {
       System.out.println("更新使用者");
  }

   @Override
   public void select() {
       System.out.println("查詢使用者");
  }
}
  1. 寫我們的增強類(日誌) , 我們編寫兩個 , 一個前置增強 一個後置增強
public class BeforeLog implements MethodBeforeAdvice {

    //method : 要執行的目標物件的方法
    //objects : 被呼叫的方法的引數
    //Object : 目標物件
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被執行了");
    }
}
public class AfterLog implements AfterReturningAdvice {
    //returnValue 返回值
    //method被呼叫的方法
    //args 被呼叫的方法的物件的引數
    //target 被呼叫的目標物件
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("執行了" + target.getClass().getName()
                +"的"+method.getName()+"方法,"
                +"返回值:"+returnValue);
    }
}
  1. 在spring的檔案中註冊 , 並實現aop切入實現 , 注意匯入約束 .
<!--註冊bean-->
<bean id="userService" class="com.qi.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.qi.log.BeforeLog"/>
<bean id="afterLog" class="com.qi.log.AfterLog"/>

<!--方式一:使用Spring的API介面【主要SpringAPI介面實現】-->
<!--aop的配置-->
<aop:config>
    <!--切入點 expression:表示式匹配要執行的方法-->
    <aop:pointcut id="pointcut" expression="execution(* com.qi.service.UserServiceImpl.*(..))"/>
    <!--執行環繞; advice-ref執行方法 . pointcut-ref切入點-->
    <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
  1. 測試
@Test
public void test01(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService) context.getBean("userService");
    userService.select();
}

11.3.2 方式二:自定義來實現【主要是切面定義】

  1. 寫我們自己的一個切入類
public class DiyPointCut {
    public void before() {
        System.out.println("==========方法執行前============");
    }

    public void after() {
        System.out.println("==========方法執行後============");
    }
}
  1. 去spring中配置
<!--方式二:自定義類-->
<!--註冊bean-->
<bean id="diy" class="com.qi.diy.DiyPointCut"/>
<!--aop的配置-->
<aop:config>
    <!--自定義切面,ref要引用的類-->
    <aop:aspect ref="diy">
        <!--切入點-->
        <aop:pointcut id="point" expression="execution(* com.qi.service.UserServiceImpl.*(..))"/>
        <aop:before method="before" pointcut-ref="point"/>
        <aop:after method="after" pointcut-ref="point"/>
        <!--通知-->
    </aop:aspect>
</aop:config>
  1. 測試

11.3.3 使用註解實現!

  1. 編寫一個註解實現的增強類
//方式三:使用註解的方式實現AOP
@Aspect//標註這個類是一個切面
public class AnnotationPointCut {

    @Before("execution(* com.qi.service.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("===========方法執行前==========");
    }

    @After("execution(* com.qi.service.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("===========方法執行後==========");
    }

    //在環繞增強中,我們可以給定一個引數,代表我們要獲取處理切入的點
    @Around("execution(* com.qi.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("==========環繞前============");
        //執行方法
        Object proceed = jp.proceed();
        System.out.println("==========環繞後============");
    }
}
  1. 在Spring配置檔案中,註冊bean,並增加支援註解的配置
<!--方式三:註解-->
<bean id="annotationPointCut" class="com.qi.diy.AnnotationPointCut"/>
<!--開啟註解支援: JDK(預設proxy-target-class="false") cglib-->
<aop:aspectj-autoproxy proxy-target-class="true"/>

12. 整合Mybatis

【步驟】

  1. 匯入相關jar包
<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.2</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.2</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.8</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.8</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>RELEASE</version>
        <scope>compile</scope>
    </dependency>
</dependencies>
  1. 配置Maven靜態資源過濾問題!
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
  1. 編寫配置檔案

  2. 測試

12.1 整合方式一:mybatis-spring

  • 編寫資料來源配置

  • 配置SqlSessionFactory

  • 註冊sqlSessionTemplate , 關聯sqlSessionFactory

  • 需要給介面加實現類【新加的】

  • 將自己寫的實現類,注入到Spring中

<?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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xs
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--配置資料來源:資料來源有非常多,可以使用第三方的,也可使使用Spring的-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!--配置SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--關聯Mybatis-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/qi/mapper/*.xml"/>
    </bean>

    <!--註冊sqlSessionTemplate , 關聯sqlSessionFactory-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--利用構造器注入,沒有set注入,只能使用構造器注入-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
</beans>

### 12.2 整合實現二:繼承 SqlSessionDaoSupport
1. 將我們上面寫的UserDaoImpl修改一下
```java
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{

    @Override
    public List<User> selectUser() {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}
  1. 修改bean的配置
<bean id="userMapperImpl2" class="com.qi.mapper.UserMapperImpl2">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
  1. 測試

12.3 總結

整合到spring以後可以完全不要mybatis的配置檔案,除了這些方式可以實現整合之外,我們還可以使用註解來實現,這個等我們後面學習SpringBoot的時候還會測試整合!

13 宣告式事務

13.1 回顧事務

MySQL整理

事務的ACID原則

  • 把一組業務當成一個業務來做;要麼都成功,要麼都失敗!

  • 確保完整性和一致性

13.2 Spring中的事務管理

  1. 宣告式事務【推薦使用】:AOP
  • 一般情況下比程式設計式事務好用。

  • 將事務管理程式碼從業務方法中分離出來,以宣告的方式來實現事務管理。

  • 將事務管理作為橫切關注點,通過aop方法模組化。Spring中通過Spring AOP框架支援宣告式事務管理

  1. 程式設計式事務:需要在程式碼中,進行事務的管理
  • 將事務管理程式碼嵌到業務方法中來控制事務的提交和回滾

  • 缺點:必須在每個事務操作業務邏輯中包含額外的事務管理程式碼

使用Spring管理事務,注意標頭檔案的約束匯入 : tx

xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

在spring-mapper.xml中配置JDBC事務

<!--JDBC事務-->
<bean id="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="add" propagation="REQUIRED"/>
        <tx:method name="delete" propagation="REQUIRED"/>
        <tx:method name="update" propagation="REQUIRED"/>
        <tx:method name="search*" propagation="REQUIRED"/>
        <tx:method name="get" read-only="true"/>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!--配置aop織入事務-->
<aop:config>
    <aop:pointcut id="txPointcut" expression="execution(* com.qi.mapper.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

spring事務傳播特性

  • propagation_requierd:如果當前沒有事務,就新建一個事務,如果已存在一個事務中,加入到這個事務中,這是最常見的選擇。

  • propagation_supports:支援當前事務,如果沒有當前事務,就以非事務方法執行。

  • propagation_mandatory:使用當前事務,如果沒有當前事務,就丟擲異常。

  • propagation_required_new:新建事務,如果當前存在事務,把當前事務掛起。

  • propagation_not_supported:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

  • propagation_never:以非事務方式執行操作,如果當前事務存在則丟擲異常。

  • propagation_nested:如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與propagation_required類似的操作

為什麼需要事務?

  • 如果不配置,可能存在資料提交不一致的情況;

  • 如果我們不在Spring中去配置宣告式事務,我們需要在程式碼中手動配置事務!

  • 事務在專案開發過程非常重要,涉及到資料的一致性的問題,不容馬虎!