栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

文章目录
  • 一、AOP
    • JDK动态代理
    • CGLIB动态代理
  • 二、AOP开发
    • AspectJ开发
    • 1.基于注解的声明式AspectJ
    • 2.基于XML的声明式AspectJ

一、AOP

1.面向切面编程(AOP)
AOP全称Aspect-Orinted Programing(面向切面编程),是一种编程思想,是OOP的延伸和补充,采用横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行的时候,再将这些提取出来的代码应用到需要执行的地方。
2.AOP术语
(1)Aspect(切面):封装的用于横向插入系统功能(如事务、权限、日志)的类;
(2)JoinPoint(连接点):程序执行的某个阶段点,如方法的调用、异常的抛出等;(在Spring的AOP中,连接点就是方法的调用)
(3)PointCut(切入点):需要处理的连接点;
(4)Advice(通知/增强):定义好的切入点需要执行的代码;
(通知分:前置、后置、环绕、异常、引介、最终通知)
(5)Target Object(目标对象):所有被通知的对象,也称为被增强对象;
(6)Proxy(代理):通知应用到目标对象后被创建的对象;
(7)Weaving(织入):将切面插入到目标对象上,从而生成代理对象的过程;
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);
    }
    
    @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();
    }
    
    @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("最终通知:模拟方法执行结束后的释放资源...");
    }
}


    
    
    
    

//测试方法
@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("最终通知:模拟方法执行结束后的释放资源...");
    }
}


    
    
    
    
    
    
        
        
        
        
            
            
            
            
            
            
        
    

//测试方法
@Test
public void xmlAspectjTest(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao= (UserDao) context.getBean("userDao");
    userDao.addUser();
    System.out.println("---------------------------------------------------------------------");
    userDao.deleteUser();
}

没有异常时的正常通知情况:

模拟UserDaoImpl中出现异常,如下:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/673223.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号