Spring Aop:面向切面编程

244 阅读6分钟

面向Google编程的我第一次遇到了挑战:面向切面编程.

代理模式

要理解 Aop ,首先要了解一下代理模式.

代理有两种基本方式:静态代理和动态代理.

静态代理

什么是静态代理呢,举个栗子:

我现在要租房,某个房东要出租房,那么最简单的就是我直接去联系房租就好了.

现在问题来了,房东不能 7X24在线,不能带我看房.我和房东的时间总是赶不到一块去,
租房进度严重减慢. 那怎么办呢,房东直接委托中介公司和我交涉,这样只要我有需求,
中介公司就能满足我的所有需求.这里的中介就是一个代理.具体一下,是中介公司的某个
员工是一个代理.

所谓静态代理,就是通过 代理类 的形式对某个类的功能进行继承和扩展.

import java.util.Date;

public class HelloProxy {
    public static void main(String[] args) {
        //代理首先需要有一个代理的对象
        Host oldWang = new Host();
        //进行代理
        proxy proxy = new proxy(oldWang);
        //出租
        proxy.rent();
    }
    
    /**
     * 输出:
     * Sat Jan 18 15:44:04 CST 2020
     * 代理代理了Host
     * 房东出租了房子!
     * */
}


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

class proxy implements Rent {
    // 代理类也可以继承Host类,但是Java不支持多继承,因此通过这种方式进行扩展。
    Host host;

    public proxy(Host host) {
        this.host = host;
    }

    @Override
    //代理代替房主出租房屋
    public void rent() {
        log();
        this.host.rent();
    }
    //代理可以在租房的基础上提供附加服务,看房等等
    public void log() {
        System.out.println(new Date().toString());
        System.out.println("代理代理了" + host.getClass().getName());
    }

}

interface Rent {
    void rent();
}

上边就是静态代理的简单原理了,但是静态代理也有缺点.房东需要一个代理来进行代理,楼下卖煎饼的大妈也需要一个代理......当需要代理的类太多时,编写维护代理就十分的麻烦了.这时候就需要动态代理出马了.

动态代理

实现动态代理的方式有很多,大致有三类:基于接口 基于类 基于字节码.

基于接口

这是JDK的原生动态代理方式.

基于接口的动态代理方式要求被代理的类实现某个接口,然后再通过Java的反射机制获得这个对象的所有属性,并创建构造器,相当于静态代理的 new. 这一部分工作主要由 java.lang.reflect.Proxy 实现.

public static Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

由图可知,上述静态方法的三个字段分别代表

  • 当前类的类加载器
  • 一系列接口
  • 一个InvocationHandler

通过这个方法就可以获得一个代理类(相当于静态代理示例的中介),但是这个代理和原来的(房东)并无区别,并没有扩展我们需要的方法(打印时间). 在这个类中,有一个 InvocationHandler 属性,每一次执行 rent 方法时,生成的这个代理类都会执行 invoke 方法,这样我们才能将扩展功能整合进去.

那么InvocationHandler又是什么呢?

从JavaDoc可知, InvocationHandler是一个接口且只有一个方法:invoke.这个方法的说明如下:


Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.


每个代理实例(中介)都有一个相关联的 Invocation Handler,当一个方法被实例(中介)调用时, 这个方法的调用会被 (整合?){此处不知如何翻译更好} 到这个方法对应的invocation handler的invoke方法中.

说简单一点就是当代理类(中介)执行 rent 方法时,会先执行中介关联的 invocation handler 的invoke方法,然后再执行 rent 方法.

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

/**
 * @author wangz
 */
public class DynamicProxy {
    public static void main(String[] args) {
        Host host = new Host();
        InvocationHandlerProxy handlerProxy = new InvocationHandlerProxy();
        handlerProxy.setTarget(host);
        Object proxy = handlerProxy.getProxy();
        proxy.rent();
    }
    /**
     * 结果
     * rent于Sat Jan 18 17:24:31 CST 2020
     * 房东出租了房子!
     * */
}


class InvocationHandlerProxy implements InvocationHandler {
    private Object target;

    //被代理的接口
    public void setTarget(Object target) {
        this.target = target;
    }

    //通过反射的的方式生成代理类
    public Object getProxy() {
        return (Object) Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    //处理代理实例并返回结果
    public java.lang.Object invoke(java.lang.Object proxy, Method method, java.lang.Object[] args) throws Throwable {
        System.out.println(method.getName() + "于" + log());
        return method.invoke(target, args);
    }

    private String log() {
        return new Date().toString();
    }
}

这样一来,所有实现了某些接口的类都可以通过这一个模板来动态的进行代理.

基于类

如果某些类并未实现任何接口,那么通过接口进行动态代理就不能实现了.

未完待续.

Aop

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率.

放一张谁也看不懂的图~

再放一张谁都能看懂的图~

核心仍然是是代理模式(动态代理).

Spring实现Aop的几种方式

Spring原生实现 (切入点)

项目一览

Service interface

package com.wang.service;

public interface Service {
    void add();
    void delete();
    void update();
    void select();
}

serviceImpl实现类

package com.wang.service;

/**
 * @author wangz
 */
public class serviceImpl implements Service {
    @Override
    public void add() {
        System.out.println("add");
    }

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

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

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

需要切入的方法

package com.wang.cut;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

/**
 * @author wangz
 */

public class Before implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("执行前");
    }
}

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
        https://www.springframework.org/schema/beans/spring-beans.xsd 
        http://www.springframework.org/schema/aop 
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean name="serviceImpl" class="com.wang.service.serviceImpl"/>
    <bean name="cut" class="com.wang.cut.Before"/>

    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.wang.service.serviceImpl.*(..))"/>
        <aop:advisor advice-ref="cut" id="a" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

入口

package com.wang;

import com.wang.service.Service;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
        Service serviceImpl = (com.wang.service.Service) classPathXmlApplicationContext.getBean("serviceImpl");
        serviceImpl.add();

    }
}

在此处要格外注意JDK原生支持的动态代理为通过接口代理,因此在获取对象时要强制转换为(com.wang.service.Service)

使用自定义类实现 (切面)

与原生方法不同,我们不需要实现任何借口,直接编写一个类. 方法名称没有限制.然后在xml中配置即可.

package com.wang.cut;

class myCut {
    public void before() {
        System.out.println("-----------------------");
    }

    public void after() {
        System.out.println("++++++++++++++++++++++++++");
    }
}
 <aop:config>
        <aop:aspect ref="cut">
            <aop:pointcut id="aaa" expression="execution(* com.wang.service.serviceImpl.* (..))"/>
            <aop:after method="after" pointcut-ref="aaa"/>
            <aop:before method="before" pointcut-ref="aaa"/>
        </aop:aspect>
    </aop:config>

使用注解实现

首先在xml配置: 为统一使用注解开发,使用bean自动扫描

    <context:component-scan base-package="com.wang"/>
    <context:annotation-config/>
    <aop:aspectj-autoproxy/>

首先要在需要切入的类上加上注解 @Aspect,然后再需要切入的方法上加入注解 @Before 等即可.

@Component
@Aspect
class myCut {
    @Before("execution(* com.wang.service.serviceImpl.*(..))")
    public void before() {
        System.out.println("-----------------------");
    }
    @After("execution(* com.wang.service.serviceImpl.*(..))")
    public void after() {
        System.out.println("++++++++++++++++++++++++++");
    }
}

完结.