• 第3章 Spring AOP


    3.1 Spring AOP简介

    3.11什么是AOP?

    AOP的全称是Aspect-Oriented Programming,即面向切面编程(也称面向方面编程)。它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。

    AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,采用传统的OOP思想显然无法办到,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,但不是OOP的替代品,只是延伸和补充。

    在AOP思想中,类与切面的关系如图3-1所示。

    经典应用:事务处理,日志记录等。

    3.12 AOP术语

    Aspect(切面):在实际应用中,切面通常是指封装的用于横向插入系统功能(如事务、日志等)的该类要被Spring容器识别为切面,需要在配置文件中通过<bean>元素指定

    Joinpoint(连接点):在程序执行过程中的某个阶段点,它实际上是对象的一个操作,例如方法的调用或者异常的抛出。在Spring AOP中,连接点就是指方法的调用

    Pointcut(切入点):是指切面与程序流程的交叉点,即那些需要处理的连接点。通常在程序中,切入点指的是类或者方法名,如某个通知要应用到所有以add开头的方法中,那么所有满足这一规则的方法都是切入点。

    Advice(通知/增强处理):AOP框架在特定切入点执行的增强处理,即定义好的切入点处所要执行的代码。可以将其理解为切面类中的方法,它是切面的具体体现。

    Target Object(目标对象):指所有被通知的对象,也称为被增强的对象。如果AOP框架采用的是动态AOP实现,那么该对象就是一个被代理对象。

    Proxy(代理):将通知应用到目标对象之后,被动态创建 的 对象。

    Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程

    3.2 动态代理:AOP中的代理就是由AOP框架动态生成的一个对象,该对象可以作为目标对象使用

    3.21 JDK动态代理

    package com.itheima.jdk;
    public interface UserDao {
        public void addUser();
        public void deleteUser();
    }
    package com.itheima.jdk;
    import org.springframework.stereotype.Repository;
    
    // 目标类
    /**首先使用@Repository注解将UserDaoImpl类标识为Spring中的Bean,相当于配置文件中的
    <bean id="userDao" class="com.itheima.annotation.UserDaoImpl"/> 很普通的配置*/
    @Repository("userDao")
    public class UserDaoImpl implements UserDao {
        public void addUser() {
            System.out.println("添加用户");
        }
        public void deleteUser() {
            System.out.println("删除用户");
        }
    }

    本案例会将实现类UserDaoImpl作为目标类,对其中的方法进行增强处理。

    package com.itheima.aspect;
    //切面类:可以存在多个通知Advice(即增强的方法),要切入的各种方法
    public class MyAspect {
        public void check_Permissions(){
            System.out.println("模拟检查权限...");
        }
        public void log(){
            System.out.println("模拟记录日志...");
        }
    }
    package com.itheima.jdk;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import com.itheima.aspect.MyAspect;
    /**
     * JDK代理类,需要实现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[] clazz = userDao.getClass().getInterfaces();
            ///userDao是UserDao这个类参数进来的,里面有很多接口要实现
            
            // 3.使用代理类,进行增强,返回的是代理后的对象
            return  Proxy.newProxyInstance(classLoader,clazz,this);
            ///第一个参数是当前类加载器,第二个参数表示 被代理对象实现的所有接口
            ///第三个参数this代表的就是代理类JdkProxy本身
        }
        /**
         * 所有动态代理类的方法调用,都会交由invoke()方法去处理
         * proxy 被代理后的对象 
         * method 将要被执行的方法信息(反射) 
         * args 执行方法时需要的参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) 
                                                                    throws Throwable {
            // 声明切面,先造一个切面类对象,等下各种引用
            MyAspect myAspect = new MyAspect();
            
            // 前增强,这条语句放在前面
            myAspect.check_Permissions();
            
            // 在目标类上调用方法,并传入参数
            Object obj = method.invoke(userDao, args);
            
            // 后增强,这条语句放在后面
            myAspect.log();
            
            return obj;
        }
    }

    JdkProxy实现了InvocationHandler接口,并实现了接口中的invoke()方法,所有动态代理类所调用的方法都会交给该方法处理。

    jdk的动态代理调用了Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法。

    通过该方法生成字节码,动态的创建了一个代理类,interfaces参数是该动态类所继承的所有接口,而继承InvocationHandler 接口的类则是实现在调用代理接口方法前后的具体逻辑

    第一个参数是当前类加载器,第二个参数表示 被代理对象实现的所有接口,第三个参数this代表的就是代理类JdkProxy本身

    java反射机制--Method.invoke方法,不懂,照抄模仿

    args 执行方法时需要的参数,不懂,照抄模仿

     

    package com.itheima.jdk;
    public class JdkTest{
        public static void main(String[] args) {
            // 创建代理对象,类似输入常用的scan,然后再调用该对象的方法
            JdkProxy jdkProxy = new JdkProxy();
            
             // 创建目标对象
            UserDao userDao= new UserDaoImpl();
            
            // 从代理对象中获取增强后的目标对象
            //**后面参数是userDao,表示这个对象被增强
            UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao);
            
            //**测试不同
            userDao.addUser();
            userDao.deleteUser();//很普通的调用
            System.out.println("facai111");
            
            // 执行方法
            userDao1.addUser();
            System.out.println("facai222");
            userDao1.deleteUser();//被代理对象jdk.Proxy用CreateProxy增强的对象,
        }
    }

     

    输出:

    添加用户
    删除用户
    facai111
    模拟检查权限...
    添加用户
    模拟记录日志...
    facai222
    模拟检查权限...
    删除用户
    模拟记录日志...

    UserDao中的方法都被增强了,增强指的是切面的具体实现,本案例实现形式为在方法前后插入两个切面方法。

    3.22 CGLIB代理(Code Generation Library)

    JDK动态代理的使用非常简单,但它还有一定的局限性:使用动态代理的对象必须实现一个或多个接口

    如果要对没有实现接口的类进行代理,可以使用CGLIB代理。

    CGLIB是一个高性能开源的代码生成包,它采用非常底层的字码技术,对指定的目标类生成一个子类,并对子类进行增强。在Spring的核心包中已经继承了CGLIB所需要的包,不需要另外导入包。

    package com.itheima.cglib;
    //目标类
    public class UserDao {
        public void addUser() {
            System.out.println("添加用户");
        }
        public void deleteUser() {
            System.out.println("删除用户");
        }
    }

    没有接口,直接是实现类。

    package com.itheima.aspect;
    //切面类:可以存在多个通知Advice(即增强的方法),要切入的各种方法
    public class MyAspect {
        public void check_Permissions(){
            System.out.println("模拟检查权限...");
        }
        public void log(){
            System.out.println("模拟记录日志...");
        }
    }
    package com.itheima.cglib;
    import java.lang.reflect.Method;
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    import com.itheima.aspect.MyAspect;
    // 代理类 public class CglibProxy implements MethodInterceptor{ // 代理方法 public Object createProxy(Object target) { // 创建一个动态类对象 Enhancer enhancer = new Enhancer(); // 确定需要增强的类,设置其父类 enhancer.setSuperclass(target.getClass()); // 添加回调函数 enhancer.setCallback(this); // 返回创建的代理类 return enhancer.create(); } /** * proxy CGlib根据指定父类生成的代理对象 * method 拦截的方法 * args 拦截方法的参数数组 * methodProxy 方法的代理对象,用于执行父类的方法 */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 创建切面类对象 MyAspect myAspect = new MyAspect(); // 前增强 myAspect.check_Permissions(); // 目标方法执行 Object obj = methodProxy.invokeSuper(proxy, args); // 后增强 myAspect.log(); return obj; } }

    Cglib代理类需要实现MethodInterceptor接口,并实现接口中的intercept()方法。

    package com.itheima.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();
        }
    }

    输出:

    模拟检查权限...
    添加用户
    模拟记录日志...
    模拟检查权限...
    删除用户
    模拟记录日志...

     

    3.3 基于代理类的AOP实现

    实际上,Spring中的AOP代理默认就是使用JDK动态代理的方式来实现的。在Spring中,使用ProxyFactoryBean是创建AOP代理的基本方式。

    3.31 Spring的通知类型

    org.aopalliance.intercept.MethodInterceptor(环绕通知)
    在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。

    org.springframework.aop.MethodBeforeAdvice(前置通知)
    在目标方法执行前实施增强,可以应用于权限管理等功能。

    org.springframework.aop.AfterReturningAdvice(后置通知)
    在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。

    org.springframework.aop.ThrowsAdvice(异常抛出通知)
    在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。

    org.springframework.aop.IntroductionInterceptor(引介通知)
    在目标类中添加一些新的方法和属性,可以应用于修改老版本程序(增强类)。

    3.32 ProxyFactoryBean

    ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactoryBean负责为其他Bean创建代理实例。在Spring中,使用ProxyFactoryBean是创建AOP代理的最基本方式。

    ProxyFactoryBean类中的常用可配置属性:

    package com.itheima.factorybean;
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    
    // 切面类
    public class MyAspect implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation mi) throws Throwable 
        {
            check_Permissions();
            
            // 执行目标方法
            Object obj = mi.proceed();
            
            log();
            return obj;
        }
        public void check_Permissions(){
            System.out.println("模拟检查权限...");
        }
        public void log(){
            System.out.println("模拟记录日志...");
        }
    }

    切面类:需要实现MethodInterceptor接口,并且实现接口中的invoke()方法,来执行目标方法。在目标方法前后分别执行了检查权限和记录日志的方法,这两个方法也就是增强的方法,也就是通知。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        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-4.3.xsd">
        
        <!-- 1 目标类:jdk包里的Dao实现类 -->
        <bean id="userDao111" class="com.itheima.jdk.UserDaoImpl" />
        
        <!-- 2 切面类:本包里的切面类 -->
        <bean id="myAspect222" class="com.itheima.factorybean.MyAspect" />
        
        
        <!-- 3 使用Spring代理工厂定义一个名称为userDaoProxy的代理对象 -->
        <bean id="userDaoProxy"  class="org.springframework.aop.framework.ProxyFactoryBean">
            
            <!-- 3.1 指定代理实现的接口--> 
            <property name="proxyInterfaces"  value="com.itheima.jdk.UserDao" />
            
            <!-- 3.2 指定目标对象 -->
            <property name="target" ref="userDao111" />
            
            <!-- 3.3 指定切面,织入环绕通知 -->
            <property name="interceptorNames" value="myAspect222" />
            
            <!-- 3.4 指定代理方式,true:使用cglib,false(默认):使用jdk动态代理 -->
            <property name="proxyTargetClass" value="true" />
        
        </bean>
    </beans>

    第三点,class="org.springframework.aop.framework.ProxyFactoryBean"这个是导入包里的,固定的,照抄就行。

    3.1指定代理实现的接口:property元素里的name的值是上表的属性名称,固定选proxyInterfaces,后面的value是接口的包路径

    3.2指定目标对象:property元素里的name的值是上表的属性名称,固定选target,后面的ref是目标类的bean的id

    3.3指定切面,织入环绕通知: name的值是interceptorNames,后面的value是切面类的bean的id

    3.4代理方式: name是proxyTargetClass,后面的value如果是true,则是cglib代理,如果是false,则是jdk代理。

    总结:name是固定的,target、proxyInterfaces、proxyTargetClass、interceptorNames、singleton、optimize其中一个。

    package com.itheima.factorybean;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;///原始配置方式需要的包
    import com.itheima.jdk.UserDao;///目标类不在这个包里,在jdk那个包里
    
    // 测试类
    public class ProxyFactoryBeanTest {
        public static void main(String args[]) 
        {
           String xmlPath = "com/itheima/factorybean/applicationContext.xml";
           ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
           
           // 从Spring容器获得内容
           UserDao userDao = (UserDao) applicationContext.getBean("userDaoProxy");
           
           // 执行方法
           userDao.addUser();
           System.out.println("facai111");
           userDao.deleteUser();
        }
    }

    输出:

    模拟检查权限...
    添加用户
    模拟记录日志...
    facai111
    模拟检查权限...
    删除用户
    模拟记录日志...

    3.4 AspectJ开发

    使用AspectJ实现AOP有两种方式:一种基于XML的声明式AspectJ,另一种是基于注解的声明式AspectJ。

    3.41 基于XML的声明式AspectJ:指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在<aop:config>元素内。

    <aop:config>元素及其子元素:

    XML常用配置元素如下:

    <!--定义切面Bean -->
        <bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" /> 
            <aop:config>
                <!-- 配置切面 -->
                <aop:aspect  id="aspect" ref="myAspect"> 
                <!-- 配置切入点 -->
                <aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))“ id="myPointCut" />
                <aop:before method="myBefore" pointcut-ref="myPointCut" />
                <aop:after-returning method="myAfterReturning“ pointcut-ref="myPointCut" returning="returnVal" />
                <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>

    配置切面:在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean(如myAspect)。配置<aop:aspect>元素时,通常会指定id和ref两个属性:

    配置切入点:<aop:pointcut>元素如果作为<aop:config>元素的子元素定义时,表示全局切入点,可以被多个切面共享;如果作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效。<aop:pointcut>元素通常会指定id和expression两个属性。

    切入点表达式:execution( * com.itheima.jdk.*.*(..) ),该切入点表达式的意思是匹配com.itheima.jdk包中任意类的任意方法的执行。第1个*表示的是返回类型,使用*代表所有类型;com.itheiima.jdk表示需要拦截的包名;第2个*表示的是类名,使用*代表所有的类;第3个*表示的是方法名,使用*表示所有的方法;后面的(..)表示方法的参数,其中".."表示任意参数。注意:第1个*和包名之间有一个空格。

    切入点表达式的基本格式:

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

    蓝色带问号属于可配置项,爱配不配,其他必须配置。

    配置通知使用<aop:aspect>的子元素可以配置5种常用通知,这5个子元素不支持使用子元素,但在使用时可以指定一些属性,其常用属性及其描述如下:

    比如在com.itheima.aspectj.xml包里

    package com.itheima.aspectj.xml;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    /**
     *切面类,在此类中编写通知
     */
    public class MyAspect {
        // 前置通知
        public void myBefore1(JoinPoint joinPoint) {
            System.out.print("前置通知 :模拟执行权限检查...,");
            System.out.print("目标类是:"+joinPoint.getTarget() );
            System.out.println(",被织入增强处理的目标方法为:"
                                +joinPoint.getSignature().getName());
        }
        // 后置通知
        public void myAfterReturning2(JoinPoint joinPoint) {
            System.out.print("后置通知:模拟记录日志...," );
            System.out.println("被织入增强处理的目标方法为:"
                              + joinPoint.getSignature().getName());
        }
        /**
         * 环绕通知
         * ProceedingJoinPoint 是JoinPoint子接口,表示可以执行目标方法
         * 1.必须是Object类型的返回值
         * 2.必须接收一个参数,类型为ProceedingJoinPoint
         * 3.必须throws Throwable
         */
        public Object myAround3(ProceedingJoinPoint proceedingJoinPoint) 
                 throws Throwable {
            // 开始
            System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
            // 执行当前目标方法
            Object obj = proceedingJoinPoint.proceed();
            // 结束
            System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
            return obj;
        }
        // 异常通知
        public void myAfterThrowing4(JoinPoint joinPoint, Throwable e) {
            System.out.println("异常通知:" + "出错了" + e.getMessage());
        }
        // 最终通知
        public void myAfter5() {
            System.out.println("最终通知:模拟方法结束后的释放资源...");
        }
    }

    分别定义了5种不同类型的通知,在通知中使用了JoinPoint接口及其子接口ProceedingJoinPoint作为参数来获得目标对象的类名、目标方法和目标方法参数等。注意:环绕通知必须接收一个类型为ProceedingJoinPoint的参数,返回值也必须是Object类型,且必须抛出异常。异常通知中可以传入Throwable类型的参数来输出异常信息。

    <?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"
            xsi:schemaLocation="http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
            http://www.springframework.org/schema/aop 
            http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
        <!-- 4、7、8行代码中,分别引入了schema约束 -->
        
        <!-- 1 目标类 普通配置,添加一个id为userDao的实例 -->
        <bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
        
        <!-- 2 切面  普通配置-->
        <bean id="myAspect111" class="com.itheima.aspectj.xml.MyAspect" />
        
        <!-- 3 aop编程 -->
        <aop:config>
            <!-- 配置切面 -->
            <aop:aspect ref="myAspect111">
                  <!-- 3.1 配置切入点,通知最后增强哪些方法 -->
                 <aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))"
                                                          id="myPointCut" />
                                                          
                <!-- 3.2 关联通知Advice和切入点pointCut -->
                <!-- 3.2.1 前置通知 -->
                <aop:before method="myBefore1" pointcut-ref="myPointCut" />
                
                <!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值
                 returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
                <aop:after-returning method="myAfterReturning2"
                    pointcut-ref="myPointCut" returning="returnVal123" />
                    
                <!-- 3.2.3 环绕通知 -->
                <aop:around method="myAround3" pointcut-ref="myPointCut" />
                
                <!-- 3.2.4 抛出通知:用于处理程序发生异常-->
                <!-- * 注意:如果程序没有异常,将不会执行增强 -->
                <!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
                <aop:after-throwing method="myAfterThrowing4"
                    pointcut-ref="myPointCut" throwing="e" />
                
                <!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
                <aop:after method="myAfter5" pointcut-ref="myPointCut" />
            </aop:aspect>
        </aop:config>
    </beans>

    后置通知:只有目标方法被执行后才会被织入。

    最终通知:不论目标方法如何结束(正常执行或者异常终止)都会被织入。

    后置通知的属性中多了一个returning="returnVal123",尝试修改后面的值,都能正常输出结果,表明后面的值可以自由命名。

    method为方法名,要和切面类中的方法名一样,pointcut-ref是配置切面后的切入点id。

    package com.itheima.aspectj.xml;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import com.itheima.jdk.UserDao;
    // 测试类
    public class TestXmlAspectj {
        public static void main(String args[]) {
            
            String xmlPath = 
                             "com/itheima/aspectj/xml/applicationContext.xml";
            
            ApplicationContext applicationContext = 
                              new ClassPathXmlApplicationContext(xmlPath);
            
            // 1 从spring容器获得内容
            UserDao userDao = (UserDao) applicationContext.getBean("userDao");
            
            // 2 执行方法
            userDao.addUser();
        }
    }

    输出:

    前置通知 :模拟执行权限检查...,目标类是:com.itheima.jdk.UserDaoImpl@2893de87,被织入增强处理的目标方法为:addUser
    环绕开始:执行目标方法之前,模拟开启事务...
    添加用户
    最终通知:模拟方法结束后的释放资源...
    环绕结束:执行目标方法之后,模拟关闭事务...
    后置通知:模拟记录日志...,被织入增强处理的目标方法为:addUser

    在目标方法里加入int i=10/0;重新运行,发生异常

    输出:

    前置通知 :模拟执行权限检查...,目标类是:com.itheima.jdk.UserDaoImpl@2893de87,被织入增强处理的目标方法为:addUser
    环绕开始:执行目标方法之前,模拟开启事务...
    添加用户
    最终通知:模拟方法结束后的释放资源...
    异常通知:出错了/ by zero

    3.42 基于注解的声明式AspectJ

    基于XML的声明式AspectJ存在的代码臃肿的缺点,为此,AspectJ框架为AOP的实现提供了一套注解。

    package com.itheima.aspectj.annotation;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    /**
     * 切面类,在此类中编写通知
     */
    @Aspect
    @Component
    public class MyAspect {
        
        // 定义切入点表达式
        @Pointcut("execution(* com.itheima.jdk.*.*(..))")
        
        // 使用一个返回值为void、方法体为空的方法来命名切入点
        private void myPointCut(){}
        
        // 前置通知
        @Before("myPointCut()")
        public void myBefore(JoinPoint joinPoint) {
            System.out.print("前置通知 :模拟执行权限检查...,");
            System.out.print("目标类是:"+joinPoint.getTarget() );
            System.out.println(",被织入增强处理的目标方法为:"
                           +joinPoint.getSignature().getName());
        }
        
        // 后置通知
        @AfterReturning(value="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("最终通知:模拟方法结束后的释放资源...");
        }
    }
    <?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-4.3.xsd
      http://www.springframework.org/schema/aop 
      http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
      http://www.springframework.org/schema/context 
      http://www.springframework.org/schema/context/spring-context-4.3.xsd">
      
          <!-- 指定需要扫描的包,使注解生效 -->
          <context:component-scan base-package="com.itheima" />
          
          <!-- 启动基于注解的声明式AspectJ支持 -->
          <aop:aspectj-autoproxy />
    </beans>
    package com.itheima.aspectj.annotation;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import com.itheima.jdk.UserDao;
    // 测试类
    public class TestAnnotationAspectj {
        public static void main(String args[]) {
            String xmlPath = "com/itheima/aspectj/annotation/applicationContext.xml";
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
            
            // 1 从spring容器获得内容
            UserDao userDao = (UserDao) applicationContext.getBean("userDao");
            
            // 2 执行方法
            userDao.addUser();
        }
    }

    输出同上。

    注解套路:

    (1)先引入context约束信息:

    xmlns:context="http://www.springframework.org/schema/context"

    http://www.springframework.org/schema/context 

    http://www.springframework.org/schema/context/spring-context-4.3.xsd">

    (2)配置bean的时候:

    使用context命名空间,在配置文件中开启相应的注解处理器:<context:annotation-config>

    然后扫描包,使注解生效:<context:componet-scan base-package="Bean所在的包路径">

    (2)如果是关于切面的基于注解的声明式AspectJ的时候:

    直接扫包,使注解生效:<context:componet-scan base-package="Bean所在的包路径">

    然后启动基于注解的声明式AspectJ支持:<aop:aspectj-autoproxy />

    注意:如果在同一连接点有多个通知需要执行,那么在同一切面中,目标方法之前的前置通知和环绕通知的执行顺序是未知的,目标方法之后的后置通知和环绕通知的执行顺序也是未知的。

  • 相关阅读:
    《那些年啊,那些事——一个程序员的奋斗史》——29
    《那些年啊,那些事——一个程序员的奋斗史》——33
    《那些年啊,那些事——一个程序员的奋斗史》——34
    《那些年啊,那些事——一个程序员的奋斗史》——36
    《那些年啊,那些事——一个程序员的奋斗史》——31
    抽像类和接口的区别
    编码规范
    网站色彩搭配技术.
    读取(写入)配置文件
    异步调用和多线程调用
  • 原文地址:https://www.cnblogs.com/shoulinniao/p/10887453.html
Copyright © 2020-2023  润新知