Skip to content

03.AOP

1. 底层原理

  • 面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。通俗来说就是在不修改代码的情况下添加新的功能。
  • 底层通过动态代理来实现:
    • 第一种:有接口的情况,使用 JDK 动态代理:创建接口实现类的代理对象
    • 第二种:无接口的情况,使用 CGLIB 动态代理:创建当前类子类的代理对象

JDK 动态代理举例:

  • 通过 java.lang.reflect.Proxy 类newProxyInstance 方法 创建代理类。
  • newProxyInstance 方法:
  • 参数一:类加载器 参数二:所增强方法所在的类,这个类实现的接口,支持多个接口 参数三:实现 InvocationHandle 接口,重写 invoke 方法来添加新的功能

代码举例:

java
public interface UserDao {
    public int add(int a, int b);
    public int multi(int a, int b);
}
java
public class UserDaoImpl implements UserDao {
    @Override
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public int multi(int a, int b) {
        return a*b;
    }
}
java
public class Main {

    @Test
    public void test1(){

        //所需代理的类实现的接口,支持多个接口
        Class[] interfaces = {UserDao.class};

        UserDao userDao = new UserDaoImpl();

  //调用newProxyInstance方法来创建代理类
        UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(Main.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));

        int result = userDaoProxy.add(1, 2);
        System.out.println(result);
    }

    //创建内部类,实现InvocationHandler接口,重写invoke方法,添加新功能
    class UserDaoProxy implements InvocationHandler {

        Object obj;
  //通过有参构造函数将所需代理的类传过来
        public UserDaoProxy(Object obj){
            this.obj = obj;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            System.out.println("进入" + method.getName() + "方法,这是新增的代码,参数有" + Arrays.toString(args));

   //执行原有的代码
            Object invoke = method.invoke(obj, args);

            System.out.println("方法原先的内容执行完了");

            return invoke;
        }
    }
}

2.基于 AspectJ 实现 AOP 操作

2.1 AOP 相关术语

  • 连接点:类中可以被增强的方法,称为连接点。
  • 切入点:实际被增强的方法,称为切入点。
  • 通知:增强的那一部分逻辑代码。通知有多种类型:
    • 前置通知:增强部分代码在原代码前面。
    • 后置通知:增强部分代码在原代码后面。
    • 环绕通知:增强部分代码既有在原代码前面,也有在原代码后面。
    • 异常通知:原代码发生异常后才会执行。
    • 最终通知:类似与 finally 那一部分
  • 切面:指把通知应用到切入点这一个动作。

2.2 基于 AspectJ 实现 AOP 有两种方式

  • 基于 xml 配置文件
  • 基于注解方法

2.3 切入点表达式

  • 语法:execution([权限修饰符] [返回类型] [类全路径] [方法名称] [参数列表])
  • 举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
java
execution(* com.auguigu.dao.BookDao.add(..))
  • 举例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
java
execution(* com.atguigu.dao.BookDao.*(..))
  • 举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
java
execution(* com.atguigu.dao.*.* (..))

3.基于注解方式

java
@Component
public class User {
    public void add(){
        System.out.println("User.add()");
    }
}
java
@Component
@Aspect   //使用Aspect注解
public class UserProxy {
    //前置通知
    @Before(value="execution(* com.oymn.spring5.User.add(..))")
    public void before(){
        System.out.println("UserProxy.before()");
    }

    //后置通知
    @AfterReturning(value="execution(* com.oymn.spring5.User.add(..))")
    public void afterReturning(){
        System.out.println("UserProxy.afterReturning()");
    }

    //最终通知
    @After(value="execution(* com.oymn.spring5.User.add(..))")
    public void After(){
        System.out.println("UserProxy.After()");
    }

    //异常通知
    @AfterThrowing(value="execution(* com.oymn.spring5.User.add(..))")
    public void AfterThrowing(){
        System.out.println("UserProxy.AfterThrowing()");
    }

    //环绕通知
    @Around(value="execution(* com.oymn.spring5.User.add(..))")
    public void Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{

        System.out.println("UserProxy.Around()   _1");

        //调用proceed方法执行原先部分的代码
        proceedingJoinPoint.proceed();

        System.out.println("UserProxy.Around()   _2");
    }
}

配置 xml 文件:

xml
<!--开启组件扫描-->
<context:component-scan base-package="com.oymn"></context:component-scan>
<!--开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

测试类:

java
@Test
public void test2(){
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    User user = context.getBean("user", User.class);
    user.add();
}

运行结果:

运行结果中没有出现异常通知,在 add 方法中添加int i = 1/0;

java
public void add(){
    int i = 1/0;
    System.out.println("User.add()");
}

运行结果:从这里也可以看到,但出现异常时,After 最终通知有执行,而 AfterReturning 后置通知并没有执行。

对于上面的例子,有很多通知的切入点都是相同的方法,因此,可以将该切入点进行抽取:通过@Pointcut 注解

java
@Pointcut(value="execution(* com.oymn.spring5.User.add(..))")
public void pointDemo(){

}

//前置通知
@Before(value="pointDemo()")
public void before(){
    System.out.println("UserProxy.before()");
}

设置增强类优先级:

当有多个增强类对同一方法进行增强时,可以通过**@Order(数字值)来设置增强类的优先级,数字越小优先级越高。**

java
@Component
@Aspect
@Order(1)
public class PersonProxy

完全注解开发

可以通过配置类来彻底摆脱 xml 配置文件:

java
@Configuration
@ComponentScan(basePackages = "com.oymn.spring5")
//@EnableAspectJAutoProxy注解相当于上面xml文件中配置的 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Config {
}

4.基于 xml 方式

这种方式开发中不怎么用,了解即可。

创建 Book 和 BookProxy 类

java
public class Book {
    public void buy(){
        System.out.println("buy()");
    }
}
java
public class BookProxy {
    public void before(){
        System.out.println("before()");
    }
}

配置 xml 文件:

xml
<!--创建对象-->
<bean id="book" class="com.oymn.spring5.Book"></bean>
<bean id="bookProxy" class="com.oymn.spring5.BookProxy"></bean>

<aop:config>
    <!--切入点-->
    <aop:pointcut id="p" expression="execution(* com.oymn.spring5.Book.buy(..))"/>
    <!--配置切面-->
    <aop:aspect ref="bookProxy">
        <aop:before method="before" pointcut-ref="p"/>  <!--将bookProxy中的before方法配置为切入点的前置通知-->
    </aop:aspect>
</aop:config>

Released under the MIT License.