zoukankan      html  css  js  c++  java
  • AOP介绍

    一、概念

           AOP(Aspect Oriented Programming)是面向切面编程。就是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

    二、使用场景

    • 权限的校验

    • 日志的记录

    • 事务的处理

    • 性能的检测

    三、AOP对功能的增强方式

    AOP对程序的扩展方式采用动态代理的方式. (JDK动态代理和Cglib动态代理两种方式)

    1)生活中的代理:

    2)程序中的代理描述

    四、JDK的动态代理

        **程序不用改变   在程序运行的过程中生成一个代理对象 用来完成所要增加的功能的需求

            ---介绍对应的API , 是java提供的Proxy类  

    • Proxy类的静态方法可以创建代理对象
      static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
      
      /*
      参数1:ClassLoader loader 类加载器 , 用来加载代理对象
      
      参数2:Class<?>[] interfaces 目标类的字节码对象数组. 因为代理的是接口,需要知道接口中所有的方法
      
      参数3:InvocationHandler h 执行句柄, 代理对象处理的核心逻辑就在该接口中
      */
    •   ---介绍接口 InvocationHandler
    • Object invoke(Object proxy, Method method, Object[] args) 
      
      
      /*当代理对象调用原来的目标方法的时候就会执行InvocationHandler 中的invoke方法
      
      参数1: Object proxy  代理对象
      
      参数2: Method method  当前调用方法的对象
      
      参数3: Object[] args  实际传递的参数*/

      需求:完成用户登录,记录日志

    1.编写UserDao

    public interface UserDao {
         public void login();
    }
    

    2.编写UserDaoImpl

    public class UserDaoImpl implements UserDao {
    
        public void login() {
            System.out.println("用户登录...");
        }
    }

    3.编写ProxyBeanFactory

    package com.itheima.jdkproxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 创建UserDao的代理对象
     */
    public class ProxyBeanFactory {
        //1.声明目标对象  UserDaoImpl  用它来获取类加载器 获取接口的字节码数组
        private UserDaoImpl  target;
    
        //2.创建有参构造  目的是初始化UserDaoImpl 对象
    
    
        public ProxyBeanFactory(UserDaoImpl target) {
            this.target = target;
        }
    
        //3.创建一个获取代理对象的方法
        public Object  getProxyBean(){
            /**
             * Proxy.newProxyInstance 返回一个代理对象
             * 参数1:类加载器
             * 参数2:接口的字节码数组对象
             * 参数3: InvocationHandler 接口的匿名内部类对象  代理对象执行的核心逻辑
             */
            return Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    new InvocationHandler() {
                        /**
                         * 当代理对象调用login方法的时候,就会进入该invoke方法执行
                         * 参数1:Object proxy 代理对象 在内存中生成
                         * 参数2:执行方法的对象
                         * 参数3:方法传递的实际参数
                         */
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //1.调用原方法
                            method.invoke(target);
                            //2.模拟记录日志
                            System.out.println("记录日志完毕");
                            return null;
                        }
                    });
        }
    }
    

    4.测试

    package com.itheima.jdkproxy;
    
    public class UserDaoTest {
    
        public static void main(String[] args) {
            //1.创建UserDaoImpl
            UserDaoImpl target = new UserDaoImpl();
            //2.创建ProxyBeanFactory
            ProxyBeanFactory factory = new ProxyBeanFactory(target);
            //3.调用ProxyBeanFactory 获取代理对象的方法  getProxyBean
            UserDao proxyBean = (UserDao) factory.getProxyBean();
            //4.使用代理对象调用login方法看看是否有记录日志 它会进入获取代理对象的工厂类中的invoke方法执行
            proxyBean.login();
        }
    }
    

    五、Cglib的动态代理

    该实现代理的方式是spring提供. 索引要引入spring的依赖

    准备:导入spring-context.jar

     <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>4.2.4.RELEASE</version>
            </dependency>
        </dependencies>

    介绍API

    Enhance cglib的核心类 ,主要作用就是创建代理对象

    • setSuperClass() 设置目标类的字节码对象

    • setCallback() 设置回调函数, 目的让代理对象执行的时候进入回调函数执行具体的逻辑 ; 该方法的参数

      Callback 接口 , 我们传递的是子接口 MethodInceptor 匿名内部类

    • create() 创建代理对象的方法

    需求:

    使用Cgblib 来代理我们PersonService 类 , 给该类中的 work方法添加日志的增强.

    分析

    代码实现

    1.编写PersonService

    public class PersonService {
    
        public void work(){
            System.out.println("我爱上班..");
        }
    }
    

    2.编写创建代理对象的工厂

    public class ProxyBeanFactory {
    
        //1.声明目标类对象
        private PersonService target;
    
        //2.提供有参构造
    
        public ProxyBeanFactory(PersonService target) {
            this.target = target;
        }
    
        //3.创建方法获取代理对象
        public Object getProxyBean(){
            //1)创建Enhance核心对象
            Enhancer enhancer = new Enhancer();
            //2)设置SuperClass
            enhancer.setSuperclass(target.getClass());
            //3)设置回调函数
            /**
             * 给该回调函数中传递一个Callback的子接口 MehtodInterceptor 的匿名内部类
             * MehtodInterceptor 接口中有intercept方法拦截
             */
            enhancer.setCallback(new MethodInterceptor() {
                /**
                 * intercept 函数当代理对象执行目标方法时,进入该拦截方法,执行里面的内容
                 * 参数1:Object proxy 代理对象
                 * 参数2:Method method 当前执行的方法对象
                 * 参数3:原目标方法的实际传入的参数Object[] objects
                 * 参数4: MethodProxy methodProxy 代理方法对象
                 */
    
                public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    method.invoke(target);
                    System.out.println("记录日志");
                    return null;
                }
            });
            //4.返回create的代理对象
            return enhancer.create();
        }
    
    }
    

    3.测试

    package com.itheima.cglibproxy;
    
    public class PersonServiceTest {
    
        public static void main(String[] args) {
            PersonService service = new PersonService();
            ProxyBeanFactory factory = new ProxyBeanFactory(service);
    
            //获取代理对象
            PersonService proxyBean = (PersonService) factory.getProxyBean();
            proxyBean.work();
        }
    }
    

    六、 AOP专业术语 

    1. 目标类target:就是我们需要增强的那个类
    2. 代理类proxy:就是自定义的代理的对象
    3. 连接点joinPoint:程序执行的某个特定位置,Spring仅支持方法的连接点
    4. 切入点pointCut:就是在目标类中实际增强的方法
    5. 织入weave:就是将代理类中需要增强的方法放入到目标类中去执行的过程
    6. 引介Introduction:引介是一种特殊的增强,它为类添加一些属性和方法 
    7. 通知advice:将代理对象中的方法应用到目标类的过程中产生的结果。
    8. 切面aspect:所有的连接点和代理对象的方法组成在一起 构成了切面

    七、基于xml的方式实现spring的aop 

    需求:

    使用Spring的aop完成User类的work方法的增强,添加记录日志功能 (不能修改源代码,只做配置)

    导包    

     <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>4.2.4.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.7.2</version>
            </dependency>
    
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>4.2.4.RELEASE</version>
            </dependency>
        </dependencies>

    分析:

    实现

    1.目标接口

    package com.itheima.aop_xml;
    
    public interface User {
    
        public void work();
    }
    

    2.目标实现

    package com.itheima.aop_xml;
    
    public class UserImpl implements User {
    
        public void work() {
            System.out.println("我爱加班!!");
        }
    }
    

    3.增强类

    package com.itheima.aop_xml;
    
    //增强类
    public class Advice {
    
        public void writeLog(){
            System.out.println("记录日志");
        }
    }
    

    4.bean3.xml配置

    <!--1.创建目标类对象-->
        <bean id="user" class="com.itheima.aop_xml.UserImpl"/>
        <!--2.创建增强类对象-->
        <bean id="advice" class="com.itheima.aop_xml.Advice"/>
        <!--3.配置aop-->
        <aop:config>
            <!--3.1配置切入点-->
            <aop:pointcut id="point01" expression="execution(* *.work(..))"/>
            <!--3.2配置切面-->
            <aop:aspect ref="advice">
                <!--spring 给我的增强方式有5种
                    before 前置增强,增强的功能将会在目标方法之前执行
                    method 使用增强类中的哪个方法进行增强
                -->
                <aop:before method="writeLog" pointcut-ref="point01"/>
            </aop:aspect>
        </aop:config>
    </beans>

    5.测试类

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:bean3.xml")
    public class UserTest {
    
        @Autowired
        private User user;
    
        @Test
        public void testUser(){
            System.out.println(user.getClass());
            user.work();
        }
    }

    八、切点表达式

    execution(modifiers-pattern?ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
    
    ​       这里问号表示当前项可以有也可以没有,其中各项的语义如下:
    
    ​       modifiers-pattern:方法的可见性,如public,protected;
    
    ​       ret-type-pattern:方法的返回值类型,如int,void等;
    
    ​       declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
    
    ​       name-pattern:方法名类型,如buisinessService();
    
    ​       param-pattern:.. 方法的参数类型,如java.lang.String;
    
    ​       throws-pattern:方法抛出的异常类型,如java.lang.Exception;

    九、通知的方式

    1. 前置通知 : 增强的逻辑在目标方法之前执行

    2. 后置通知 : 增强的逻辑在目标方法之后执行

    3. 异常通知: 目标方法出现异常,则增项逻辑运行

    4. 环绕通知: 目标方法执行之前和之后,增强的逻辑都运行

    5. 最终通知: 增强的逻辑在目标方法之后执行,不管有没有异常都会执行.

    代码实现:

    1、User类

    package com.itheima.aop_xml;
    
    public interface User {
    
        public void work();
    
        public void addUser();  // 前置增强该方法
    
        public void deleteUser();  //后置增强该方法
    
        public void updateUser();  //异常增强
    
        public void queryUser();  //环绕增强
    
        public void batchDeleteUser();  //最终增强
    }
    

    2、UserImpl实现类

    package com.itheima.aop_xml;
    
    public class UserImpl implements User {
    
        public void work() {
            System.out.println("我爱加班!!");
        }
    
        public void addUser() {
            System.out.println("添加用户");
        }
    
        public void deleteUser() {
            System.out.println("删除用户");
    
        }
    
        public void updateUser() {
            System.out.println("修改用户");
            int i = 1/0;
        }
    
        public void queryUser() {
            System.out.println("查询用户");
        }
    
        public void batchDeleteUser() {
            System.out.println("批量删除用户");
            int i = 1/0;
        }
    }
    

    3.增强类

    package com.itheima.aop_xml;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    
    //增强类
    public class Advice {
    
        public void writeLog(){
            System.out.println("记录日志");
        }
    
        public void before(){
            System.out.println("前面记录日志");
        }
    
        public void afterReturning(){
            System.out.println("后面记录日志");
        }
    
        public void throwable(){
            System.out.println("异常记录日志");
        }
    
        //..暂定
        public void  around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("环绕增强前");
            joinPoint.proceed();  //切到原来的目标方法,进行执行
            System.out.println("环绕增强后");
        }
    
        public void  after(){
            System.out.println("最终记录日志");
        }
    }
    

    4、bean4.xml

     <!--1.创建目标类对象-->
        <bean id="user" class="com.itheima.aop_xml.UserImpl"/>
        <!--2.创建增强类对象-->
        <bean id="advice" class="com.itheima.aop_xml.Advice"/>
        <!--3.配置aop-->
        <aop:config>
            <!--让增加方法变为切点-->
            <aop:pointcut id="add" expression="execution(* com.itheima..add*(..))"/>
            <!--让删除方法变为切点-->
            <aop:pointcut id="delete" expression="execution(* com.itheima..delete*(..))"/>
            <!--让修改方法变为切点-->
            <aop:pointcut id="update" expression="execution(* com.itheima..update*(..))"/>
            <!--让查询方法变为切点-->
            <aop:pointcut id="query" expression="execution(* com.itheima..query*(..))"/>
            <!--让批量删除法变为切点-->
            <aop:pointcut id="batch" expression="execution(* com.itheima..batch*(..))"/>
    
            <!--3.2配置切面-->
            <aop:aspect ref="advice">
                <!--spring 给我的增强方式有5种
                    before 前置增强,增强的功能将会在目标方法之前执行
                    method 使用增强类中的哪个方法进行增强
                -->
                <aop:before method="before" pointcut-ref="add" />
                <!--后置通知增强delete方法-->
                <aop:after-returning method="afterReturning" pointcut-ref="delete" />
                <!--异常通知增强修改-->
                <aop:after-throwing method="throwable" pointcut-ref="update"/>
                <!--环绕通知增强查询-->
                <aop:around method="around" pointcut-ref="query"/>
                <!--使用最终通知增强批量删除-->
                <aop:after method="after" pointcut-ref="batch"/>
            </aop:aspect>
        </aop:config>
    </beans>

    5、测试

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:bean4.xml")
    public class UserTest {
    
        @Autowired
        private User user;
    
        @Test
        public void testUser(){
            //System.out.println(user.getClass());
            //user.work();
            //user.addUser();
            //user.deleteUser();
            //user.updateUser();
            //user.queryUser();
            user.batchDeleteUser();
        }
    }

    十、 AOP基于注解实现 

    需求:使用增强类中的不同方法来增强Person类中的方法进行前置,后置,异常,环绕,最终增强

    1.配置文件配置

    <!--包扫描创建对象-->
        <context:component-scan base-package="com.itheima.aop_annotation"/>
    
        <!--开启自动代理-->
        <aop:aspectj-autoproxy/>

    2.Person 类

    package com.itheima.aop_annotation;
    
    public interface Person {
    
        public void work();
    
        public void addPerson();  // 前置增强该方法
    
        public void deletePerson();  //后置增强该方法
    
        public void updatePerson();  //异常增强
    
        public void queryPerson();  //环绕增强
    
        public void batchDeletePerson();  //最终增强
    }
    

    3.PersonImpl实现

    package com.itheima.aop_annotation;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class PersonImpl implements Person {
    
        public void work() {
            System.out.println("我爱加班!!");
        }
    
        public void addPerson() {
            System.out.println("添加用户");
        }
    
        public void deletePerson() {
            System.out.println("删除用户");
    
        }
    
        public void updatePerson() {
            System.out.println("修改用户");
            int i = 1/0;
        }
    
        public void queryPerson() {
            System.out.println("查询用户");
        }
    
        public void batchDeletePerson() {
            System.out.println("批量删除用户");
            int i = 1/0;
        }
    }
    

    3.增强类

    package com.itheima.aop_annotation;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    //增强类
    @Component
    @Aspect  //让本类作为一个切面
    public class PersonAdvice {
    
        public void writeLog(){
            System.out.println("记录日志");
        }
    
        @Before("execution(* com.itheima.aop_annotation..add*(..))")
        public void before(){
            System.out.println("前面记录日志");
        }
    
        @AfterReturning("execution(* com.itheima.aop_annotation..delete*(..))")
        public void afterReturning(){
            System.out.println("后面记录日志");
        }
    
        @AfterThrowing("execution(* com.itheima.aop_annotation..update*(..))")
        public void throwable(){
            System.out.println("异常记录日志");
        }
    
        @Around("execution(* com.itheima.aop_annotation..query*(..))")
        public void  around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("环绕增强前");
            joinPoint.proceed();  //切到原来的目标方法,进行执行
            System.out.println("环绕增强后");
        }
    
        @After("execution(* com.itheima.aop_annotation..bat*(..))")
        public void  after(){
            System.out.println("最终记录日志");
        }
    }
    

    4.测试类

    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:bean5.xml")
    public class PersonTest {
    
        @Autowired
        private  Person person;
    
        @Test
        public void test(){
            //person.addPerson();   //前置
            //person.deletePerson(); //后置
            //person.updatePerson();  //异常
            //person.queryPerson();
            person.batchDeletePerson();
        }
    }
    
  • 相关阅读:
    再见 2020, 愿“山河无恙,人间皆安”| 年终总结
    Oracle
    Linux安装
    线程池
    AutoJS
    VSCode
    c++ 解析yaml文件
    管道: 哪些命令能直接从管道的输出中读取?
    K8S 集群部署
    Android项目实战(六十一):pdf文件用图片方式预览
  • 原文地址:https://www.cnblogs.com/haojia/p/12386233.html
Copyright © 2011-2022 走看看