day34_Spring學習筆記_02

一、AOP

1.1、AOP介紹

1.1.1、什麼是AOP?

  • 在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP(面向對象編程)的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
  • AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性代碼。如下圖所示:
  • 經典應用:事務管理、性能監視、安全檢查、緩存 、日誌等。
  • Spring AOP使用純Java實現,不需要專門的編譯過程和類加載器,在運行期通過代理方式向目標類織入增強代碼。
  • AspectJ是一個基於Java語言的AOP框架,從Spring2.0開始,Spring AOP引入對Aspect的支持,AspectJ擴展了Java語言,提供了一個專門的編譯器,在編譯時提供橫向代碼的織入。

1.1.2、AOP實現原理

  • aop底層將採用代理機制進行實現。
  • 接口 + 實現類時 :spring採用 jdk 的 動態代理 Proxy。
  • 只有實現類時:spring 採用 cglib 字節碼增強

1.1.3、AOP術語【掌握】

  1. Target :目標類,需要被代理的類。本例中如:UserService
  2. Joinpoint(連接點) :所謂連接點是指那些可能被攔截到的點。在spring中,這些點指的是方法,因爲spring只支持方法類型的連接點。本例中如:UserService的所有的方法
  3. PointCut 切入點 :所謂切入點是指我們要對哪些Joinpoint進行攔截,即已經被增強的連接點。例如:addUser()
  4. Advice :通知/增強,增強的代碼。例如:after()、before() 所謂通知是指攔截到Joinpoint之後所要做的事情就是通知,通知分爲前置通知、後置通知、異常通知、最終通知、環繞通知(即切面要完成的功能)。
  5. Weaving(織入) :是指把通知/增強advice應用到目標對象target來創建新的代理對象proxy的過程。 spring採用動態代理織入,而AspectJ採用編譯期織入和類裝在期織入。
  6. Proxy :代理類,一個類被AOP織入增強後,就產生一個結果代理類。
  7. Aspect(切面) : 是切入點Pointcut和通知Advice(引介)的結合。
  8. Introduction(引介) :引介是一種特殊的通知,在不修改類代碼的前提下,Introduction 可以在運行期爲類動態地添加一些方法或Field。

  • 小結: 一個線是一個特殊的面。 一個切入點和一個通知,組成成一個特殊的面。

1.2、手動方式

1.2.1、JDK動態代理

  • JDK動態代理:是對“裝飾者”設計模式的簡化。JDK動態代理使用前提:必須有接口。
    1. 目標類:接口 + 實現類
    2. 切面類:用於存放通知,名稱叫:MyAspect.java
    3. 工廠類:編寫工廠生成代理
    4. 測試類

1.2.1.1、目標類   UserService.java

package com.itheima.a_proxy.a_jdk;

// 目標接口
public interface UserService {

    public void addUser();
    public void updateUser();
    public void deleteUser();
}

  UserServiceImpl.java

package com.itheima.a_proxy.a_jdk;

// 目標實現類,有接口
public class UserServiceImpl implements UserService {

    @Override
    public void addUser() {
        System.out.println("a_proxy.a_jdk addUser");
    }

    @Override
    public void updateUser() {
        System.out.println("a_proxy.a_jdk updateUser");
    }

    @Override
    public void deleteUser() {
        System.out.println("a_proxy.a_jdk deleteUser");
    }
}

1.2.1.2、切面類   MyAspect.java

package com.itheima.a_proxy.a_jdk;

// 切面類
public class MyAspect {
    public void before() {
        System.out.println("前方法");
    }

    public void after() {
        System.out.println("後方法");
    }
}

1.2.1.3、工廠類(自定義的)   MyBeanFactory.java

package com.itheima.a_proxy.a_jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 工廠類
public class MyBeanFactory {
    public static UserService createService() {
        // 1、先有目標類對象
        final UserService userService = new UserServiceImpl();
        // 2、再有切面類
        final MyAspect myAspect = new MyAspect();
        /* 3、最後有代理類,將目標類(切入點)和切面類(通知)進行結合  =》  切面
         *  Proxy.newProxyInstance
         *      參數1:ClassLoader loader      類加載器,我們知道,動態代理類在運行時創建的,任何類都需要類加載器將其加載到內存。
         *          類加載器該如何寫呢?
         *          答:一般情況下:當前類.class.getClassLoader()
         *              或者       目標類的實例.getClass().getClassLoader()
         * 
         *      參數2:Class[] interfaces      代理類需要實現的所有接口
         *          方式1:目標類的實例.getClass().getInterfaces()  注意:該方式只能獲得自己接口,不能獲得父元素接口
         *          方式2:new Class[]{UserService.class}   
         *              例如:jdbc 驅動   => DriverManager => 獲得接口 Connection
         * 
         *      參數3:InvocationHandler h 處理類,是一個接口,必須進行實現類,一般情況下采用:匿名內部類
         *          該接口提供了一個 invoke 方法,代理類的每一個方法執行時,都將調用一次invoke 方法
         *              參數31:Object proxy       代理對象
         *              參數32:Method method      代理對象當前執行的方法的描述對象(反射)
         *                  執行的方法名:method.getName()
         *                  執行的方法:method.invoke(對象, 實際參數)
         *              參數33:Object[] args      方法的實際參數
         */
        UserService proxyService = (UserService) Proxy.newProxyInstance(
                                MyAspect.class.getClassLoader(), 
                                userService.getClass().getInterfaces(), 
                                new InvocationHandler() {

                                    @Override
                                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                                        // 前執行
                                        myAspect.before();

                                        // 執行目標類的方法
                                        Object obj = method.invoke(userService, args);

                                        // 後執行
                                        myAspect.after();

                                        return obj;
                                    }
                                });

        return proxyService;
    }
}

1.2.1.4、測試類   TestJDK.java

package com.itheima.a_proxy.a_jdk;

import org.junit.Test;

// 測試類
public class TestJDK {

    @Test
    public void demo01() {
        UserService userService = MyBeanFactory.createService();
        userService.addUser();
        userService.updateUser();
        userService.deleteUser();
    }
}

程度運行結果爲:

前方法
a_proxy.a_jdk addUser
後方法
前方法
a_proxy.a_jdk updateUser
後方法
前方法
a_proxy.a_jdk deleteUser
後方法

debug調試的結果: JDK動態代理返回的是:$Proxy (id=34)

1.2.2、CGLIB字節碼增強

  • 沒有接口,只有實現類。
  • 採用字節碼增強框架 cglib,運行原理:在運行時,創建目標類的子類,從而對目標類進行增強。
  • 導入jar包: 自己導jar包(瞭解):
    • 核心包:hibernate-distribution-3.6.10.Final\lib\bytecode\cglib\cglib-2.2.jar
    • 依賴包:struts-2.3.15.3\apps\struts2-blank\WEB-INF\lib\asm-3.3.jar
  • spring-core-3.2.0.RELEASE.jar 已經整合以上兩個內容,所以我們只需要導入這個包就可以了,如下圖所示:

1.2.2.1、目標類   UserServiceImpl.java

package com.itheima.a_proxy.b_cglib;

// 目標實現類,沒接口
public class UserServiceImpl {

    public void addUser() {
        System.out.println("a_proxy.b_cglib addUser");
    }

    public void updateUser() {
        System.out.println("a_proxy.b_cglib updateUser");
    }

    public void deleteUser() {
        System.out.println("a_proxy.b_cglib deleteUser");
    }
}

1.2.2.2、切面類   MyAspect.java的代碼同上 1.2.1.2、切面類 代碼,這裏不再贅述!

1.2.2.3、工廠類(自定義的)   MyBeanFactory.java

package com.itheima.a_proxy.b_cglib;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

// 工廠類
public class MyBeanFactory {
    public static UserServiceImpl createService() {
        // 1、先有目標類對象
        final UserServiceImpl userServiceImpl = new UserServiceImpl();
        // 2、再有切面類對象
        final MyAspect myAspect = new MyAspect();

        // 3、最後有代理類,採用cglib,底層創建目標類的子類
        // 3.1、核心類
        Enhancer enhancer = new Enhancer();
        // 3.2 、先確定父類
        enhancer.setSuperclass(userServiceImpl.getClass());
        /* 3.3、 設置回調函數 ,MethodInterceptor接口  等效  jdk中的 InvocationHandler接口 
         *      intercept() 等效 jdk中的  invoke()
         *          參數1、參數2、參數3:和以invoke()方法的參數一樣
         *          參數4:methodProxy 方法的代理
         */
        enhancer.setCallback(new MethodInterceptor() {

            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

                // 前執行
                myAspect.before();

                // 執行目標類的方法
                Object obj = method.invoke(userServiceImpl, args);
                // 執行代理類的父類,就是執行目標類(目標類和代理類是斧子關係),相當於inwoke調用了2次
                methodProxy.invokeSuper(proxy, args);

                // 後執行
                myAspect.after();

                return obj;
            }});

        // 4、創建代理
        UserServiceImpl proxyService = (UserServiceImpl) enhancer.create();

        return proxyService;
    }
}

1.2.2.4、測試類   TestJDK.java的代碼同上 1.2.1.4、切面類 代碼,這裏不再贅述!   程度運行結果爲:

前方法
a_proxy.b_cglib addUser
a_proxy.b_cglib addUser
後方法
前方法
a_proxy.b_cglib updateUser
a_proxy.b_cglib updateUser
後方法
前方法
a_proxy.b_cglib deleteUser
a_proxy.b_cglib deleteUser
後方法

  debug調試的結果:   CGLIB字節碼增強返回的是:UserServiceImpl$$EnhancerByCGLIB$$157a2b67 (id=34)

1.2.3、代理知識總結

  • Spring在運行期,生成動態代理對象,不需要特殊的編譯器。
  • Spring AOP的底層就是通過JDK動態代理或CGLib動態代理技術爲目標Bean執行橫向織入的。
    1. 若目標對象實現了若干接口,spring使用JDK的java.lang.reflect.Proxy類代理。
    2. 若目標對象沒有實現任何接口,spring使用CGLIB庫生成目標對象的子類。
  • 程序中應優先對接口創建代理,便於程序解耦維護。
  • 標記爲final的方法,不能被代理,因爲無法進行覆蓋。
    1. JDK動態代理,是針對接口生成子類,接口中的方法不能使用final修飾。
    2. CGLib動態代理,是針對目標類生產子類,因此目標類和目標類的方法是不能使用final修飾。
  • Spring只支持方法連接點,不提供屬性連接。

1.3、AOP聯盟增強(通知)類型

  • AOP聯盟爲通知Advice定義了org.aopalliance.aop.Advice
  • Spring按照通知Advice在目標類方法的連接點位置,可以分爲5類:
    • 1、前置通知:org.springframework.aop.MethodBeforeAdvice
      • 在目標方法執行前實施增強
    • 2、後置通知:org.springframework.aop.AfterReturningAdvice
      • 在目標方法執行後實施增強
    • 3、環繞通知:org.aopalliance.intercept.MethodInterceptor
      • 在目標方法執行前後實施增強
    • 4、異常拋出通知:org.springframework.aop.ThrowsAdvice
      • 在方法拋出異常後實施增強
    • 5、引介通知:org.springframework.aop.IntroductionInterceptor
      • 在目標類中添加一些新的方法和屬性

模擬環繞通知:

環繞通知:`必須手動執行目標方法`
    try {
       // 前置通知
       // 執行目標方法
       // 後置通知
    } catch() {
       // 異常拋出通知
    }

1.4、spring 編寫代理:半自動

  • 讓spring 給我們創建代理對象,我們從spring容器中手動的獲取代理對象。
  • 導入jar包:
    • 核心jar包:4 + 1
    • AOP的jar包:AOP聯盟(規範/接口)、spring-aop(實現)

1.4.1、目標類

  UserService.java

package com.itheima.b_factory_bean;

// 目標接口
public interface UserService {

    public void addUser();
    public void updateUser();
    public void deleteUser();
}

  UserServiceImpl.java

package com.itheima.b_factory_bean;

// 目標實現類,有接口
public class UserServiceImpl implements UserService {

    @Override
    public void addUser() {
        System.out.println("b_factory_bean addUser");
    }

    @Override
    public void updateUser() {
        System.out.println("b_factory_bean updateUser");
    }

    @Override
    public void deleteUser() {
        System.out.println("b_factory_bean deleteUser");
    }
}

1.4.2、切面類

package com.itheima.b_factory_bean;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

// 切面類
/**
 * 切面類中需要刪除之前自己寫的通知,添加上AOP聯盟提供的通知(即要增強的東西),需要實現不同接口,接口就是規範,從而就確定方法名稱。
 *         採用“環繞通知” MethodInterceptor
 *
 */
public class MyAspect implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {

        System.out.println("我們的前代碼");

        // 使用AOP聯盟的環繞通知:必須手動執行目標方法
        Object obj = mi.proceed();

        System.out.println("我們的後代碼");

        return obj;
    }
}

1.4.3、spring配置

  bean.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                              http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 創建目標類對象 -->
    <bean id="userServiceId" class="com.itheima.b_factory_bean.UserServiceImpl"></bean>
    <!-- 創建切面類 -->
    <bean id="myAspectId" class="com.itheima.b_factory_bean.MyAspect"></bean>

    <!-- 創建代理類對象 
            因爲我們使用的是工廠bean:FactoryBean,它的底層調用的是 getObject() 返回一個特殊的bean
            工廠bean的一個具體實現類:ProxyFactoryBean類,該類用於創建代理工廠bean,生產特殊的代理對象   
                第一個屬性:interfaces :確定接口們
                            通過  <array> + <value> 可以設置多個值;
                            如果只有一個值時,可以簡寫  value=""
                        target :確定目標類
                        interceptorNames :通知,切面類的名稱,類型是String[],如果設置一個值用value=""      注意:jdk1.8中不支持該屬性!
                        optimize :強制使用cglib
                            <property name="optimize" value="true"></property>
            創建代理類對象的底層機制:
                如果目標類有接口,就採用jdk 動態代理
                如果目標類沒有接口,就採用cglib 字節碼增強
                如果聲明 optimize = true ,無論是否有接口,都採用cglib 字節碼增強
    -->
    <bean id="proxyServiceId" class="org.springframework.aop.framework.ProxyFactory">
        <property name="interfaces" value="com.itheima.b_factory_bean.UserService"></property>
        <property name="target" ref="userServiceId"></property>
        <property name="interceptorNames" value="myAspectId"></property>
        <property name="optimize" value="true"></property>
        <!-- 
        <property name="interfaces">
            <array>
                <value></value>
                <value></value>
                <value></value>
            </array>
        </property> 
        -->
    </bean>
</beans>

1.4.4、測試類

  TestFactoryBean.java

package com.itheima.b_factory_bean;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

// 測試類
public class TestFactoryBean {

    @Test
    public void demo01() {
        String xmlPath = "com/itheima/b_factory_bean/beans.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        // 獲取代理類對象
        UserService userService = (UserService) applicationContext.getBean("proxyServiceId");

        userService.addUser();
        userService.updateUser();
        userService.deleteUser();
    }
}

1.5、spring aop編程:全自動【掌握】

  • 我們從spring容器獲得的就是目標類,如果我們配置了aop,spring將自動生成代理對象。
  • 我們要確定目標類,使用aspectj 切入點表達式,要導入jar包: spring-framework-3.0.2.RELEASE-dependencies\org.aspectj\com.springsource.org.aspectj.weaver\1.6.8.RELEASE

1.5.1、目標類

  UserService.java 和 UserServiceImpl.java 代碼 同 1.4.1、目標類 代碼一樣。

1.5.2、切面類

  MyAspect.java 代碼同 1.4.2、切面類 代碼一樣。

1.5.3、spring配置

  beans.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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                              http://www.springframework.org/schema/beans/spring-beans.xsd
                              http://www.springframework.org/schema/aop 
                              http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 1、創建目標類對象 -->
    <bean id="userServiceId" class="com.itheima.c_spring_aop.UserServiceImpl"></bean>
    <!-- 2、創建切面類 (通知)-->
    <bean id="myAspectId" class="com.itheima.c_spring_aop.MyAspect"></bean>
    <!-- 3、aop編程 
         3.1、 導入命名空間
         3.2 、使用 <aop:config>進行配置
                proxy-target-class="true" 聲明使用cglib代理,否則默認使用jdk代理
                    <aop:pointcut> 切入點 ,從目標對象上來獲得具體方法
                    <aop:advisor> 特殊的切面,只有一個通知 和 一個切入點
                        advice-ref 通知引用
                        pointcut-ref 切入點引用
         3.3 、切入點表達式
             execution(* com.itheima.c_spring_aop.*.*(..))
             選擇方法   返回值任意  包     類名任意  方法名任意  參數任意

    -->
    <aop:config proxy-target-class="true">
        <aop:pointcut expression="execution(* com.itheima.c_spring_aop.*.*(..))" id="myPointCut"/>
        <aop:advisor advice-ref="myAspectId" pointcut-ref="myPointCut"/>
    </aop:config>
</beans>

1.5.4、測試類

  TestFactoryBean.java

package com.itheima.c_spring_aop;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

// 測試類
public class TestSpringAOP {

    @Test
    public void demo01() {
        String xmlPath = "com/itheima/c_spring_aop/beans.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        // 獲取目標類對象
        UserService userService = (UserService) applicationContext.getBean("userServiceId");

        userService.addUser();
        userService.updateUser();
        userService.deleteUser();
    }
}

  程度運行結果爲:

我們的前代碼
c_spring_aop addUser
我們的後代碼
我們的前代碼
c_spring_aop updateUser
我們的後代碼
我們的前代碼
c_spring_aop deleteUser
我們的後代碼

二、使用 AspectJ 實現 AOP

2.1、AspectJ 的介紹

  • AspectJ是一個基於Java語言的AOP框架。
  • Spring2.0以後新增了對AspectJ切點表達式支持。
  • @AspectJ 是AspectJ1.5新增的功能,通過JDK5註解技術,允許直接在Bean類中定義切面。
  • 在新版本的Spring框架中,建議使用AspectJ方式來開發AOP。
  • 主要用途:自定義開發

2.2、切入點表達式【掌握】

1.execution()  用於描述方法【掌握】
    語法:execution(修飾符  返回值  包.類.方法名(參數) throws異常)
        修飾符,一般省略
            public      公共方法
            *           任意
        返回值,不能省略
            void        返回沒有值
            String      返回值字符串
            *           任意
        包,[可以省略]
            com.itheima.crm                 固定的包
            com.itheima.crm.*.service       crm包下面的任意子包,固定目錄service(例如:com.itheima.crm.staff.service)
            com.itheima.crm..               crm包下面的所有子包(含自己)
            com.itheima.crm.*.service..     crm包下面的任意子包,固定目錄service,service目錄任意包(含自己)
        類,[可以省略]
            UserServiceImpl                 指定的類
            *Impl                           以Impl結尾的類
            User*                           以User開頭的類
            *                               任意的類
        方法名,不能省略
            addUser                         固定的方法名
            add*                            以add開頭的方法名
            *Do                             以Do結尾的方法名
            *                               任意的方法名
        (參數)
            ()                              無參
            (int)                           一個整型
            (int, int)                      兩個整型
            (..)                            參數任意
        throws,[可以省略],一般省略。

    綜合案例1:
        execution(* com.itheima.crm.*.service..*.*(..))
    綜合案例2:
        <aop:pointcut expression="execution(* com.itheima.*WithCommit.*(..)) || 
                                  execution(* com.itheima.*Service.*(..))" id="myPointCut"/>

2.within:匹配包或子包中的方法(瞭解)
    within(com.itheima.aop..*)
3.this:匹配實現了接口的代理對象中的方法(瞭解)
    this(com.itheima.aop.user.UserDAO)
4.target:匹配實現了接口的目標對象中的方法(瞭解)
    target(com.itheima.aop.user.UserDAO)
5.args:匹配參數格式符合標準的方法(瞭解)
    args(int, int)
6.bean(id):對指定的bean所有的方法(瞭解)
    bean('userServiceId')

2.3、AspectJ 的通知類型

  • aop聯盟定義的通知類型,具有特定的接口,我們必須去實現該接口,從而確定方法名稱。
  • aspectj 的通知類型:只定義了類型的名稱以及方法的格式。
  • AspectJ 的通知類型的個數:共6種,知道5種,掌握1種即可。
    • before:前置通知(應用:各種校驗)
      • 在方法執行前執行,如果該通知拋出異常,將阻止方法運行。
    • afterReturning:後置通知(應用:常規數據處理)
      • 方法正常返回後執行,如果方法中拋出異常,那麼通知將無法執行。
      • 必須在方法執行後才執行,所以可以獲得方法的返回值。
    • `around`:環繞通知(應用:十分強大,可以做任何事情)
      • 方法執行前後分別執行,可以阻止方法的執行。
      • 使用AOP聯盟的環繞通知,必須手動執行目標方法。
    • afterThrowing:拋出異常通知(應用:包裝異常信息)
      • 方法拋出異常後執行,如果方法沒有拋出異常,則該通知無法執行。
    • after:最終通知(應用:清理現場)
      • 方法執行完畢後執行,無論方法中是否出現異常,該通知都執行。

模擬以上幾個通知:

環繞通知:around

try {
    // 前置通知:before
    // 手動執行目標方法
    // 後置通知:afterRetruning
} catch() {
    // 拋出異常通知:afterThrowing
} finally {
    // 最終通知:after
}

要學習的5個實現類,如下圖所示:

2.4、導入jar包

  • 需要導入4個jar包:
    • aop 聯盟規範
    • spring aop 實現
    • aspect 規範
    • spring aspect 實現

如下如所示:

2.5、基於xml

  • 思路:
    • 1、目標類:接口類 + 實現類
    • 2、切面類:編寫多個通知,採用aspectj 的方法,所以通知名稱任意(即方法名任意)
    • 3、aop編程,將通知應用到目標類
    • 4、測試類

2.5.1、目標類

  UserService.java 和 UserServiceImpl.java 代碼 同 1.4.1、目標類 代碼一樣。

2.5.2、切面類

  MyAspect.java

package com.itheima.d_aspect.a_xml;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

// 切面類,含有多個通知
public class MyAspect {

    public void myBefore(JoinPoint joinPonint) {
        System.out.println("我的前置通知:" + joinPonint.getSignature().getName());
    }

    public void myAfterReturning(JoinPoint joinPoint, Object ret) {
        System.out.println("我的後置通知 : " + joinPoint.getSignature().getName() + ", --> " + ret);
    }

    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("前方法");

        // 手動執行目標方法
        Object obj = joinPoint.proceed();

        System.out.println("後方法");

        return null;
    }

    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("我的拋出異常通知 : " + e.getMessage());
    }

    public void myAfter(JoinPoint joinPoint) {
        System.out.println("我的最終通知");
    }
}

2.5.3、spring配置

  beans.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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                              http://www.springframework.org/schema/beans/spring-beans.xsd
                              http://www.springframework.org/schema/aop 
                              http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 1、創建目標類對象 -->
    <bean id="userServiceId" class="com.itheima.d_aspect.a_xml.UserServiceImpl"></bean>
    <!-- 2、創建切面類 (通知)-->
    <bean id="myAspectId" class="com.itheima.d_aspect.a_xml.MyAspect"></bean>
    <!-- 3、aop編程 
        <aop:aspect>    將切面類,聲明爲“切面”,從而獲得切面類的通知們(方法們)
            ref 切面類引用(id)
        <aop:pointcut>  聲明一個切入點,該切入點所有的通知都可以使用。
            expression  切入點表達式
            id  名稱,用於其它通知的引用
    -->
    <aop:config>
        <aop:aspect ref="myAspectId">
            <aop:pointcut expression="execution(* com.itheima.d_aspect.a_xml.UserServiceImpl.*(..))" id="myPointCut"/>

            <!-- 3.1、 前置通知 
                <aop:before method="" pointcut="" pointcut-ref=""/>
                    method : 通知名稱,即方法名稱。
                    pointcut :切入點表達式,此表達式只能在當前通知使用。
                    pointcut-ref : 切入點引用,可以與其他通知共享切入點。
                通知/方法格式:public void myBefore(JoinPoint joinPoint) { ... }
                    參數1:org.aspectj.lang.JoinPoint  用於描述連接點(目標方法),可以獲得目標方法的方法名等
                例如:
                <aop:before method="myBefore" pointcut-ref="myPointCut"/>
            -->

            <!-- 3.2、後置通知,在目標方法後執行,可以用於獲得方法執行後的返回值
                <aop:after-returning method="" pointcut-ref="" returning=""/>
                    returning :通知/方法的第二個參數的名稱
                通知/方法格式:public void myAfterReturning(JoinPoint joinPoint, Object ret) { ... }
                    參數1:用於描述連接點(目標方法),可以獲得目標方法的方法名等
                    參數2:類型Object,該參數的名稱是由 returning="ret" 配置的
                例如:
                <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret"/>
            -->

            <!-- 3.3 、環繞通知 
                <aop:around method="" pointcut-ref=""/>
                通知/方法格式:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable { ... }
                    返回值類型 :Object
                    方法名 :任意
                    參數 :org.aspectj.lang.ProceedingJoinPoint
                    拋出異常
                手動執行目標方法:Object obj = joinPoint.proceed();
                例如:
                <aop:around method="myAround" pointcut-ref="myPointCut"/>
            -->
            <!-- 3.4、 拋出異常通知
                <aop:after-throwing method="" pointcut-ref="" throwing=""/>
                    throwing :通知方法的第二個參數名稱
                通知/方法格式:public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { ... }
                    參數1:用於描述連接點(目標方法),可以獲得目標方法的方法名等
                    參數2:獲得異常信息,類型Throwable,該參數的名稱是由 throwing="e" 配置
                例如:
                <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
            -->
            <!-- 3.5 、最終通知 -->  

            <!-- <aop:before method="myBefore" pointcut-ref="myPointCut"/> -->
            <!-- <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret"/> -->
            <!-- <aop:around method="myAround" pointcut-ref="myPointCut"/> -->
            <!-- <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/> -->
            <aop:after method="myAfter" pointcut-ref="myPointCut"/>
        </aop:aspect>
    </aop:config>

</beans>

2.6、基於註解(替代xml)

2.6.1、替換bean

    <!-- 1、創建目標類對象 -->
    <bean id="userServiceId" class="com.itheima.d_aspect.b_annotation.UserServiceImpl"></bean>
    <!-- 2、創建切面類 (通知)-->
    <bean id="myAspectId" class="com.itheima.d_aspect.b_annotation.MyAspect"></bean>
  • 注意:需要配置掃描,註解使用前提,必須添加命名空間,讓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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                              http://www.springframework.org/schema/beans/spring-beans.xsd
                              http://www.springframework.org/schema/aop 
                              http://www.springframework.org/schema/aop/spring-aop.xsd
                              http://www.springframework.org/schema/context 
                              http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 1、組件掃描:掃描含有註解的類 -->
    <context:component-scan base-package="com.itheima.d_aspect.b_annotation"></context:component-scan>
    <!-- 2、確定 aop註解生效 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

2.6.2、替換aop

  • 聲明 切面
以前:
    <aop:aspect ref="myAspectId">
現在:
    // 切面類,含有多個通知
    @Component
    @Aspect
    public class MyAspect {
  • 替換 前置通知
現在:
    <aop:before method="myBefore" pointcut="execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))"/>
以前:
    // 切入點在當前有效
    @Before("execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))")
    public void myBefore(JoinPoint joinPonint) {
        System.out.println("我的前置通知:" + joinPonint.getSignature().getName());
    }
  • 替換 公共切入點
以前:
    <aop:pointcut expression="execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))" id="myPointCut"/>
現在:
    // 聲明公共切入點
    @Pointcut("execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))")
    private void myPointCut() {
    }
  • 替換 後置通知
以前:
    <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret"/>
現在:
    @AfterReturning(value="myPointCut()", returning="ret")
    public void myAfterReturning(JoinPoint joinPoint, Object ret) {
        System.out.println("我的後置通知 : " + joinPoint.getSignature().getName() + ", --> " + ret);
    }
  • 替換 環繞通知
以前:
    <aop:around method="myAround" pointcut-ref="myPointCut"/>
現在:
    @Around(value="myPointCut()")
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("前方法");

        // 手動執行目標方法
        Object obj = joinPoint.proceed();

        System.out.println("後方法");

        return null;
    }
  • 替換 拋出異常通知
以前:
    <aop:after-throwing method="myAfterThrowing" pointcut="execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))" throwing="e"/>
現在:
    @AfterThrowing(value="execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))" ,throwing="e")
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("我的拋出異常通知 : " + e.getMessage());
    }
  • 替換 最終通知
以前:
    <aop:after method="myAfter" pointcut-ref="myPointCut"/>
現在:
    @After("myPointCut()")
    public void myAfter(JoinPoint joinPoint) {
        System.out.println("我的最終通知");
    }

2.6.3、切面類

  MyAspect.java

package com.itheima.d_aspect.b_annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

// 切面類,含有多個通知
@Component
@Aspect
public class MyAspect {

    // 切入點在當前有效
    // @Before("execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))")
    public void myBefore(JoinPoint joinPonint) {
        System.out.println("我的前置通知:" + joinPonint.getSignature().getName());
    }

    // 聲明公共切入點
    // @Pointcut("execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))")
    private void myPointCut() {
    }

    // @AfterReturning(value="myPointCut()", returning="ret")
    public void myAfterReturning(JoinPoint joinPoint, Object ret) {
        System.out.println("我的後置通知 : " + joinPoint.getSignature().getName() + ", --> " + ret);
    }

    @Around(value="myPointCut()")
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("前方法");

        // 手動執行目標方法
        Object obj = joinPoint.proceed();

        System.out.println("後方法");

        return null;
    }

    @AfterThrowing(value="execution(* com.itheima.d_aspect.b_annotation.UserServiceImpl.*(..))" ,throwing="e")
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("我的拋出異常通知 : " + e.getMessage());
    }

    @After("myPointCut()")
    public void myAfter(JoinPoint joinPoint) {
        System.out.println("我的最終通知");
    }
}

2.6.4、spring配置

  bean.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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                              http://www.springframework.org/schema/beans/spring-beans.xsd
                              http://www.springframework.org/schema/aop 
                              http://www.springframework.org/schema/aop/spring-aop.xsd
                              http://www.springframework.org/schema/context 
                              http://www.springframework.org/schema/context/spring-context.xsd">                      
    <!-- 1、組件掃描:掃描含有註解的類 -->
    <context:component-scan base-package="com.itheima.d_aspect.b_annotation"></context:component-scan>
    <!-- 2、確定 aop註解生效 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

2.6.5、aop註解總結

切面
    @Aspect     用於聲明切面,修飾切面類,從而獲得通知。
通知
    @Before     前置通知
    @AfterReturning 後置通知
    @Around     環繞通知
    @AfterThrowing  拋出異常通知
    @After      最終通知
切入點
    @PointCut   該註解修飾方法格式爲:private void xxx(){}  我們通過“方法名”獲得切入點的引用。

三、JdbcTemplate

  • JdbcTemplate是spring 提供用於操作JDBC的工具類,類似:DBUtils。
  • JdbcTemplate依賴連接池DataSource(數據源)

3.1、環境搭建

3.1.1、創建表

CREATE DATABASE day34;
USE day34;

CREATE TABLE t_user(
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50),
  PASSWORD VARCHAR(32)
);

INSERT INTO t_user(username,PASSWORD) VALUES('jack','1234');
INSERT INTO t_user(username,PASSWORD) VALUES('rose','5678');

3.1.2、導入jar包

如下圖所示:

3.1.3、創建數據模型

  User.java

package com.itheima.domain;

public class User {
    private Integer id;
    private String username;
    private String password;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

3.2、使用api(瞭解)

package com.itheima.b_api;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class TestAPI {
    public static void main(String[] args) {

        // 1、創建數據源(連接池), 使用dbcp
        BasicDataSource dataSource = new BasicDataSource();
        // 設置基本4項
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/day34");
        dataSource.setUsername("root");
        dataSource.setPassword("root");

        // 2、 創建模板
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);

        // 3、 通過api操作
        jdbcTemplate.update("insert into t_user(username,password) values(?,?);", "tom", "998");
    }
}

3.3、配置DBCP

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

    <!-- 創建數據源對象,需要注入基本四項 -->
    <bean id="dataSourceId" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/day34"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!-- 創建模板對象 ,需要注入數據源-->
    <bean id="jdbcTemplateId" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSourceId"></property>
    </bean>

    <!-- 配置dao對象 -->
    <bean id="userDaoId" class="com.itheima.c_dbcp.UserDao">
        <property name="jdbcTemplate" ref="jdbcTemplateId"></property>
    </bean>

</beans>

3.4、配置C3P0

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

    <!-- 創建數據源對象,c3p0,需要注入基本四項 -->
    <bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/day34"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!-- 創建模板對象 ,需要注入數據源-->
    <bean id="jdbcTemplateId" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSourceId"></property>
    </bean>

    <!-- 配置dao對象,需要注入jdbc模板  -->
    <bean id="userDaoId" class="com.itheima.d_c3p0.UserDao">
        <property name="jdbcTemplate" ref="jdbcTemplateId"></property>
    </bean>

</beans>

3.5、使用JdbcDaoSupport

    // jdbc的模板將由spring注入
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

  由於每一個dao裏面都需要將jdbc模板代碼使用Spring注入進來(如上代碼),比較麻煩,所以Spring就想了一個招:把jdbc模板代碼寫到一個父類中,然後讓dao去繼承它。這個父類叫做JdbcDaoSupport。

3.5.1、dao層

package com.itheima.e_JdbcDaoSupport;

import java.util.List;

import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

import com.itheima.a_domain.User;

public class UserDao extends JdbcDaoSupport {

    // 更新用戶
    public void update(User user) {
        String sql = "update t_user set username=?,password=? where id =?";
        Object[] args = { user.getUsername(), user.getPassword(), user.getId() };
        this.getJdbcTemplate().update(sql, args);
    }

    // 查詢所有
    public List<User> findAll() {
        return this.getJdbcTemplate().query("select * from t_user", ParameterizedBeanPropertyRowMapper.newInstance(User.class));
    }
}

3.5.2、spring配置文件

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

    <!-- 創建數據源對象,c3p0,需要注入基本四項 -->
    <bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/day34"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!-- 配置dao對象,需要注入jdbc模板  ,需要注入數據源
         由於dao繼承了 JdbcDaoSupport,之後只需要注入數據源即可,底層將自動創建模板。
    -->
    <bean id="userDaoId" class="com.itheima.e_JdbcDaoSupport.UserDao">
        <property name="dataSource" ref="dataSourceId"></property>
    </bean>

</beans>

3.5.3、JdbcDaoSupport源碼分析

3.6、配置properties

  以後在開發中,我們會將創建數據源對象時需要注入的基本四項,放在一個properties文件中。   JdbcInfo.properties

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/day34
jdbc.user=root
jdbc.password=root

  beans.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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                              http://www.springframework.org/schema/beans/spring-beans.xsd
                              http://www.springframework.org/schema/aop 
                              http://www.springframework.org/schema/aop/spring-aop.xsd
                              http://www.springframework.org/schema/context 
                              http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 加載配置文件 JdbcInfo.properties
         "classpath:" 該前綴表示在src目錄下
          在配置文件中通過  ${key} 獲得配置文件 內容(值)
    -->
    <context:property-placeholder location="classpath:com/itheima/f_properties/jdbcInfo.properties"/>

    <!-- 創建數據源對象,c3p0,需要注入基本四項 -->
    <bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}"></property>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
        <property name="user" value="${jdbc.user}"></property>
        <property name="password"  value="${jdbc.password}"></property>
    </bean>

    <!-- 配置dao對象,需要注入jdbc模板  ,需要注入數據源
         由於dao繼承了 JdbcDaoSupport,之後只需要注入數據源即可,底層將自動創建模板。
    -->
    <bean id="userDaoId" class="com.itheima.f_properties.UserDao">
        <property name="dataSource" ref="dataSourceId"></property>
    </bean>

</beans>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章