【Spring】JDK、CGLIB动态代理与AOP

一、AOP

1.面向切面编程(AOP)

AOP全称Aspect-Orinted Programing(面向切面编程),是一种编程思想,是OOP的延伸和补充,采用横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行的时候,再将这些提取出来的代码应用到需要执行的地方。
AOP术语

名称作用
Aspect(切面)封装的用于横向插入系统功能(如事务、权限、日志)的类
JoinPoint(连接点)程序执行的某个阶段点,如方法的调用、异常的抛出等;(在Spring的AOP中,连接点就是方法的调用)
PointCut(切入点)需要处理的连接点
Advice(通知/增强)定义好的切入点需要执行的代码
Target Object(目标对象)所有被通知的对象,也称为被增强对象
Proxy(代理)通知应用到目标对象后被创建的对象
Weaving(织入)将切面插入到目标对象上,从而生成代理对象的过程

按照通知在目标方法中的连接点位置,通知分5种类型:
① 前置通知:在目标方法前实施增强,用于权限管理;
② 后置通知:在目标方法后实施增强,用于关闭流、上传文件、删除临时文件等;(目标方法出现异常后不会执行)
③ 环绕通知:在目标方法前后实施增强,用于日志、事务管理;
④ 异常通知:在目标方法后实施增强,用于处理异常、记录日志;(只有目标方法出现异常时才会执行)
⑤ 最终通知:在目标方法前实施增强,用于释放资源;(无论目标方法出现异常都会执行)

2.AOP底层原理

AOP的底层采用JDK动态代理,实际上动态代理具体有两种:

JDK动态代理

对于使用业务接口的类,Spring默认采用JDK动态代理实现AOP
① JDK动态代理是通过java.lang.reflect.Proxy类实现的,可以调用Proxy类的静态方法newProxyInstance()来创建代理对象。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces ,InvocationHandler h) //返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
② JDK动态代理实现的需要实现InvocationHandler接口,实现invoke方法
Object invoke(Object proxy, Method method, Object[] args) //处理代理实例上的方法调用并返回结果。

JDK动态代理如下:

//接口TestDao
public interface UserDao {
    public void addUser();
    public void deleteUser();
}
//接口实现类TestDaoImpl(此时的TestDaoImpl作为目标类将被增强)
public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        System.out.println("添加用户");
    }
    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }
}
//切面类MyAspect
public class MyAspect {
    public void check(){
        System.out.println("模拟检查权限...");
    }
    public void log(){
        System.out.println("模拟记录日志...");
    }
}
//代理类JDKProxy(需要实现InvocationHandler )
public class JDKProxy implements InvocationHandler {
    //声明目标类接口
    private UserDao userDao;
    //代理方法
    public Object createProxy(UserDao userDao){
        this.userDao = userDao;
        //1.类加载器
        ClassLoader classLoader = JDKProxy.class.getClassLoader();
        //2.被代理对象实现的所有接口
        Class<?>[] interfaces = userDao.getClass().getInterfaces();
        //3.使用代理类进行增强
        return Proxy.newProxyInstance(classLoader,interfaces,this);
    }
    /**
     * 所有动态代理类的方法调用,都会交由invoke()方法去处理
     * @param proxy 被代理的对象
     * @param method 将要执行的方法信息(通过反射获取)
     * @param args 执行方法需要的参数
     * @return 返回创建的代理类对象
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //声明切面
        MyAspect myAspect = new MyAspect();
        //前增强
        myAspect.check();
        //在目标类上调用方法,并传入参数
        Object obj = method.invoke(userDao, args);
        //后增强
        myAspect.log();
        //返回创建的代理类对象
        return obj;
    }
}
//测试类
public class JDKTest {
    public static void main(String[] args) {
        //创建代理对象
        JDKProxy proxy = new JDKProxy();
        //创建目标对象
        UserDao userDao = new UserDaoImpl();
        //从代理对象中获取增强后的目标对象
        UserDao userDao1 = (UserDao) proxy.createProxy(userDao);
        //执行方法
        userDao1.addUser();
        userDao1.deleteUser();
    }
}

在这里插入图片描述

CGLIB动态代理

JDK动态代理有局限性——使用动态代理的业务类必须实现接口,对于没有实现接口的类就需要使用CGLIB动态代理。

① CGLIB(Code Generation Library)是一个高性能的代码生成包,采用底层的字节码技术,对指定目标类生成一个子类,并对子类进行增强。
② 代理类需要实现MethodInterceptor接口,实现接口中的interceptor()方法

//目标类(不需要实现接口,只是定义需要增强的方法即可)
public class UserDao {
    public void addUser(){
        System.out.println("添加用户");
    }
    public void deleteUser(){
        System.out.println("删除用户");
    }
}

此处的切面类依旧是上面JDK动态代理中的MyAspect.java

//代理类
//注意:import org.springframework.cglib.proxy.MethodInterceptor
public class CglibProxy implements MethodInterceptor {
    //代理方法
    public Object createProxy(Object obj){
        //创建一个动态类对象
        Enhancer enhancer = new Enhancer();
        //确定需要增强的类,设置其父类
        enhancer.setSuperclass(obj.getClass());
        //添加回调函数
        enhancer.setCallback((Callback) this);
        //返回创建的代理类
        return enhancer.create();
    }
    /**
     * @param proxy CGLIB根据指定父类生成的代理对象
     * @param method 拦截的方法
     * @param args 拦截方法的参数数组
     * @param methodProxy 方法的代理对象,用于执行父类的方法
     * @return 返回创建的代理类对象
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //创建切面类对象
        MyAspect myAspect = new MyAspect();
        //前增强
        myAspect.check();
        //目标方法执行
        Object obj = methodProxy.invokeSuper(proxy, args);
        //后增强
        myAspect.log();
        return obj;
    }
}
//CGLIB测试类
public class CglibTest {
    public static void main(String[] args) {
        //创建代理对象
        CglibProxy cglibProxy = new CglibProxy();
        //创建目标对象
        UserDao userDao = new UserDao();
        //获取增强后的目标对象
        UserDao userDao1 = (UserDao) cglibProxy.createProxy(userDao);
        //执行方法
        userDao1.addUser();
        userDao1.deleteUser();
    }
}

在这里插入图片描述

二、AOP开发

AspectJ开发

AscpectJ是一个基于java语言的独立于Spring的AOP框架,使用AspectJ实现AOP有两种方式:基于XML的声明式AspectJ、基于注解的声明式AspectJ;
1.切入点表达式
(1)作用:指明对类中的那个方法进行增强处理;
(2)语法格式:execution([权限修饰符][返回值类型][类全路径][方法名称]([参数列表]))
(类全路径=包名+类名)
例1:对com.jd.dao.BookDao类里面的add进行增强:execution(* com.atguigu.dao.BookDao.add(..))
例2:对com.jd.dao.BookDao类里面的所有的方法进行增强:execution(* com.atquiqu.dao.BookDao.*(..))
例3:对com.jd.dao包里面所有类,类里面所有方法进行增强:execution(* com.atauiau.dao.*.*(..))

注解名称描述
@Aspect用于定义一个切面
@Pointcut用于定义切入点表达式,声明一个返回值为void,且方法体为空的普通方法作为切入点名称
@Before用于定义前置通知,相当于BeforeAdvice。value属性用于指定切入点表达式
@AfterReturning用于定义后置通知,相当于AfterReturningAdvice。pointcut/value属性用于指定切入点表达式
@Around用于定义环绕通知,相当于MethodInterceptor。value属性用于指定切入点表达式
@AfterThrowing用于定义异常通知来处理程序中的未处理的异常,相当于ThrowAdvice。pointcut/value属性用于指定切入点表达式
@After用于定义最终通知,无论是否异常,该通知都会执行。value属性用于指定切入点表达式
@DeclareParents用于定义引介通知

1.基于注解的声明式AspectJ

(1)需要导入的jar包
在这里插入图片描述
(2)具体实现:

//接口(业务逻辑的接口,aop默认是JDK动态代理(业务逻辑需要实现接口))
public interface UserDao {
    public void addUser();
    public void deleteUser();
}
//目标类,实现UserDao接口
@Repository("userDao")
public class UserDaoImpl implements UserDao{
    @Override
    public void addUser() {
        //int i = 10/0;//模拟异常
        System.out.println("添加用户");
    }
    @Override
    public void deleteUser() {
        System.out.println("删除用户");
    }
}
//切面类,在此类中编写通知
@Aspect
@Component
public class MyAspect {
    //定义切入点表达式
    @Pointcut("execution(* com.jd.aspectj.dao.*.*(..))")//此处的配置很关键
    public void myPointCut(){}
    //前置通知
    @Before("myPointCut()")
    public void myBefore(JoinPoint joinPoint){
        System.out.print("前置通知:模拟检查权限...");
        System.out.print("目标类是:"+joinPoint.getTarget());
        System.out.println(",被织入增强的目标方法是:"+joinPoint.getSignature().getName());
    }
    //后置通知
    @AfterReturning("myPointCut()")
    public void myAfterReturning(JoinPoint joinPoint){
        System.out.print("后置通知:模拟记录日志...");
        System.out.println(",被织入增强处理的方法是:"+joinPoint.getSignature().getName());
    }
    //环绕通知
    @Around("myPointCut()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //开始
        System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
        //执行当前目标方法
        Object obj = proceedingJoinPoint.proceed();
        //结束
        System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
        return obj;
    }
    //异常通知(只有当程序执行过程中出现异常时才会执行)
    @AfterThrowing(value = "myPointCut()",throwing = "e")
    public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
        System.out.println("异常通知:"+"出错了"+e.getMessage());
    }
    //最终通知(无论是否有异常抛出,都会执行)
    @After("myPointCut()")
    public void myAfter(){
        System.out.println("最终通知:模拟方法执行结束后的释放资源...");
    }
}
<!--Spring的配置文件applicationContext.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!--开启注解扫描,使注解生效-->
    <context:component-scan base-package="com.jd.aspectj"/>
    <!--开启Aspectj生成代理对象-->
    <aop:aspectj-autoproxy/>
</beans>
//测试方法
@Test
public void annotationAspectjTest(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao= (UserDao) context.getBean("userDao");
    userDao.addUser();
    System.out.println("---------------------------------------------------------------------");
    userDao.deleteUser();
}

没有异常时的正常通知情况:
在这里插入图片描述
模拟UserDaoImpl中出现异常,如下:
在这里插入图片描述
同样,可以使用配置类替代配置文件,具体如下:
在这里插入图片描述

扩展有多个切面类(增强类)对同一个方法进行增强,可以设置切面类(增强类)的优先级,在增强类上面添加注解@Order(数字类型值),数字类型值越小优先级越高

@component
@Aspect
@Order(1)
public class YourAspect{...}

2.基于XML的声明式AspectJ

(xml的方式了解即可)
UserDao和UserDaoImpl还是上面的,需要该变的是Spring配置文件、切面类、

//切面类
public class MyAspect2 {
    //定义切入点表达式
    public void myPointCut(){}
    //前置通知
    public void myBefore(JoinPoint joinPoint){
        System.out.print("前置通知:模拟检查权限...");
        System.out.print("目标类是:"+joinPoint.getTarget());
        System.out.println(",被织入增强的目标方法是:"+joinPoint.getSignature().getName());
    }
    //后置通知
    public void myAfterReturning(JoinPoint joinPoint){
        System.out.print("后置通知:模拟记录日志...");
        System.out.println(",被织入增强处理的方法是:"+joinPoint.getSignature().getName());
    }
    //环绕通知
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //开始
        System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
        //执行当前目标方法
        Object obj = proceedingJoinPoint.proceed();
        //结束
        System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
        return obj;
    }
    //异常通知(只有当程序执行过程中出现异常时才会执行)
    public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
        System.out.println("异常通知:"+"出错了"+e.getMessage());
    }
    //最终通知(是否有异常抛出,都会执行)
    public void myAfter(){
        System.out.println("最终通知:模拟方法执行结束后的释放资源...");
    }
}
<!--xml方式实现aop的Spring配置文件-->
<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!--1.目标类-->
    <bean id="userDao" class="com.jd.aspectj.dao.UserDaoImpl"/>
    <!--2.切面类-->
    <bean id="myAspect2" class="com.jd.aspectj.xml.MyAspect2"/>
    <!--3.aop编程-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect ref="myAspect2">
        <!--配置切入点,通知最后需要增强哪些方法-->
        <aop:pointcut id="myPointCut" expression="execution(* com.jd.aspectj.dao.*.*(..))"/>
            <!--前置通知-->
            <aop:before method="myBefore" pointcut-ref="myPointCut"/>
            <aop:after method="myAfterReturning" pointcut-ref="myPointCut"/>
            <aop:around method="myAround" pointcut-ref="myPointCut"/>
            <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
            <aop:after method="myAfter" pointcut-ref="myPointCut"/>
        </aop:aspect>
    </aop:config>
</beans>
//测试方法
@Test
public void xmlAspectjTest(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao= (UserDao) context.getBean("userDao");
    userDao.addUser();
    System.out.println("---------------------------------------------------------------------");
    userDao.deleteUser();
}

没有异常时的正常通知情况:
在这里插入图片描述
模拟UserDaoImpl中出现异常,如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智商三岁半i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值