目录

概述

使用登录例子说明 AOP

AOP(底层原理,了解)

有两种情况动态代理

第一种 有接口情况,使用 JDK动态代理

第二种 没有接口情况,使用 CGLIB 动态代理 

 回顾JDK静态代理

①明星唱歌,经纪人替他签合同以及收钱的例子

②我们找房屋中介租房子 

回顾JDK动态代理

1、使用 Proxy 类里面的方法创建代理对象

 调用 newProxyInstance 方法

2.编写 JDK 动态代理代码

测试

AOP(术语)

连接点

切入点

通知(增强)

通知类型

切面

AOP操作

Spring 框架一般都是基于 AspectJ 实现 AOP 操作

基于 AspectJ 实现 AOP 操作

引入相关依赖

 切入点表达式

AOP 操作(AspectJ 注解)

1、创建类,在类里面定义方法

2、创建增强类(编写增强逻辑)

3.进行通知的配置

(1)在 spring 配置文件中,开启注解扫描

(2)使用注解创建 UserDao 和 UserProxy 对象

(3)在增强类上面添加注解 @Aspect

(4)在 spring 配置文件中开启生成代理对象

4、配置不同类型的通知

5、相同的切入点抽取

测试

AOP 操作(AspectJ 配置文件)

创建被增强类和增强类

在xml配置文件中创建两个类的对象

在 xml 配置文件中配置aop

 写在最后


概述

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

通俗描述:不通过修改源代码方式,在主干功能里面添加新功能。

使用登录例子说明 AOP

 也许这个例子还是比较空洞,我们接着往下看,后面有代码了又实际需求了就会明白了。

AOP(底层原理,了解)

底层原理了解即可,因为Spring底层为我们封装好了,这里之所以介绍是因为我们得了解这是怎么回事。

有两种情况动态代理

第一种 有接口情况,使用 JDK动态代理

第二种 没有接口情况,使用 CGLIB 动态代理 

 回顾JDK静态代理

①明星唱歌,经纪人替他签合同以及收钱的例子

有一个接口Show(表演)里面有个抽象方法sing()

有一个Star类(明星)实现Show接口中的sing去唱歌,他只需要唱歌其他不用管。

明星去唱歌就行了,至于谈酬劳以及签合同都是经纪人的事情了,因此我们这里需要一个经纪人类AgentMan,商家有事情都找他就行了,因为经纪人就是明星的代理人,他可以全权负责。下面我们用代码来实现。

Show接口
public interface Show {
    void sing();
}
RealStar类
public class RealStar implements Show {
    @Override
    public void sing() {
        System.out.println("我是歌手只负责唱歌就可以了");
    }
}
经纪人类
public class AgentMan implements Show {
    private Show show;

    public AgentMan(Show show) {
        this.show = show;
    }

    @Override
    public void sing() {
        System.out.println("我是经纪人我负责签合同");
        show.sing();
        System.out.println("唱完了,我该去收钱了");

    }
}

 测试

 

②我们找房屋中介租房子 

//租房接口
public interface Rent {
    void rent();
}
//租房者类
public class You implements Rent {
    @Override
    public void rent() {
        System.out.println("你真实的需要租房子而且还需要你自己去花钱哦");
    }
}
//中介类
public class LianJia implements Rent {
    private Rent rent;

    public LianJia(Rent rent){
        this.rent = rent;
    }
    @Override
    public void rent() {
        System.out.println("我是中介,我在帮你找房子,马上就要找到了");
        rent.rent();
        System.out.println("我是中介,你房子也租到了,你现在该干嘛干嘛去吧");
    }
}

测试

 通过上面两个例子你是否对代理有了深入了解呢?相信你一定能感受到了代理类能做我们想做的事情而核心的事情需要我们被代理类自己去实现。

回顾JDK动态代理

从上面的两个例子不知道你发现问题了没有。当我们需要代理明星的时候就需要创建一个AgentMan来代理明星,当我们需要代理客户租房的时候,需要创建一个LianJian来代理客户。也就是说静态代理需要对应实际的被代理类(明星、客户)创建对应的代理类。如果我们在开发中有100个类需要被代理,难道我们还需要创建100个代理类吗?当然不需要,我们仅仅需要动态代理即可。

1、使用 Proxy 类里面的方法创建代理对象

这个是官方API的介绍

动态代理类 (以下简称为代理类 )是一个实现在类创建时在运行时指定的接口列表的类,具有如下所述的行为。 代理接口是由代理类实现的接口。 代理实例是代理类的一个实例。 每个代理实例都有一个关联的调用处理程序对象,它实现了接口InvocationHandler 。 通过其代理接口之一的代理实例上的方法调用将被分派到实例调用处理程序的invoke方法,传递代理实例, java.lang.reflect.Method被调用方法的java.lang.reflect.Method对象以及包含参数的类型Object Object的数组。 调用处理程序适当地处理编码方法调用,并且返回的结果将作为方法在代理实例上调用的结果返回。

代理类具有以下属性:

代理类是公共的,最终的,而不是抽象的,如果所有代理接口都是公共的。
如果任何代理接口是非公开的,代理类是非公开的,最终的,而不是抽象的 。
代理类的不合格名称未指定。 然而,以字符串"$Proxy"开头的类名空间应该保留给代理类。
一个代理类扩展了java.lang.reflect.Proxy 。
代理类完全按照相同的顺序实现其创建时指定的接口。
如果一个代理类实现一个非公共接口,那么它将被定义在与该接口相同的包中。 否则,代理类的包也是未指定的。 请注意,程序包密封不会阻止在运行时在特定程序包中成功定义代理类,并且类也不会由同一类加载器定义,并且与特定签名者具有相同的包。
由于代理类实现了在其创建时指定的所有接口, getInterfaces在其类对象上调用getInterfaces将返回一个包含相同列表接口的数组(按其创建时指定的顺序),在其类对象上调用getMethods将返回一个数组的方法对象,其中包括这些接口中的所有方法,并调用getMethod将在代理接口中找到可以预期的方法。
Proxy.isProxyClass方法将返回true,如果它通过代理类 - 由Proxy.getProxyClass返回的类或由Proxy.newProxyInstance返回的对象的类 - 否则为false。
所述java.security.ProtectionDomain代理类的是相同由引导类装载程序装载系统类,如java.lang.Object ,因为是由受信任的系统代码生成代理类的代码。 此保护域通常将被授予java.security.AllPermission 。
每个代理类有一个公共构造一个参数,该接口的实现InvocationHandler ,设置调用处理程序的代理实例。 而不必使用反射API来访问公共构造函数,也可以通过调用Proxy.newProxyInstance方法来创建代理实例,该方法将调用Proxy.getProxyClass的操作与调用处理程序一起调用构造函数。

 调用 newProxyInstance 方法

 

方法有三个参数:
第一参数,类加载器
第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
第三参数,实现这个接口 InvocationHandler ,创建代理对象,写增强的部分

2.编写 JDK 动态代理代码

public class ProxyAgent {

    public static Object getInstance(Object obj){
        return Proxy.newProxyInstance(
                obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                new MyInvocationHandler(obj));
    }

}
class MyInvocationHandler implements InvocationHandler{
    private Object obj;
    public MyInvocationHandler(Object obj){
        this.obj=obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是动态代理类,我在真实方法处理之前被执行");
        Object invoke = method.invoke(obj, args);
        System.out.println("我是动态代理类,我在真实方法处理之后被执行");
        return invoke;
    }
}

测试

    @Test
    public void test3(){
        Show realStar = new RealStar();
        Show show = (Show) ProxyAgent.getInstance(realStar);
        show.sing();

        System.out.println("===========================");

        Rent you = new You();
        Rent rent = (Rent) ProxyAgent.getInstance(you);
        rent.rent();
    }

执行结果

这样,是不是对动态代理有了很好的理解呢,用一个类就可以实现动态的代理。

好了现在我们进入本篇文章最重要的地方了。

以上都是在回顾再介绍,没必要掌握,因为我们也讲过了Spring对aop有了封装,我们只需要对Spring封装的aop会操作即可,当然了能掌握其原理和底层也是极好的。

AOP(术语)

连接点

即为在类中可以被增强的方法。

切入点

即为类中实际被增强的方法。

通知(增强)

被增强的逻辑部分。

通知类型

前置通知

后置通知

环绕通知

异常通知

最终通知

切面

所谓切面是一个过程,就是把通知加到切入点上的过程。

AOP操作

Spring 框架一般都是基于 AspectJ 实现 AOP 操作

AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使
用,进行 AOP 操作 。

基于 AspectJ 实现 AOP 操作

(1)基于 xml 配置文件实现
(2)基于注解方式实现 (先讲基于注解)

引入相关依赖

 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.16</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
         
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.16</version>
        </dependency>

 切入点表达式

(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强
(2)语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
举例 1:对 com.csdn.dao.BookDao 类里面的 add 进行增强
execution(* com.csdn.dao.BookDao.add(..))
举例 2:对 com.csdn.dao.BookDao 类里面的所有的方法进行增强
execution(* com.csdn.dao.BookDao.* (..))
举例 3:对 com.csdn.dao 包里面所有类,类里面所有方法进行增强
execution(* com.csdn.dao.*.* (..))

AOP 操作(AspectJ 注解)

1、创建类,在类里面定义方法

package com.csdn.dao;

public class UserDao {
    public int add(int a, int b) {
        System.out.println("我被调用了,参数为"+a+"和"+b);
        return a + b;
    }
}

2、创建增强类(编写增强逻辑)

public class UserProxy {
    public void before(){
        System.out.println("我是在方法调用之前执行");
    }
}

3.进行通知的配置

1)在 spring 配置文件中,开启注解扫描

 <context:component-scan base-package="com.csdn"></context:component-scan>

这个根据你自己的报名写就可以。

(2)使用注解创建 UserDao UserProxy 对象

 

(3)在增强类上面添加注解 @Aspect

 

 

(4)在 spring 配置文件中开启生成代理对象

<!-- 开启 Aspect 生成代理对象--> 
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

4、配置不同类型的通知

在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
@Component
@Aspect
public class UserProxy {
//    前置通知在方法调用之前调用
    @Before(value="execution(* com.csdn..dao.*.*(..))")
    public void before(){
        System.out.println("我是前置通知");
    }

//    返回通知或者后置通知在方法调用后执行
    @AfterReturning(value="execution(* com.csdn..dao.*.*(..))")
    public void afterReturn(){
        System.out.println("我是后置通知或成为返回通知");
    }
//最终通知,不管程序是否出现异常都会被执行
    @After(value="execution(* com.csdn..dao.*.*(..))")
    public void after(){
        System.out.println("我是最终通知,类似于try-catch-finally中的finally");
    }
//出现异常的时候调用
    @AfterThrowing(value="execution(* com.csdn..dao.*.*(..))")
    public void throwing(){
        System.out.println("我是异常通知");
    }
//环绕通知在方法执行前后都会执行
    @Around(value="execution(* com.csdn..dao.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("我是环绕通知,我在方法前执行");
       Object obj joinPoint.proceed();
        System.out.println("我是环绕通知,我在方法后执行");
        return obj;
    }

}

5、相同的切入点抽取

我们看到上面方法中的execution是一样的,我们可以考虑将其提取再使用,将增强类修改为如下

   @Pointcut(value="execution(* com.csdn.dao.*.*(..))")
    public void pointCut(){

    }


//    前置通知在方法调用之前调用
    @Before(value="pointCut()")
    public void before(){
        System.out.println("我是前置通知");
    }

//    返回通知或者后置通知在方法调用后执行
    @AfterReturning(value="pointCut()")
    public void afterReturn(){
        System.out.println("我是后置通知或成为返回通知");
    }
//最终通知,不管程序是否出现异常都会被执行
    @After(value="pointCut()")
    public void after(){
        System.out.println("我是最终通知,类似于try-catch-finally中的finally");
    }
//出现异常的时候调用
    @AfterThrowing(value="pointCut()")
    public void throwing(){
        System.out.println("我是异常通知");
    }
//环绕通知在方法执行前后都会执行
    @Around(value="pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("我是环绕通知,我在方法前执行");
        Object proceed = joinPoint.proceed();

        System.out.println("我是环绕通知,我在方法后执行");
        return proceed;
    }

}

测试

    @Test
    public void test4(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("app.xml");
        UserDao userDao = ac.getBean("userDao", UserDao.class);
        System.out.println(userDao.add(1,2));
    }

结果

如果你想获取更多信息可以使用环绕通知的参数ProceedingJoinPoint  或者在其他方法上加入JoinPoint

我对环绕通知和最终通知修改如下:

 

 

 再测试查看打印,会看到一些关于你调用的方法名以及方法的一些其他信息,可以选择打印,不用纠结

这是再次执行测试侯打印的结果

我是环绕通知,我在方法前执行
[1, 2]
class com.csdn.dao.UserDao
Integer com.csdn.dao.UserDao.add(int,int)
com.csdn.dao.UserDao
1
add
public java.lang.Integer com.csdn.dao.UserDao.add(int,int)
UserDao.add(..)
我是前置通知
我被调用了,参数为1和2
我是后置通知或成为返回通知
[1, 2]
add
我是最终通知,类似于try-catch-finally中的finally
我是环绕通知,我在方法后执行
3
Disconnected from the target VM, address: '127.0.0.1:57941', transport: 'socket'

Process finished with exit code 0

AOP 操作(AspectJ 配置文件)

创建被增强类和增强类

package com.csdn.service;

/**
 * 被增强类
 */
public class UserService {
    public void add(){
        System.out.println("userService's add().....");
    }
}
//增强类
public class UserServiceProxy {
    public void before(){
        System.out.println("我在之前被执行");
    }
}

在xml配置文件中创建两个类的对象

 <bean id="userService" class="com.csdn.service.UserService"></bean>
    <bean id="userServiceProxy" class="com.csdn.service.UserServiceProxy"></bean>

在 xml 配置文件中配置aop

   <aop:config>
<!--        写切入点-->
        <aop:pointcut id="point" expression="execution(* com.csdn.service.*.*(..))"/>
<!--        写被增强的那个类,就是类似用注结时候写的@Aspect配置切面-->
        <aop:aspect ref="userServiceProxy">
            <aop:before method="before" pointcut-ref="point"></aop:before>
        </aop:aspect>
    </aop:config>

测试

    @Test
    public void test5(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("app.xml");
        UserService userService = ac.getBean("userService", UserService.class);
        userService.add();
    }

 写在最后

如果我们的类被多个类增强怎么办呢?

如果是注解的方式可以使用Order注解写在增强类上,数值越小越先被执行

如下

如果是配置形式的话,也是通过order属性来标记

 

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐