Javaweb-spring的AOP极其简单的入门

一.什么是AOP

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

二.基本术语

  •  连接点(Jointpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点,在AOP中表示为“在哪里干”;
  • 切入点(Pointcut):选择一组相关连接点的模式,即可以认为连接点的集合,Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,在AOP中表示为“在哪里干的集合”;
  • 通知(Advice):在连接点上执行的行为,通知提供了在AOP中需要在切入点所选择的连接点处进行扩展现有行为的手段;包括前置通知(before advice)、后置通知(after advice)、环绕通知(around advice),在Spring中通过代理模式实现AOP,并通过拦截器模式以环绕连接点的拦截器链织入通知;在AOP中表示为“干什么”;
    • 前置通知(Before Advice):在切入点选择的连接点处的方法之前执行的通知,该通知不影响正常程序执行流程(除非该通知抛出异常,该异常将中断当前方法链的执行而返回);
    • 环绕通知(Around Advices):环绕着在切入点选择的连接点处的方法所执行的通知,环绕通知可以在方法调用之前和之后自定义任何行为,并且可以决定是否执行连接点处的方法、替换返回值、抛出异常等等。
    • 后置通知(After Advice):在切入点选择的连接点处的方法之后执行的通知,包括如下类型的后置通知:
      • 后置返回通知(After returning Advice):在切入点选择的连接点处的方法正常执行完毕时执行的通知,必须是连接点处的方法没抛出任何异常正常返回时才调用后置通知。
      • 后置异常通知(After throwing Advice): 在切入点选择的连接点处的方法抛出异常返回时执行的通知,必须是连接点处的方法抛出任何异常返回时才调用异常通知。
      • 后置最终通知(After finally Advice): 在切入点选择的连接点处的方法返回时执行的通知,不管抛没抛出异常都执行,类似于Java中的finally块。
  • 方面/切面(Aspect):横切关注点的模块化,比如上边提到的日志组件。可以认为是通知、引入和切入点的组合;在Spring中可以使用Schema和@AspectJ方式进行组织实现;在AOP中表示为“在哪干和干什么集合”;
  • 引入(inter-type declaration):也称为内部类型声明,为已有的类添加额外新的字段或方法,Spring允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象), 在AOP中表示为“干什么(引入什么)”;
  • 目标对象(Target Object):需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为“被通知对象”;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象,在AOP中表示为“对谁干”;
  • AOP代理(AOP Proxy):AOP框架使用代理模式创建的对象,从而实现在连接点处插入通知(即应用切面),就是通过代理来对目标对象应用切面。在Spring中,AOP代理可以用JDK动态代理或CGLIB代理实现,而通过拦截器模型应用切面。
  • 织入(Weaving):织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期、类装载期、运行期进行。

三.AOP的实现策略

(1)Java SE动态代理:
    使用动态代理可以为一个或多个接口在运行期动态生成实现对象,生成的对象中实现接口的方法时可以添加增强代码,从而实现AOP。缺点是只能针对接口进行代理,另外由于动态代理是通过反射实现的,有时可能要考虑反射调用的开销。
(2)字节码生成(CGLib 动态代理)
    动态字节码生成技术是指在运行时动态生成指定类的一个子类对象,并覆盖其中特定方法,覆盖方法时可以添加增强代码,从而实现AOP。其常用工具是cglib。
(3)定制的类加载器
    当需要对类的所有对象都添加增强,动态代理和字节码生成本质上都需要动态构造代理对象,即最终被增强的对象是由AOP框架生成,不是开发者new出来的。解决的办法就是实现自定义的类加载器,在一个类被加载时对其进行增强。JBoss就是采用这种方式实现AOP功能。
(4)代码生成
    利用工具在已有代码基础上生成新的代码,其中可以添加任何横切代码来实现AOP。
(5)语言扩展
    可以对构造方法和属性的赋值操作进行增强,AspectJ是采用这种方式实现AOP的一个常见Java语言扩展。

https://blog.csdn.net/qq_27717967/article/details/73561179

spring AOP核心技术就是使用了Java 的动态代理技术, 这里简单的总结下JDK和CGLIB两种动态代理机制:

3.1JDK的动态代理

InvocationHandler:

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数

Proxy:

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法,这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:

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

loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

这里写一个例子

interface Person {
    public void cook();
    public void shouwer();
}
class Student implements Person {
    @Override
    public void cook() {
        System.out.println("cook!");
    }
    @Override
    public void shouwer() {
        System.out.println("shower!");
    }
}

class StudentProxy implements InvocationHandler {
    private Person person;

    StudentProxy(Person person) {
        this.person = person;
    }

    Person getPersonProxy() {
        return (Person) Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("say".equals(method.getName())) {
            System.out.println("now I will say sth to u!");
            return method.invoke(person, args);
        } else {
            return method.invoke(person, args);
        }
    }
}

执行这段代码

public class ProxyTest {
    public static void main(String[] args) {
        StudentProxy proxy = new StudentProxy(new Student());
        Person person = (Person) proxy.getPersonProxy();
        System.out.println(person.getClass().getName());
    }
}

输出:

可能我以为返回的这个代理对象会是Person类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为Person类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Person类型,所以就可以将其转化为Person类型了。

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号

3.2CGLIB的动态代理

CGLIB(CODE GENERLIZE LIBRARY)代理是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明称final的。

如果目标对象没有实现接口,则默认会采用CGLIB代理;如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库,并在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

原版类:

class Person {
    public void say() {
        System.out.println("Im saying!");
    }

    public void shower() {
        System.out.println("Im showering!");
    }
}

 代理类:

class PersonProxy implements MethodInterceptor {
    Person person;

    PersonProxy(Person person) {
        this.person = person;
    }

    public Person createProxy() {
        //Enhancer类是cglib中的一个字节码增强器,它可以方便的为你所要处理的类进行扩展
        Enhancer enhancer = new Enhancer();
        //将目标对象所在的类作为Enhaner类的父类
        enhancer.setSuperclass(person.getClass());
        //通过实现MethodInterceptor实现方法回调
        enhancer.setCallback(this);
        // 创建代理:
        Person personProxy = (Person) enhancer.create();
        return personProxy;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        if ("shower".equals(method.getName())) {
            System.out.println("please get away!");
            return methodProxy.invoke(person, objects);
        } else {
            return methodProxy.invoke(person, objects);
        }
    }
}

该代理类可以简化成一个工厂的方法:

class PersonFactory {
    Person person;

    PersonFactory(Person person) {
        this.person = person;
    }

    Person getPerson() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Person.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                if ("shower".equals(method.getName())) {
                    System.out.println("please get away!");
                    return methodProxy.invoke(person, objects);
                } else {
                    return methodProxy.invoke(person, objects);
                }
            }
        });
        return (Person) enhancer.create();
    }
}

四.spring中AOP的使用

4.1代理工厂式

使用的时候除了要导入基本的spring包,还要导入

Advicer类:在调用方法前输出start,在调用方法后输出end。

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

    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("end");

    }
}

JavaBean:

class Person {
    public void say() {
        System.out.println("Im saying!");
    }
}

测试代码:

public class AOPTest {
    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(new Person());
        factory.addAdvice(new MyAdvicer());
        Person person = (Person) factory.getProxy();
        person.say();
    }
}

输出:

4.2经典xml方式

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-3.0.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="AOPDemo.AspectJAnnotationDemo"/>
    <bean id="person" class="AOPDemo.AspectJXmlDemo.Person"/>
    <bean id="before" class="AOPDemo.AspectJXmlDemo.MyPersonBefore"/>
    <bean id="after" class="AOPDemo.AspectJXmlDemo.MyPersonAfter"/>
    <aop:config>
        <aop:pointcut id="pointCut" expression="execution(* AOPDemo.AspectJXmlDemo.Person.*(..))"/>
        <aop:advisor id="be" advice-ref="before" pointcut-ref="pointCut" />
        <aop:advisor id="af" advice-ref="after" pointcut-ref="pointCut" />
    </aop:config>
</beans>

JavaBean:

class Person {
    public void eat() {
        System.out.println("eat!");
    }
    public void drink() {
        System.out.println("drink!");
    }
}

advisor:

class MyPersonBefore implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("before!");
    }
}
class MyPersonAfter implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("after!");
    }
}

输出结果:

4.3AspectJ注解式

4.3.1选择spring的AOP还是AspectJ?

spring确实有自己的AOP。功能已经基本够用了,除非你的要在接口上动态代理或者方法拦截精确到getter和setter。这些都是写奇葩的需求,一般不使用。
在使用AOP的时候,你是用xml还是注解的方式(@Aspect)?
1)如果使用xml方式,不需要任何额外的jar包。
2)如果使用@Aspect方式,你就可以在类上直接一个@Aspect就搞定,不用费事在xml里配了。但是这需要额外的jar包( aspectjweaver.jar)。因为spring直接使用AspectJ的注解功能,注意只是使用了它 的注解功能而已。并不是核心功能 !!!

注意到文档上还有一句很有意思的话:文档说到 是选择spring AOP还是使用full aspectJ?
什么是full aspectJ?如果你使用"full aspectJ"。就是说你可以实现基于接口的动态代理,等等强大的功能。而不仅仅是aspectj的 注-解-功-能 !!!
如果用full AspectJ。比如说Load-Time Weaving的方式 还 需要额外的jar包 spring-instrument.jar
当然,无论是使用spring aop还是 aspectj都需要aspectjweaver.jar spring-aop.jar这两个jar包。

4.3.2什么是AspectJ

AOP虽然是方法论,但就好像OOP中的Java一样,一些先行者也开发了一套语言来支持AOP。目前用得比较火的就是AspectJ了,它是一种几乎和Java完全一样的语言,而且完全兼容Java(AspectJ应该就是一种扩展Java,但它不是像Groovy[1]那样的拓展。)。当然,除了使用AspectJ特殊的语言外,AspectJ还支持原生的Java,只要加上对应的AspectJ注解就好。所以,使用AspectJ有两种方法:

  • 完全使用AspectJ的语言。这语言一点也不难,和Java几乎一样,也能在AspectJ中调用Java的任何类库。AspectJ只是多了一些关键词罢了。
  • 或者使用纯Java语言开发,然后使用AspectJ注解,简称@AspectJ

4.3.3AspectJ怎么用

Join Points介绍

Join Points是AspectJ中的一个关键概念。Join Points可以看作是程序运行时的一个执行点,比如:一个函数的调用可以看作是个Join Points,如Log.e()这个函数,e()可以看作是个Join Points,而调运e()的函数也可以认为是一个Join Points;设置一个变量,或者读取一个变量也可以是个Join Points;for循环也可以看作是Join Points。

这里列出了AspectJ所认可的JoinPoints的类型。实际上,也就是你想把新的代码插在程序的哪个地方,是插在构造方法中,还是插在某个方法调用前,或者是插在某个方法中,这个地方就是Join Points,当然,不是所有地方都能给你插的,只有能插的地方,才叫Join Points。

Pointcuts介绍

一个程序会有多个Join Points,即使同一个函数,也还分为call和execution类型的Join Points,但并不是所有的Join Points都是我们关心的,Pointcuts就是提供一种使得开发者能够选择自己需要的JoinPoints的方法。

Advice

Advice就是我们插入的代码以何种方式插入,有Before还有After、Around。

@Before("execution(* android.app.Activity.on**(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
}

这里会分成几个部分,我们依次来看:

  • @Before:Advice,也就是具体的插入点
  • execution:处理Join Point的类型,例如call、execution
  • (* android.app.Activity.on**(..)):这个是最重要的表达式,第一个*表示返回值,*表示返回值为任意类型,后面这个就是典型的包名路径,其中可以包含 * 来进行通配,几个 * 没区别。同时,这里可以通过&&、||、!来进行条件组合。()代表这个方法的参数,你可以指定类型,例如android.os.Bundle,或者(..)这样来代表任意类型、任意个数的参数。
  • public void onActivityMethodBefore:实际切入的代码。

Before和After其实还是很好理解的,也就是在Pointcuts之前和之后,插入代码,那么Around呢,从字面含义上来讲,也就是在方法前后各插入代码,是的,他包含了Before和After的全部功能,代码如下:

@Around("execution(* com.xys.aspectjxdemo.MainActivity.testAOP())")
public void onActivityMethodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    String key = proceedingJoinPoint.getSignature().toString();
    Log.d(TAG, "onActivityMethodAroundFirst: " + key);
    proceedingJoinPoint.proceed();
    Log.d(TAG, "onActivityMethodAroundSecond: " + key);
}

其中,proceedingJoinPoint.proceed()代表执行原始的方法,在这之前、之后,都可以进行各种逻辑处理。

入门用法:

JavaBean:

class Person {
    public void say() {
        System.out.println("hello!");
    }

    public void shower() {
        System.out.println("shower!");
    }
}

切面:

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-3.0.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="AOPDemo.AspectJAnnotationDemo"/>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>//开启AspectJ注解
    <bean id="person" class="AOPDemo.AspectJAnnotationDemo.Person"/>
    <bean id="myperon" class="AOPDemo.AspectJAnnotationDemo.MyPerson"/>
</beans>

结果:

公共节点用法(并探究JointPoint):

@Aspect
class MyPerson {
    @Pointcut("execution(public * AOPDemo.AspectJAnnotationDemo.Person.*(..))")
    public void play() {
    }

    @Before("play()")
    public void before(JoinPoint joinPoint) {
        System.out.println(joinPoint);
        System.out.println("Now I wnat to say something!");
    }

    @After("play()")
    public void after() {
        System.out.println("Now Im done!");
    }
}

输出:

4.4AspectJ的xml配置式

javabean:

class Person {
    public void cry() {
        System.out.println("cry!");
    }
}

通知:

class MyPerson {
    public void before() {
        System.out.println("before!");
    }

    public void after() {
        System.out.println("after!");
    }
}

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-3.0.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="AOPDemo.AspectJXmlDemo"/>
    <bean id="person" class="AOPDemo.AspectJXmlDemo.Person"/>
    <bean id="myperson" class="AOPDemo.AspectJXmlDemo.MyPerson"/>
    <aop:config>
        <aop:pointcut id="pointCut" expression="execution(* AOPDemo.AspectJXmlDemo.Person.*(..))"/>
        <aop:aspect ref="myperson">
            <aop:before method="before" pointcut-ref="pointCut"/>
            <aop:after method="after" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>
</beans>

测试代码:

public class AspectJXmlTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("AOPDemo/AspectJXmlDemo/spring.xml");
        Person person = (Person) ac.getBean("person");
        person.cry();
    }
}

输出:

猜你喜欢

转载自blog.csdn.net/weixin_38967434/article/details/82987993
今日推荐