基於SpringBoot實現AOP+jdk/CGlib動態代理詳解


動態代理是一種設計模式。在Spring中,有倆種方式可以實現動態代理--JDK動態代理和CGLIB動態代理。

JDK動態代理

首先定義一個人的接口:

public interface Person {
	void study();
}

然后接上一個Student class

public class Student implements Person{
	@Override
	public void study() {
		System.out.println("學生要學習");
	}
}

然后我們創建一個動態代理類,需要實現InvocationHandler接口

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

public class AnimalInvocationHandler implements InvocationHandler {
	private Object target;
	
	public Object bind(Object target) {
		this.target = target;
		return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
	}
	@Override
	public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
		Object result = null;
		System.out.println("----調用前處理");
		result = method.invoke(target,args);
		System.out.println("----調用后處理");
		return result;
	}
}

然后給一個main方法。

public class Test {
	public static void main(String[] args) {
		Student dog = new Student();
		AnimalInvocationHandler ani = new AnimalInvocationHandler();
		Person proxy = (Person)ani.bind(dog);
		proxy.study();
	}
}

運行結果如下。

想要在student對象前后加上額外的邏輯,可以不直接修改study方法。

這就是AOP實現的基本原理,只是Spring不需要開發人員自己維護。

但是這么實現有個缺點,那就是必須實現接口。煩死了。所以我們要用CGLIB了。

CGLIB動態代理

首先把。這玩意是個開源包。
給個下載地址:
https://repo1.maven.org/maven2/cglib/cglib/3.3.0/cglib-3.3.0.jar
https://repo1.maven.org/maven2/org/ow2/asm/asm/7.0/asm-7.0.jar
下載之后添加到eclipse里面。

首先是Teacher類

public class Teacher {
	public void play(){
		System.out.println("老師改作業");
	}
}

然后是這個,需要重寫MethodInterceptor

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class TeacherMethodInterceptor implements MethodInterceptor {
	@Override
	public Object intercept(Object o,Method method,Object[] objects,MethodProxy methodProxy) throws Throwable{
		
		System.out.println("調用前。。。");
		Object obj = methodProxy.invokeSuper(o,objects);
		System.out.println("調用后。。。");
		return obj;
	}
}

main方法如下所示

import net.sf.cglib.proxy.Enhancer;

public class CglibDemo {
	public static void main(String[] args) {
		Enhancer en = new Enhancer();
		en.setSuperclass(Teacher.class);
		en.setCallback(new TeacherMethodInterceptor());
		Teacher t = (Teacher)en.create();
		t.play();
	}
}

運行結果如下:

這就實現了橫向編程。

AOP

面向切面編程是面向對象編程的一種補充。
以Java為例,提供了封裝,繼承,多態等概念,實現了面向對象編程。但是假如我們要實現以下場景。

給每個類設置權限攔截器。

如果不用AOP思想,我們都能瘋掉。因為會有大量代碼重用重寫。但是AOP的出現提供“橫向”的邏輯,將與多個對象有關的公共模塊分裝成一個可重用模塊,並且將這個模塊整合成Aspect,即切面。

AOP的一些概念,整理成表如下:

名稱 概念
橫切關注點 一個橫切需求(例如日志)
切面 一個橫切關注點可能有多個對象
連接點 一個方法的執行
切入點 AspectJ的切入點語法
通知 攔截后的動作
目標對象 業務中需要增強的對象
織入 將切面作用到對象
引入 不用定義接口就能使用其中的方法

Spring的AOP實現

由於Spring framework 的依賴過多,具體哪個jar包缺了啥報啥錯啥版本能把我弄吐血。
為了頭發,我這里采用SpringBoot來實現AOP

首先打開InteliJ

new Project 完之后一直點就行。
啥都不用勾選。
然后我們會發現

啟動如果沒報錯,那就完事。

報錯了去搜搜怎么搭建Spring-boot。都是一鍵生成的。

下面開始敲代碼:注意!一個東西都不能落下!!

首先我們修改一下pom文件

我的pom文件如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo1</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

完整路徑如下所示:

首先是Fruit類

package com.example.demo1;

public interface Fruit {
    void eat();
}

然后是Apple類

package com.example.demo1;

import org.springframework.stereotype.Component;

@Component
public class Apple implements Fruit {
    @Override
    public void eat() {
        System.out.println("吃蘋果");
    }
}

Orange類

package com.example.demo1;

import org.springframework.stereotype.Component;

@Component
public class Orange implements Fruit {
    @Override
    public void eat() {
        System.out.println("吃桔子");
    }
}

然后是FruitAnnotationHandler 類

@execution的含義是匹配該包下任意類的任意方法名的任意入參的任意方法返回值。

@Aspect用來聲明這是切面,注解“@Before”用來表明前置通知,“@After用來表示后置通知”

package com.example.demo1;

import org.aspectj.lang.annotation.After;
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 FruitAnnotationHandler {
    /**
     * 定義切點
     */
    @Pointcut("execution(* com.example.demo1.*.*(..))")
    public void eatFruit(){

    }
    /**
     * 前置通知
     */
    @Before("eatFruit()")
    public void startEatFruit(){
        System.out.println("要開始吃了");
    }
    /**
     * 后置通知
     */
    @After("eatFruit()")
    public void endEatFruit(){
        System.out.println("吃完了");
    }
}

最后是Application類

package com.example.demo1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Demo1Application {
    public static void main(String[] args) {
        ApplicationContext app = SpringApplication.run(Demo1Application.class, args);
        Fruit apple = app.getBean(Apple.class);
        Fruit orange = app.getBean(Orange.class);
        apple.eat();
        orange.eat();
    }
}

然后運行~

運行成功完美!

其實SpringBoot默認的AOP實現就是使用的CGLib代理。

我們並不用定義哪個Fruit接口。

但是你如果脾氣倔,非要用jdk代理的話。

把這個加上就OK了。

如果你沒定義接口的話,下場就是這樣。

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo1.Apple' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:346)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:337)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)
at com.example.demo1.Demo1Application.main(Demo1Application.java:11)

至此我們就完成了AOP的入門


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM