Spring【AOP模塊】就是這麼簡單

前言

到目前爲止,已經簡單學習了Spring的Core模塊、....因而咱們就開啓了Spring的AOP模塊了...在講解AOP模塊以前,首先咱們來說解一下cglib代理、以及怎麼手動實現AOP編程java

cglib代理

在講解cglib以前,首先咱們來回顧一下靜態代理和動態代理....我以前就寫過了靜態代理、動態代理的博文:blog.csdn.net/hon_3y/arti…spring

因爲靜態代理須要實現目標對象的相同接口,那麼可能會致使代理類會很是很是多....很差維護---->所以出現了動態代理express

動態代理也有個約束:目標對象必定是要有接口的,沒有接口就不能實現動態代理.....----->所以出現了cglib代理編程

cglib代理也叫子類代理,從內存中構建出一個子類來擴展目標對象的功能!微信

  • CGLIB是一個強大的高性能的代碼生成包,它能夠在運行期擴展Java類與實現Java接口。它普遍的被許多AOP的框架使用,例如Spring AOP和dynaop,爲他們提供方法的interception(攔截)。

編寫cglib代理

接下來咱們就講講怎麼寫cglib代理:session

  • 須要引入cglib – jar文件, 可是spring的核心包中已經包括了cglib功能,因此直接引入spring-core-3.2.5.jar便可。
  • 引入功能包後,就能夠在內存中動態構建子類
  • 代理的類不能爲final,不然報錯【在內存中構建子類來作擴展,固然不能爲final,有final就不能繼承了】
  • 目標對象的方法若是爲final/static, 那麼就不會被攔截,即不會執行目標對象額外的業務方法。
//須要實現MethodInterceptor接口
public class ProxyFactory implements MethodInterceptor{
	
	// 維護目標對象
	private Object target;
	public ProxyFactory(Object target){
		this.target = target;
	}
	
	// 給目標對象建立代理對象
	public Object getProxyInstance(){
		//1. 工具類
		Enhancer en = new Enhancer();
		//2. 設置父類
		en.setSuperclass(target.getClass());
		//3. 設置回調函數
		en.setCallback(this);
		//4. 建立子類(代理對象)
		return en.create();
	}
	

	@Override
	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {
		
		System.out.println("開始事務.....");
		
		// 執行目標對象的方法
		Object returnValue = method.invoke(target, args);
		
		System.out.println("提交事務.....");
		
		return returnValue;
	}

}
  • 測試:
public class App {

    public static void main(String[] args) {

        UserDao userDao = new UserDao();

        UserDao factory = (UserDao) new ProxyFactory(userDao).getProxyInstance();

        factory.save();
    }
}

 

這裏寫圖片描述

 

 

這裏寫圖片描述

 

使用cglib就是爲了彌補動態代理的不足【動態代理的目標對象必定要實現接口】app

手動實現AOP編程

AOP 面向切面的編程:框架

  • AOP能夠實現「業務代碼」與「關注點代碼」分離

下面咱們來看一段代碼:ide

// 保存一個用戶
public void add(User user) { 
		Session session = null; 
		Transaction trans = null; 
		try { 
			session = HibernateSessionFactoryUtils.getSession();   // 【關注點代碼】
			trans = session.beginTransaction();    // 【關注點代碼】
			 
			session.save(user);     // 核心業務代碼
			 
			trans.commit();     //…【關注點代碼】

		} catch (Exception e) {     
			e.printStackTrace(); 
			if(trans != null){ 
				trans.rollback();   //..【關注點代碼】

			} 
		} finally{ 
			HibernateSessionFactoryUtils.closeSession(session);   ////..【關注點代碼】

		} 
   }
  • 關注點代碼,就是指重複執行的代碼。
  • 業務代碼與關注點代碼分離,好處?
    • 關注點代碼寫一次便可
    • 開發者只須要關注核心業務
    • 運行時期,執行核心業務代碼時候動態植入關注點代碼; 【代理】

案例分析:

  • IUser接口
public interface IUser {

    void save();
}

咱們一步一步來分析,首先咱們的UserDao有一個save()方法,每次都要開啓事務和關閉事務函數

//@Component  -->任何地方都能用這個
@Repository  //-->這個在Dao層中使用
    public class UserDao {

    public void save() {

        System.out.println("開始事務");
        System.out.println("DB:保存用戶");
        System.out.println("關閉事務");

    }


}
  • 在剛學習java基礎的時候,咱們知道:若是某些功能常常須要用到就封裝成方法:
//@Component  -->任何地方都能用這個
@Repository  //-->這個在Dao層中使用
    public class UserDao {

    public void save() {

        begin();
        System.out.println("DB:保存用戶");
        close();
        
    }

    public void begin() {
        System.out.println("開始事務");
    }
    public void close() {
        System.out.println("關閉事務");
    }
}
  • 如今呢,咱們可能有多個Dao,都須要有開啓事務和關閉事務的功能,如今只有UserDao中有這兩個方法,重用性仍是不夠高。所以咱們抽取出一個類出來
public class AOP {
    
    public void begin() {
        System.out.println("開始事務");
    }
    public void close() {
        System.out.println("關閉事務");
    }
}
  • 在UserDao維護這個變量,要用的時候,調用方法就好了
@Repository  //-->這個在Dao層中使用
public class UserDao {


    AOP aop;

    public void save() {

        aop.begin();
        System.out.println("DB:保存用戶");
        aop.close();

    }
    
}
  • 如今的開啓事務、關閉事務仍是須要我在userDao中手動調用。仍是不夠優雅。。我想要的效果:當我在調用userDao的save()方法時,動態地開啓事務、關閉事務。所以,咱們就用到了代理。固然了,真正執行方法的都是userDao、要幹事的是AOP,所以在代理中須要維護他們的引用
public class ProxyFactory {
    //維護目標對象
    private static Object target;

    //維護關鍵點代碼的類
    private static AOP aop;
    public static Object getProxyInstance(Object target_, AOP aop_) {

        //目標對象和關鍵點代碼的類都是經過外界傳遞進來
        target = target_;
        aop = aop_;

        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        aop.begin();
                        Object returnValue = method.invoke(target, args);
                        aop.close();

                        return returnValue;
                    }
                }
        );
    }
}

工廠靜態方法:

  • 把AOP加入IOC容器中
//把該對象加入到容器中
@Component
public class AOP {

    public void begin() {
        System.out.println("開始事務");
    }
    public void close() {
        System.out.println("關閉事務");
    }
}
  • 把UserDao放入容器中
@Component
public class UserDao {

    public void save() {

        System.out.println("DB:保存用戶");

    }

}
  • 在配置文件中開啓註解掃描,使用工廠靜態方法建立代理對象
<?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:p="http://www.springframework.org/schema/p"
       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.xsd">


    <bean id="proxy" class="aa.ProxyFactory" factory-method="getProxyInstance">
        <constructor-arg index="0" ref="userDao"/>
        <constructor-arg index="1" ref="AOP"/>
    </bean>

    <context:component-scan base-package="aa"/>





</beans>
  •  
  • 測試,獲得UserDao對象,調用方法

public class App {

    public static void main(String[] args) {

        ApplicationContext ac =
                new ClassPathXmlApplicationContext("aa/applicationContext.xml");


        IUser iUser = (IUser) ac.getBean("proxy");

        iUser.save();



    }
}

 

這裏寫圖片描述

 

工廠非靜態方法

上面使用的是工廠靜態方法來建立代理類對象。咱們也使用一下非靜態的工廠方法建立對象

package aa;

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

/**
 * Created by ozc on 2017/5/11.
 */

public class ProxyFactory {

    public Object getProxyInstance(final Object target_, final AOP aop_) {

        //目標對象和關鍵點代碼的類都是經過外界傳遞進來

        return Proxy.newProxyInstance(
                target_.getClass().getClassLoader(),
                target_.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        aop_.begin();
                        Object returnValue = method.invoke(target_, args);
                        aop_.close();

                        return returnValue;
                    }
                }
        );
    }
}

配置文件:先建立工廠,再建立代理類對象

<?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:p="http://www.springframework.org/schema/p"
       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.xsd">



    <!--建立工廠-->
    <bean id="factory" class="aa.ProxyFactory"/>


    <!--經過工廠建立代理-->
    <bean id="IUser" class="aa.IUser" factory-bean="factory" factory-method="getProxyInstance">
        <constructor-arg index="0" ref="userDao"/>
        <constructor-arg index="1" ref="AOP"/>
    </bean>


    <context:component-scan base-package="aa"/>


</beans>

 

這裏寫圖片描述

 

AOP的概述

Aop: aspect object programming 面向切面編程

  • 功能: 讓關注點代碼與業務代碼分離!
  • 面向切面編程就是指: 對不少功能都有的重複的代碼抽取,再在運行的時候往業務方法上動態植入「切面類代碼」。

關注點:

  • 重複代碼就叫作關注點。
// 保存一個用戶
public void add(User user) { 
		Session session = null; 
		Transaction trans = null; 
		try { 
			session = HibernateSessionFactoryUtils.getSession();   // 【關注點代碼】
			trans = session.beginTransaction();    // 【關注點代碼】
			 
			session.save(user);     // 核心業務代碼
			 
			trans.commit();     //…【關注點代碼】

		} catch (Exception e) {     
			e.printStackTrace(); 
			if(trans != null){ 
				trans.rollback();   //..【關注點代碼】

			} 
		} finally{ 
			HibernateSessionFactoryUtils.closeSession(session);   ////..【關注點代碼】

		} 
   }

切面:

  • 關注點造成的類,就叫切面(類)!
public class AOP {

    public void begin() {
        System.out.println("開始事務");
    }
    public void close() {
        System.out.println("關閉事務");
    }
}

切入點:

  • 執行目標對象方法,動態植入切面代碼。
  • 能夠經過切入點表達式指定攔截哪些類的哪些方法; 給指定的類在運行的時候植入切面類代碼

切入點表達式:

  • 指定哪些類的哪些方法被攔截

使用Spring AOP開發步驟

1) 先引入aop相關jar文件 (aspectj aop優秀組件)

  • spring-aop-3.2.5.RELEASE.jar 【spring3.2源碼】
  • aopalliance.jar 【spring2.5源碼/lib/aopalliance】
  • aspectjweaver.jar 【spring2.5源碼/lib/aspectj】或【aspectj-1.8.2\lib】
  • aspectjrt.jar 【spring2.5源碼/lib/aspectj】或【aspectj-1.8.2\lib】

注意: 用到spring2.5版本的jar文件,若是用jdk1.7可能會有問題

  • 須要升級aspectj組件,即便用aspectj-1.8.2版本中提供jar文件提供。

2) bean.xml中引入aop名稱空間

  • xmlns:context="http://www.springframework.org/schema/context"
  • http://www.springframework.org/schema/context
  • http://www.springframework.org/schema/context/spring-context.xsd

引入jar包

引入4個jar包:

 

這裏寫圖片描述

 

引入名稱空間

<?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:p="http://www.springframework.org/schema/p"
       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.xsd">
    
</beans>

註解方式實現AOP編程

咱們以前手動的實現AOP編程是須要本身來編寫代理工廠的**,如今有了Spring,就不須要咱們本身寫代理工廠了。Spring內部會幫咱們建立代理工廠**。

  • 也就是說,不用咱們本身寫代理對象了。

所以,咱們只要關心切面類、切入點、編寫切入表達式指定攔截什麼方法就能夠了!

仍是以上一個例子爲案例,使用Spring的註解方式來實現AOP編程

在配置文件中開啓AOP註解方式

<?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:p="http://www.springframework.org/schema/p"
       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/context
        http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <context:component-scan base-package="aa"/>

    <!-- 開啓aop註解方式 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

代碼:

  • 切面類
@Component
@Aspect//指定爲切面類
public class AOP {


	//裏面的值爲切入點表達式
    @Before("execution(* aa.*.*(..))")
    public void begin() {
        System.out.println("開始事務");
    }


    @After("execution(* aa.*.*(..))")
    public void close() {
        System.out.println("關閉事務");
    }
}
  • UserDao實現了IUser接口
@Component
public class UserDao implements IUser {

    @Override
    public void save() {
        System.out.println("DB:保存用戶");
    }

}
  • IUser接口
public interface IUser {
    void save();
}
  • 測試代碼:
public class App {

    public static void main(String[] args) {

        ApplicationContext ac =
                new ClassPathXmlApplicationContext("aa/applicationContext.xml");

		//這裏獲得的是代理對象....
        IUser iUser = (IUser) ac.getBean("userDao");

        System.out.println(iUser.getClass());

        iUser.save();
        
    }
}

 

這裏寫圖片描述

 

目標對象沒有接口

上面咱們測試的是UserDao有IUser接口,內部使用的是動態代理...那麼咱們此次測試的是目標對象沒有接口

  • OrderDao沒有實現接口
@Component
public class OrderDao {

    public void save() {

        System.out.println("我已經進貨了!!!");
        
    }
}
  • 測試代碼:
public class App {

    public static void main(String[] args) {

        ApplicationContext ac =
                new ClassPathXmlApplicationContext("aa/applicationContext.xml");

        OrderDao orderDao = (OrderDao) ac.getBean("orderDao");

        System.out.println(orderDao.getClass());

        orderDao.save();

    }
}

 

這裏寫圖片描述

 

優化和AOP註解API

API:

  • @Aspect 指定一個類爲切面類

  • @Pointcut("execution( cn.itcast.e_aop_anno..(..))") 指定切入點表達式*

  • @Before("pointCut_()") 前置通知: 目標方法以前執行

  • @After("pointCut_()") 後置通知:目標方法以後執行(始終執行)

  • @AfterReturning("pointCut_()") 返回後通知: 執行方法結束前執行(異常不執行)

  • @AfterThrowing("pointCut_()") 異常通知: 出現異常時候執行

  • @Around("pointCut_()") 環繞通知: 環繞目標方法執行

  • 測試:

// 前置通知 : 在執行目標方法以前執行
	@Before("pointCut_()")
	public void begin(){
		System.out.println("開始事務/異常");
	}
	
	// 後置/最終通知:在執行目標方法以後執行  【不管是否出現異常最終都會執行】
	@After("pointCut_()")
	public void after(){
		System.out.println("提交事務/關閉");
	}
	
	// 返回後通知: 在調用目標方法結束後執行 【出現異常不執行】
	@AfterReturning("pointCut_()")
	public void afterReturning() {
		System.out.println("afterReturning()");
	}
	
	// 異常通知: 當目標方法執行異常時候執行此關注點代碼
	@AfterThrowing("pointCut_()")
	public void afterThrowing(){
		System.out.println("afterThrowing()");
	}
	
	// 環繞通知:環繞目標方式執行
	@Around("pointCut_()")
	public void around(ProceedingJoinPoint pjp) throws Throwable{
		System.out.println("環繞前....");
		pjp.proceed();  // 執行目標方法
		System.out.println("環繞後....");
	}

優化

咱們的代碼是這樣的:每次寫Before、After等,都要重寫一次切入點表達式,這樣就不優雅了。

@Before("execution(* aa.*.*(..))")
    public void begin() {
        System.out.println("開始事務");
    }


    @After("execution(* aa.*.*(..))")
    public void close() {
        System.out.println("關閉事務");
    }

因而乎,咱們要使用@Pointcut這個註解,來指定切入點表達式,在用到的地方中,直接引用就好了!

  • 那麼咱們的代碼就能夠改形成這樣了:
@Component
@Aspect//指定爲切面類
public class AOP {


    // 指定切入點表達式,攔截哪一個類的哪些方法
    @Pointcut("execution(* aa.*.*(..))")
    public void pt() {

    }

    @Before("pt()")
    public void begin() {
        System.out.println("開始事務");
    }


    @After("pt()")
    public void close() {
        System.out.println("關閉事務");
    }
}

XML方式實現AOP編程

首先,咱們把全部的註解都去掉...

  • 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:p="http://www.springframework.org/schema/p"
       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/context
        http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--對象實例-->
    <bean id="userDao" class="aa.UserDao"/>
    <bean id="orderDao" class="aa.OrderDao"/>

    <!--切面類-->
    <bean id="aop" class="aa.AOP"/>

    <!--AOP配置-->
    <aop:config >

        <!--定義切入表達式,攔截哪些方法-->
        <aop:pointcut id="pointCut" expression="execution(* aa.*.*(..))"/>

        <!--指定切面類是哪一個-->
        <aop:aspect ref="aop">

            <!--指定來攔截的時候執行切面類的哪些方法-->
            <aop:before method="begin" pointcut-ref="pointCut"/>
            <aop:after method="close" pointcut-ref="pointCut"/>

        </aop:aspect>
    </aop:config>

    
</beans>
  • 測試:
public class App {

    @Test
    public  void test1() {

        ApplicationContext ac =
                new ClassPathXmlApplicationContext("aa/applicationContext.xml");

        OrderDao orderDao = (OrderDao) ac.getBean("orderDao");

        System.out.println(orderDao.getClass());

        orderDao.save();

    }

    @Test
    public  void test2() {

        ApplicationContext ac =
                new ClassPathXmlApplicationContext("aa/applicationContext.xml");

        IUser userDao = (IUser) ac.getBean("userDao");

        System.out.println(userDao.getClass());

        userDao.save();

    }
}

測試OrderDao

 

這裏寫圖片描述

 

測試UserDao

 

這裏寫圖片描述

 

切入點表達式

切入點表達式主要就是來配置攔截哪些類的哪些方法

查官方文檔

..咱們去文檔中找找它的語法...

 

這裏寫圖片描述

 

在文檔中搜索:execution(

 

這裏寫圖片描述

 

語法解析

那麼它的語法是這樣子的:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

符號講解:

  • ?號表明0或1,能夠不寫
  • 「*」號表明任意類型,0或多
  • 方法參數爲..表示爲可變參數

參數講解:

  • modifiers-pattern?【修飾的類型,能夠不寫】
  • ret-type-pattern【方法返回值類型,必寫】
  • declaring-type-pattern?【方法聲明的類型,能夠不寫】
  • name-pattern(param-pattern)【要匹配的名稱,括號裏面是方法的參數】
  • throws-pattern?【方法拋出的異常類型,能夠不寫】

官方也有給出一些例子給咱們理解:

 

這裏寫圖片描述

 

測試代碼

<!-- 【攔截全部public方法】 -->
		<!--<aop:pointcut expression="execution(public * *(..))" id="pt"/>-->
		
		<!-- 【攔截全部save開頭的方法 】 -->
		<!--<aop:pointcut expression="execution(* save*(..))" id="pt"/>-->
		
		<!-- 【攔截指定類的指定方法, 攔截時候必定要定位到方法】 -->
		<!--<aop:pointcut expression="execution(public * cn.itcast.g_pointcut.OrderDao.save(..))" id="pt"/>-->
		
		<!-- 【攔截指定類的全部方法】 -->
		<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.*(..))" id="pt"/>-->
		
		<!-- 【攔截指定包,以及其自包下全部類的全部方法】 -->
		<!--<aop:pointcut expression="execution(* cn..*.*(..))" id="pt"/>-->
		
		<!-- 【多個表達式】 -->
		<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) || execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
		<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) or execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
		<!-- 下面2個且關係的,沒有意義 -->
		<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) &amp;&amp; execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
		<!--<aop:pointcut expression="execution(* cn.itcast.g_pointcut.UserDao.save()) and execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->
		
		<!-- 【取非值】 -->
		<!--<aop:pointcut expression="!execution(* cn.itcast.g_pointcut.OrderDao.save())" id="pt"/>-->

若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠關注微信公衆號:Java3y

相關文章
相關標籤/搜索