TIP

AOP是Spring框架的两大特性之一,面向切面编程,实现原理是代理模式,有点不太理解代理模式,理一理。

# 什么是代理模式

代理模式,简单的来说就是中转站。例如省长要开会,不会把我们老百姓叫过去,而是市长过去,市长了解之后又给县长开;一个人要买房,然后找中介,卖方的老板并不直接与买家交流,而是通过中介传达意愿交易,最终把房卖掉;媒婆(并不一定女的),通常都是两家都比较熟悉的人,要结婚办事,一些不好说的话,就是通过媒婆去传达;vpn上网,本地连接网络代理服务器,不直接与互联网进行数据交换,而是通过代理。

«interface»Internet+ method(type): typeNetWork+ method(type): type
Use
Use
ProxyNetWork+ netWork: NetWork+ method(type): type
Viewer does not support full SVG 1.1

# 情景:数学计算器

有两个要求

  • 执行加减乘除运算

  • 日志:在程序执行期间追踪正在发生的活动

需要实现加减乘除四个方法,并且在程序运行的过程需要打印日志(调用方法名,传递的参数,计算结果等)

接下来用以下各种各种方式实现。

# 普通实现

public class App 
{
    public static void main( String[] args )
    {
        MathI mathI = new MathImpl();
        int result = mathI.sub(1,2);
        System.out.println(result);
    }
}
public interface MathI {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
}
public class MathImpl implements MathI{
    @Override
    public int add(int i, int j) {
        System.out.println("method:add,arguments:"+i+","+j);
        int result = i + j;
        System.out.println("method:add,result:"+result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        System.out.println("method:sub,arguments:"+i+","+j);
        int result = i - j;
        System.out.println("method:sub,result:"+result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        System.out.println("method:mul,arguments:"+i+","+j);
        int result = i * j;
        System.out.println("method:mul,result:"+result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        System.out.println("method:div,arguments:"+i+","+j);
        int result = i / j;
        System.out.println("method:div,result:"+result);
        return result;
    }
}

一个计算接口中,定义了加减乘除四个方法,计算器实现计算类,并且在运行的时候打印了所调用的方法,传入的参数,计算的结果,每个方法体里面的结构都是差不多的,只是调用不同的方法,结果,标识有些不同。业务代码与非业务代码也混到了一起,显得混乱,我看着还挺整齐的。

# 静态代理实现

public class App 
{
    public static void main( String[] args )
    {
        ProxyUtil proxy = new ProxyUtil(new MathImpl());
        int result = proxy.sub(1,2);
        System.out.println(result);
    }
}
public interface MathI {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
}
public class MathImpl implements MathI{
    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }
    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }
    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}
public class Proxy implements MathI{
    public MathImpl mathImpl;
    public Proxy(MathImpl mathImpl) {
        this.mathImpl = mathImpl;
    }
    @Override
    public int add(int i, int j) {
        System.out.println("method:add,arguments:"+i+","+j);
        int result = mathImpl.add(i,j);
        System.out.println("method:add,result:"+result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        System.out.println("method:sub,arguments:"+i+","+j);
        int result = mathImpl.sub(i,j);
        System.out.println("method:sub,result:"+result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        System.out.println("method:mul,arguments:"+i+","+j);
        int result = mathImpl.mul(i,j);
        System.out.println("method:mul,result:"+result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        System.out.println("method:div,arguments:"+i+","+j);
        int result = mathImpl.div(i,j);
        System.out.println("method:div,result:"+result);
        return result;
    }
}

这种实现方式看起来并没有改进多少,相反代码更加多了,但是使MathImpl的业务代码和非业务代码隔离了,也算是进步了吧。

# JDK动态代理

public class App 
{
    public static void main( String[] args )
    {
        ProxyUtil proxy = new ProxyUtil(new MathImpl());
        MathI math = (MathI) proxy.getProxy();
        int result = math.add(1,2);
        System.out.println(result);
    }
}
public interface MathI {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
}
public class MathImpl implements MathI{
    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }
    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }
    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class ProxyUtil {
    public MathImpl mathImpl;
    public ProxyUtil(MathImpl mathImpl) {
        this.mathImpl = mathImpl;
    }
    public Object getProxy() {
        //第一个参数类加载器。用来加载代理对象所属类。
        ClassLoader loader = this.getClass().getClassLoader();
        //第二个参数interfaces,目标对象实现所有的接口的class对象。
        Class[] interfaces = mathImpl.getClass().getInterfaces();
        //第三个参数代理对象如何实现目标对象的方法。
        return Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("method:"+method.getName()+",args:"+ Arrays.toString(args));
                Object obj = method.invoke(mathImpl,args);
                System.out.println("method:"+method.getName()+",result:" + obj.toString());
                return obj;
            }
        });
    }
}

# cglib动态代理

CGlib动态代理和JDK动态代理相区别的地方就是,CGlib必须要包含继承,JDK必须包含接口实现。

# AspectJ实现

AspectJ并不是Spring自带的,是实现AOP的最好实现方式。

首先在pom.xml中添加以下依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.2</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>

然后再创建applicationContext.xml,内容如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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/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">
    <!--开启aspectJ自动代理-->
    <aop:aspectj-autoproxy />
    <context:component-scan base-package="org.maven.spring">
    </context:component-scan>
</beans>
public class App 
{
    public static void main( String[] args )
    {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        MathI mathI = ac.getBean("mathImpl",MathI.class);
        int result = mathI.add(1,2);
        System.out.println(result);
    }
}
public interface MathI {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
}
@Component
public class MathImpl implements MathI{
    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }
    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }
    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}
//标识为一个切面
@Component
@Aspect
public class MyloggerAspect {
    //切面里面的方法就叫做通知
    //@Before将方法指定为前置通知,里面些切入表达式
    //通知需要关注切入点
    //当通知执行时就会把方法的一些信息添加到JoinPoint中
    @Before(value = "execution(* org.maven.spring.*.*(..))")
    public void beforeMethod(JoinPoint joinPoint) {
        //获取方法的参数
        Object[] args = joinPoint.getArgs();
        //getSignation将切面中的名字进行封装
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println("method:"+methodName+",args:"+ Arrays.toString(args));
    }
    //返回通知
    //可用returning设置接受方法的变量名
    //在方法的参数中也应该改添加上
    @AfterReturning(value = "execution(* org.maven.spring.*.*(..))",returning = "result")
    public void afterReturning(JoinPoint joinPoint , Object result) {
        Object[] args = joinPoint.getArgs();
        String methodName = joinPoint.getSignature().getName();
        System.out.println("method:" + methodName+",result:" + result);
    }
}

# 切入点表达式

TIP

通常的形式

execution(public int org.maven.spring.MathImpl.add(int,int))

可以改写成

execution(* org.maven.spring.*.*(..))

第一个代表任意返回值任意类型 第二个代表此包下任意类 第三个*代表任意方法 两个点代表任意参数

# 切入点简化方式

TIP

@Pointcut(value = "execution(* org.maven.spring..(..))") public void proxy(){}

@Before(value="proxy()") public void auto(){}

# 五种通知类型

  • @Before作用于方法执行之前

TIP

@Before(value = "execution(* org.maven.spring.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
    //获取方法的参数
    Object[] args = joinPoint.getArgs();
    //getSignation将切面中的名字进行封装
    //获取方法名
    String methodName = joinPoint.getSignature().getName();
    System.out.println("method:"+methodName+",args:"+ Arrays.toString(args));
}
  • @After作用于finally语句块

TIP

@After(value = "execution(* org.maven.spring.*.*(..))")
public void after() {
    System.out.println("后置通知");
}
  • @AfterReturning作用于方法执行之后

TIP

@AfterReturning(value = "execution(* org.maven.spring.*.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint , Object result) {
    Object[] args = joinPoint.getArgs();
    String methodName = joinPoint.getSignature().getName();
    System.out.println("method:" + methodName+",result:" + result);
}
  • @AfterThrowing作用于catch块

TIP

//可捕捉是什么错误,用throwing
@AfterThrowing(value = "execution(* org.maven.spring.*.*(..))",throwing = "e")
public void afterThrowing(Exception e) {
    System.out.println("有异常"+e);
}
  • @Around可实现以上四种

TIP

@Around(value = "execution(* org.maven.spring.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) {
    Object result = null;
    try {
        //前置通知
        result = joinPoint.proceed();//执行方法
        //返回通知
        return result;
    } catch (Throwable e) {
        //异常通知
    } finally {
        //后置通知
    }
    return -1;
}

# spring框架之xml实现

applicationContext.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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/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">
    <context:component-scan base-package="org.maven.spring"></context:component-scan>
    <aop:config>
        <aop:aspect ref="myloggerAspect">
            <aop:before method="before" pointcut="execution(* org.maven.spring.*.*(..))"/>
            <aop:after-returning method="afterReturning"
                                 pointcut="execution(* org.maven.spring.*.*(..))"
                                 returning="result"
            />
        </aop:aspect>
    </aop:config>
</beans>

除了切面代码有所改动,其余与AspectJ实现相同。

@Component
public class MyloggerAspect {
    public void before(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("method:"+name+",args:"+Arrays.toString(args));
    }
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String name = joinPoint.getSignature().getName();
        System.out.println("method:"+name+",result:"+result);
    }
}

# 参考连接

尚硅谷spring (opens new window)