简介

什么是 AOP

(1)面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得

业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

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

(3)使用登录例子说明 AOP

一般我们登录时都是这样的流程。

java jts 面质心_AOP


现在我们想在登录功能基础之上添加权限判断功能

原始方式:修改源代码实现

AOP操作:不修改源代码就可以添加新的功能

java jts 面质心_java jts 面质心_02


各模块相互分离,不通过修改源代码,可以在主干功能中添加新功能。

AOP底层原理

1、AOP 底层使用动态代理

(1)有两种情况动态代理

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

⚫ 创建接口实现类代理对象,增强类的方法

java jts 面质心_spring_03

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

⚫ 创建子类的代理对象,增强类的方法

java jts 面质心_AOP_04

JDK 动态代理

使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象

java jts 面质心_java_05

返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序

(1)调用 newProxyInstance 方法

java jts 面质心_spring_06

newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)

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

JDK动态代理代码:

1) 创建接口,定义方法
UserDao

package com.amhu.yx.spring.dynamic;

public interface UserDao {
    public int add(int a, int b);
	public String update(String id);
}

2)创建接口实现类,实现方法
UserDaoImpl

package com.amhu.yx.spring.dynamic;

import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao{

    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public String update(String id) {
        return id;
    }
}

JDKProxy

package com.amhu.yx.spring.dynamic;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class JDKProxy {

    public static void main(String[] args) {
        //创建接口实现类代理对象
        Class[] interfaces = {UserDao.class};
/*
        Proxy.newProxyInstance(
                JDKProxy.class.getClassLoader(),
                interfaces,
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return null;
                    }
                });
*/
        UserDaoImpl userDao = new UserDaoImpl();
        UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
        int add = dao.add(1, 2);
        System.out.println("result" + add);
    }
}

//实现InvocationHandler接口,在里面添加增强功能的逻辑
class UserDaoProxy implements InvocationHandler{

    //1.创建谁的代理对象,就传入谁
    private 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 res = method.invoke(obj, args);

        //方法之后的逻辑
        System.out.println("这是在方法之后执行..." + obj );


        return res;
    }
}

ProxyFactory

package proxy.dynamic;

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

public class ProxyFactory {
    //维护一个目标对象,Object
    private Object target;

    //构造器,对target进行初始化
    public ProxyFactory(Object target) {
        this.target = target;
    }

    //给目标对象生成一个代理对象
    public Object getProxyInstance() {
        /**
         * public static Object newProxyInstance(ClassLoader loader,
         *                                           Class<?>[] interfaces,
         *                                           InvocationHandler h)
         * 1.ClassLoader loader   :指定当前目标对象使用的类加载器,获取加载器的方法
         * 2.Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方法确认类型
         * 3.InvocationHandler h  :事情处理,执行目标对象的方法时,会触发事情处理器方法,会把当前执行的目标对象方法作为参数传递
         */

        //return Proxy.newProxyInstance(loader, interfaces, h)
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("JDK 代理开始");
                        //反射机制调用目标对象的方法
                        Object returnVal = method.invoke(target, args);
                        return returnVal;
                    }
                });
    }
}

结果:

java jts 面质心_spring_07

AOP术语

1、连接点
类中可以被增强的方法称为连接点

2、切入点
实际被真正增强的方法,称为切入点

3、通知(增强)

    1) 实际增强的逻辑部分称为通知(增强)

    2) 通知有多种类型

         前置通知 @Before
         后置通知 @AfterReturning
         环绕通知 @Around
         异常通知 @AfterThrowing
         最终通知 @After

4、切面
切面是一个工作,是把通知应用到切入点的过程

AOP操作

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

(1)AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJSpirng 框架一起使

用,进行 AOP 操作

2、基于 AspectJ 实现 AOP 操作

(1)基于 xml 配置文件实现

(2)基于注解方式实现(使用)

3、在项目工程里面引入 AOP 相关依赖

java jts 面质心_spring_08


4、切入点表达式

切入点:实际被真正增强的方法,称为切入点

(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强
(2)语法结构:

execution( [权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )

修饰符中的*表示public/protected/private 都可以

举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强

execution(* com.atguigu.dao.BookDao.add(..))

举例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强

execution(* com.atguigu.dao.BookDao.* (..))

举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强

execution(* com.atguigu.dao.*.* (..))

AspectJ

AspectJ注解

看一个普通的User类

package com.amhu.yx.spring.aspectj.anno;

import org.springframework.stereotype.Component;

@Component
public class User {
    public void add() {
        System.out.println("User add()");
    }
}

现在想要增强User类中的add() 方法。
2、创建增强类(编写增强逻辑)
在增强类里面,创建方法,让不同方法代表不同通知类型
如下面的before() 是前置通知,在before() 方法前添加@Before注解,然后添加切入点表达式:

  • @Before(value="execution(* com.amhu.yx.spring.aspectj.anno.User.add(..))")
package com.amhu.yx.spring.aspectj.anno;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 增强的类
 */
@Component
@Aspect //有@Aspect注解的,生成代理对象
public class UserProxy {

    //前置通知
    //@Before表示作为前置通知
    @Before(value="execution(* com.amhu.yx.spring.aspectj.anno.User.add(..))")
    public void before() {
        System.out.println("before...");
    }
}

创建bean.xml 配置文件
注意添加aop,context命名空间

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.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="com.amhu.yx.spring.aspectj"></context:component-scan>

    <!--开启Aspect生成代理对象-->
    <!--寻找带有@Aspect注解的类,生成其代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

测试代码:

ackage com.amhu.yx.spring.test;

import com.amhu.yx.spring.aspectj.anno.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AspectJTest {
    @Test
    public void testAop(){
        ApplicationContext context =
                new ClassPathXmlApplicationContext("bean.xml");

        //注意,执行的是被增强的方法
        User user = context.getBean("user", User.class);
        user.add();
    }
}

结果:

java jts 面质心_java_09

其它通知类型:

package com.amhu.yx.spring.aspectj.anno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 增强的类
 */
@Component
@Aspect //有@Aspect注解的,生成代理对象
public class UserProxy {

    //前置通知
    //@Before表示作为前置通知
    @Before(value="execution(* com.amhu.yx.spring.aspectj.anno.User.add(..))")
    public void before() {
        System.out.println("before...");
    }

    //后置通知
    @After(value="execution(* com.amhu.yx.spring.aspectj.anno.User.add(..))")
    public  void after() {
        System.out.println("after...");
    }

    //异常通知
    @AfterThrowing(value="execution(* com.amhu.yx.spring.aspectj.anno.User.add(..))")
    public  void afterThrowing() {
        System.out.println("afterThrowing...");
    }

    //环绕通知
    @Around(value="execution(* com.amhu.yx.spring.aspectj.anno.User.add(..))")
    public  void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕之前...");

        //执行被增强的方法
        proceedingJoinPoint.proceed();

        System.out.println("环绕之后...");
    }

    //最终通知
    //在返回值之后执行
    @AfterReturning(value="execution(* com.amhu.yx.spring.aspectj.anno.User.add(..))")
    public  void afterReturning() {
        System.out.println("afterReturning...");
    }
}

结果:

java jts 面质心_User_10

通知注解

         前置通知 @Before

         后置通知 @AfterReturning

         环绕通知 @Around

         异常通知 @AfterThrowing

         最终通知 @After



公共切入点抽取

(实际被真正增强的方法,称为切入点)

上面可以看到

java jts 面质心_User_11


有相同的切入点时,每次都要重复写切入点表达式,太繁琐了吧!!

所以我们可以将相同的切入点进行抽取,

//相同切入点抽取
@Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void pointdemo() {
}

//前置通知
//@Before 注解表示作为前置通知
//直接加入有相同切入点的方法名称
@Before(value = "pointdemo()")
public void before() {
System.out.println("before.........");
}

6、有多个增强类多同一个方法进行增强,设置增强类优先级(谁先执行,谁后执行)

在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高

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

完全使用注解开发

创建配置类,不需要创建 xml 配置文件

@Configuration
@ComponentScan(basePackages = {"com.amhu.yx.spring.aspectj.anno"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}

AspectJ xml配置文件

在实际上我们一般都是实用注解方式,配置文件我们了解即可。
1、创建两个类,增强类和被增强类,创建方法
2、在 spring 配置文件中创建两个类对象

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

3、在 spring 配置文件中配置切入点

<!--配置 aop 增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(*
com.atguigu.spring5.aopxml.Book.buy(..))"/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>