spring(三):springAOP的各种织入方式一览 - 神奇的光头强

时间:2024-03-12 11:08:41

spring(三):springAOP的各种织入方式一览

前言:

上一篇简单使用反射和jdk的动态代理模拟了AOP工作原理。在这里将讲阐述AOP如何把代码织入到目标对象的方法上。


一:这里介绍一下AOP一些名词概念

(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知。

(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用。这就是目标对象的方法。

(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,
    afterThrowing,around。这些就是需要织入到连接点中的代码。

(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式

(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,
    也可以是CGLIB代理,前者基于接口,后者基于子类

(6)advisor:增强器,用来筛选类中的哪些方法是我们的连接点(哪些方法需要被拦截).

  (7)wave:织入,把切面/切面类和目标类的动态接入。           

                         

 

二:Advice(通知)

AOP的通知有before前置通知。after后置通知,无论是否成功返回都会执行。afterReturning后置通知,只有成功返回后才会执行。

afterThrowing抛出异常通知,顾名思义,只有抛出异常的时候才会执行。around环绕通知,简单来说就是集合了before和after的

功能。

这里基于xml文件实现把切面类和目标类动态接入。

(1)pojo类:Account.java

package com.cnblogs.aop.pojo;
/**
 * 简单模拟银行账户
 *
 */
public class Account {
    // 卡号
    private Long id;
    // 姓名    
    private String name;
    //余额
    private double balance;
    
    public Account() {
        super();
        // TODO Auto-generated constructor stub
    }
    
    public Account(Long id, String name, double balance) {
        super();
        this.id = id;
        this.name = name;
        this.balance = balance;
    }

   // get和set方法
}

 

(2)目标类:这里使用的是JDK的接口代理模式,所以需要准备一个接口,和接口的实现类,即目标类。

package com.cnblogs.aop.service;

public interface BankService {
    /**
     * 存钱
     * @param money
     */
    public Boolean save(double money) throws Exception;
    
    /**
     * 取钱
     * @param money
     * @return
     */
    public boolean withdraw(double money) throws Exception;
}

 

package com.cnblogs.aop.service.impl;

import com.cnblogs.aop.pojo.Account;
import com.cnblogs.aop.service.BankService;

public class BankServiceImpl implements BankService {
    
    private Account account;
    
    public Account getAccount() {
        return account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

    @Override
    public Boolean save(double money) throws Exception {
        System.out.println("存钱: " + money);
        account.setBalance(account.getBalance() + money);
        return true;
    }

    @Override
    public boolean withdraw(double money) throws Exception{
        System.out.println("取钱: " + money);
        if(account.getBalance() < money) {
            throw new Exception("余额不足");    
        }
        account.setBalance(account.getBalance() - money);
        return true;
    }

}

 

(3):目标类准备好以后,就要准备切面类了。这个切面类直接集合了所有的通知类型。

package com.cnblogs.aop.aspect;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;

/**
 * 切面类
 * 实现了前置通知 MethodBeforeAdvice
 * 成功返回才执行的后置通知 
 * 环绕通知
 * 抛出异常才会执行的通知
 * 
 *
 */
public class MyLogger implements  MethodBeforeAdvice,AfterReturningAdvice,
        MethodInterceptor,ThrowsAdvice{
    

    /**
     * 前置通知
     * @param method 连接点
     * @param args 连接点参数
     * @param target 目标对象
     */
    @Override
    public void before(Method mehtod, Object[] args, Object target) throws Throwable {
        // 添加日志功能
        System.out.println("前置通知: " + new SimpleDateFormat().format(new Date()));
    }
    
    /**
     * 后置通知会接收到连接点的返回值,连接点返回值必须为引用类型,否则
     * 报错:org.springframework.aop.AopInvocationException: 
     * Null return value from advice does not match primitive return type for
     * @param returnValue 连接点的返回值【如果有的话】
     * @param method 连接点
     * @param args 连接点方法参数
     * @param target 目标对象【委托对象】
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        // 添加日志功能
        System.out.println("成功返回才执行的后置通知: " + new SimpleDateFormat().format(new Date()));    
    }
    
    /**
     * 环绕通知
     * 在调用连接点前为前置通知
     * 在调用连接点后为后置通知
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("环绕通知之前置: " + 
                    new SimpleDateFormat().format(new Date()));    
        // 调用连接点
        invocation.proceed();
        
        System.out.println("环绕通知之后置: " + 
                new SimpleDateFormat().format(new Date()));    
        return null;
    }
    
    /**
     * 抛出异常才会执行的通知比较特殊,它的接口类体为空,
     * 只起一个标记作用。
     * 不过会默认调用afterThrowing方法作为通知。
     * 该方法重载,有4个参数的和1个参数的
     * 当两个重载方法都存在时优先调用4个参数的
     */
    public void afterThrowing(Method method, Object[] args, Object target,Exception e) {
        System.out.println("抛出异常才会执行的通知(4参): " + 
                e.toString());    
    }

    public void afterThrowing(Exception e) {
        System.out.println("抛出异常才会执行的通知(1参): " + 
                e.toString());    
    }

}

 

(4):织入

advice.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: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/context
           http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
    <!-- 银行账号 -->
    <bean name="account" class="com.cnblogs.aop.pojo.Account">
        <property name="id" value="6217" />
        <property name="name" value="jack" />
        <property name="balance" value="1000.00" />
    </bean>
    
    <!-- 目标对象 -->
    <bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
        <property name="account" ref="account" />
    </bean>
    
    <!-- 切面对象 -->
    <bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
    </bean>
    
    <!-- 生成代理对象
          这里使用的是spring的一个代理对象工厂类产生的
      -->
    <bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 注入目标对象 -->
        <property name="target" ref="service"></property>

        <!-- 注入目标对象所实现的接口 可以有多个接口,基于JDK的接口代理 -->
        <property name="proxyInterfaces">
            <list>
                <value>com.cnblogs.aop.service.BankService</value>
            </list>
        </property>

        <!-- 注入advice -->
        <property name="interceptorNames">
            <list>
                <value>log</value>
            </list>
        </property>

</bean>
</beans>

 

(5):测试  

package com.cnblogs.aop.jtest;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.cnblogs.aop.service.BankService;

@SuppressWarnings("resource")
public class AOPTest {
    
    @Test
    public void advice(){
        try {
            // 获取springIOC容器
            String path = "com/cnblogs/aop/aspect/advice.xml";
            
            ApplicationContext container = new ClassPathXmlApplicationContext(path);
            
            // 从容器中获取代理对象
            BankService proxy = (BankService) container.getBean("proxy");
            // 执行目标对象中的方法
//            proxy.save(1000.0);
            
            // 测试通知会对那些方法起作用
//            proxy.getClass(); // 无
//            proxy.toString(); // 有
            
            // 测试抛出异常通知
//            proxy.withdraw(5000.0);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

1.环绕通知的前置优先前置通知输出,后置通知优先环绕通知的后置输出。

环绕通知之前置: 19-5-28 下午7:29
前置通知: 19-5-28 下午7:29
存钱: 1000.0
成功返回才执行的后置通知: 19-5-28 下午7:29
环绕通知之后置: 19-5-28 下午7:29

2.通知对那些方法起作用?

  单独调用getClass()方法时没有通知输出,toString()方法通知有输出。去java.lang.Objcet查找两个方法,

public final Class<?> getClass(),public String toString().简单对比发现只要是非final类型修饰的,通过代理对象调用

目标对象方法时都会把通知织入。

有时候我们希望目标对象的某些方法被织入,其它方法不想被织入,当时又不想用final修饰那些不想被织入的方法该

怎么办?增强器可以起到这个效果,筛选需要的方法织入。

 

三:Advisor增强器

增强器可以筛选目标类中那些方法被织入。

 advisor.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: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/context
           http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
    <!-- 银行账号 -->
    <bean name="account" class="com.cnblogs.aop.pojo.Account">
        <property name="id" value="6217" />
        <property name="name" value="jack" />
        <property name="balance" value="1000.00" />
    </bean>
    
    <!-- 目标对象 -->
    <bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
        <property name="account" ref="account" />
    </bean>
    
    <!-- 切面对象 -->
    <bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
    </bean>
    
    <!-- 配置增强器的bean对象 -->
    <bean name="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!-- 注入advice -->
        <property name="advice" ref="advice"/>
        <!-- 注入需要被拦截的目标对象中的方法(连接点) -->
        <property name="patterns">
            <list>
                <value>.*save</value>
                <value>.*withdraw</value>
            </list>
        </property>
    </bean>
    
    <!-- 生成代理对象
          这里使用的是spring的一个代理对象工厂类产生的
      -->
    <bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 注入目标对象 -->
        <property name="target" ref="service"></property>

        <!-- 注入目标对象所实现的接口 可以有多个接口,基于JDK的接口代理 -->
        <property name="proxyInterfaces">
            <list>
                <value>com.cnblogs.aop.service.BankService</value>
            </list>
        </property>

        <!-- 注入advice -->
        <property name="interceptorNames">
            <list>
                <value>advisor</value>
            </list>
        </property>
  </bean>
</beans>

补充:与advice.xml文件对比,发现只是多了一个org.springframework.aop.support.RegexpMethodPointcutAdvisor类的bean对象,该类就是增强器,

里面需要注入切面类的bean对象,以及指定那些方法被织入。

其次在生成代理对象的bean标签中,注入advice是注入增强器。

 

测试:

@Test
    public void advisor(){
        try {
            // 获取springIOC容器
            String path = "com/cnblogs/aop/advisor/advisor.xml";
            ApplicationContext container = new ClassPathXmlApplicationContext(path);
            
            // 从容器中获取代理对象
            BankService proxy = (BankService) container.getBean("proxy");
            
//            proxy.toString();
//            proxy.save(1000);
            
            proxy.getClass();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 

 四:AutoProxy,自动代理

 如果目标类不止一个,为多个目标类生成代理对象需要配置多个

<bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">...... </bean>,

自动代理可以用很少的xml配置给目标对象生成代理对象。自动代理用到增强器的功能。

autoProxy.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: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/context
           http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
    <!-- 银行账号 -->
    <bean name="account" class="com.cnblogs.aop.pojo.Account">
        <property name="id" value="6217" />
        <property name="name" value="jack" />
        <property name="balance" value="1000.00" />
    </bean>
    
    <!-- 目标对象 -->
    <bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
        <property name="account" ref="account" />
    </bean>
    
    <!-- 切面对象 -->
    <bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
    </bean>
    
    <!-- 配置增强器的bean对象 -->
    <bean name="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!-- 注入advice -->
        <property name="advice" ref="log"/>
        <!-- 注入需要被拦截的目标对象中的方法(连接点) -->
        <property name="patterns">
            <list>
                <value>.*save</value>
                <value>.*withdraw</value>
            </list>
        </property>
    </bean>
    
    <!-- 配置自动代理对象 -->
    <bean name="proxy" 
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
    </bean>
</beans>

 

1.不能通过proxy拿代理对象,通过目标对象的名字拿代理对象。

2.当前xml文件中一定要有一个增强器advisor,配置自动代理对象不需要注入任何东西。

3.不管目标对象是否实现了一个或多接口,自动代理的方式都能够为它产生代理对象(CGLib的方式)。

4.如果目标对象有增强器中筛选的方法,将来调用方法的时候会动态织入。

 

测试:

@Test
    public void autoProxy(){
        try {
            // 获取springIOC容器
            String path = "com/cnblogs/aop/autoProxy/autoProxy.xml";
            ApplicationContext container = new ClassPathXmlApplicationContext(path);
            
            BankService target = (BankService) container.getBean("service");
            target.save(1000.0);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 结果:

环绕通知之前置: 19-5-29 下午4:22
前置通知: 19-5-29 下午4:22
存钱: 1000.0
成功返回才执行的后置通知: 19-5-29 下午4:22
环绕通知之后置: 19-5-29 下午4:22

 

2.AutoProxyByName 通过名字进行自动代理

虽然自动代理可以很方便的给xml文件中的目标对象设置对应的代理对象,但是并不是xml文件中的所有对象都是我们的目标对象,

我们更想希望可以进一步筛选出某几个对象为我们的目标对象。

autoProxyByName.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: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/context
           http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
    <!-- 银行账号 -->
    <bean name="account" class="com.cnblogs.aop.pojo.Account">
        <property name="id" value="6217" />
        <property name="name" value="jack" />
        <property name="balance" value="1000.00" />
    </bean>
    
    <!-- 目标对象 -->
    <bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
        <property name="account" ref="account" />
    </bean>
    
    <!-- 切面对象 -->
    <bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
    </bean>
    
    <!-- 配置增强器的bean对象 -->
    <bean name="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!-- 注入advice -->
        <property name="advice" ref="log"/>
        <!-- 注入需要被拦截的目标对象中的方法(连接点) -->
        <property name="patterns">
            <list>
                <value>.*save</value>
                <value>.*withdraw</value>
            </list>
        </property>
    </bean>
    
    <!-- 配置代理对象 -->
    <!-- 这里使用自动代理的方式 autoproxybyname -->
    <bean name="proxy" 
            class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <!-- 注入需要被代理的对象名字 -->
           <property name="beanNames">
               <list>
                <!-- value中填写bean标签的id或name属性值 -->
                   <value>service</value>
               </list>
           </property>
           <!-- 注入advice或者advisor -->
           <property name="interceptorNames">
               <list>
                   <value>advisor</value>
               </list>
           </property>
     </bean>
</beans> 

使用byName自动代理的时候需要注意的方面:

 

             1.当前的配置里面"有没有"advisor的配置"都没关系"

 

             2.不管目标对象是否实现了一个或多接口,自动代理的方式都能够为它产生代理对象.

 

             3.从spring容器中拿代理对象的时候,需要通过目标对象的名字来拿。

 

五:使用<aop>标签完成织入功能

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

除了返回类型模式(ret-type-pattern),名字模式(name-pattern),参数模式(param-pattern)是必须的,

其它模式都是可选的

ret-type-pattern: \' * \'代表匹配任意返回类型,java.lang.String匹配String返回类型。

name-pattern: 名字模式匹配的是方法名。 你可以使用 * 通配符作为所有或者部分命名模式。

param-pattern:参数匹配, \' () \'匹配无参,\' (*) \'匹配一个参数,‘(..)’匹配任意参数(0或多个),

\' (*,String) \' 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个必须是String类型。

下面给出一些常见切入点表达式的例子。 

    1)任意包下的任意类中的公共方法的执行: 

                  execution(public * *(..))

    2)任何一个以“set”开始的方法的执行: 

                  execution(* set*(..))

    3)BankService接口的任意方法的执行:   

                  execution(* com.cnblogs.service.BankService.*(..))

    4)定义在service包里的任意方法的执行: 

                  execution(* com.cnblogs.service.*.*(..))

    5)定义在service包或者子包里的任意方法的执行: 

                  execution(* com.cnblogs.service..*.*(..))

 

1.第一种方式使用<aop>标签

<?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:context="http://www.springframework.org/schema/context"
    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/context
           http://www.springframework.org/schema/context/spring-context-4.3.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-4.3.xsd ">
    
    <!-- 银行账号 -->
    <bean name="account" class="com.cnblogs.aop.pojo.Account">
        <property name="id" value="6217" />
        <property name="name" value="jack" />
        <property name="balance" value="1000.00" />
    </bean>
    
    <!-- 目标对象 -->
    <bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
        <property name="account" ref="account" />
    </bean>
    
    <!-- 切面对象 -->
    <bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
    </bean>
    
    <!-- 配置aop的代理  也启用了自动代理的功能-->
    <aop:config>
        <!-- 定义一个切入点 -->
        <aop:pointcut 
            expression="execution(public * com.cnblogs.aop.service.BankService.save(*))" 
            id="save"/>
        <aop:pointcut 
            expression="execution(public * com.cnblogs.aop.service.BankService.withdraw(*))" 
            id="withdraw"/>

        <!-- 定义哪一个advice在哪一个切入点上面起作用 -->
        <aop:advisor advice-ref="log" pointcut-ref="save"  />
        <aop:advisor advice-ref="log" pointcut-ref="withdraw"  />
    </aop:config>

</beans>

 

注意:

                  1.从spring容器中拿代理对象的时候也是要用目标对象的名字来拿。

                   2.没有实现任何接口的目标对象也能产生代理对象。

 

测试:

@Test
    public void aop1(){
        try {
            // 获取springIOC容器
            String path = "com/cnblogs/aop/aopConfig/config1.xml";
            ApplicationContext container = new ClassPathXmlApplicationContext(path);
            
            BankService target = (BankService) container.getBean("service");
            target.save(1000.0);
            target.withdraw(20000.0);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 

环绕通知之前置: 19-5-29 下午7:06
前置通知: 19-5-29 下午7:06
存钱: 1000.0
成功返回才执行的后置通知: 19-5-29 下午7:06
环绕通知之后置: 19-5-29 下午7:06
环绕通知之前置: 19-5-29 下午7:06
前置通知: 19-5-29 下午7:06
取钱: 20000.0
抛出异常才会执行的通知(1参): java.lang.Exception: 余额不足

 

2.第二种方式使用aop

在一个切面类中定个多个方法,根据xml文件的配置每个方法都可以织入到切入点的不同位置,并且advice是在aop的标签中进行配置,

不需要再写对应的advice类了。

切面类:

package com.cnblogs.aop.aopConfig;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 切面类,这里包含了多个切面方法,可以把每个切面方法织入到不同切入点的不同位置
 * 
 */
public class XmlHandler {
    public void beforeTest(JoinPoint p){
        System.out.println(p.getSignature().getName()+" before...");
    }
    
    
    public void afterTest(JoinPoint p){
        System.out.println(p.getSignature().getName()+" after...");
    }
    
    public void afterReturningTest(JoinPoint p){
        System.out.println(p.getSignature().getName()+" afterReturning");
    }
    
    //在和aroundAdvice结合的时候,这个方法一定要加上这个ProceedingJoinPoint类型的参数
    public Object aroundTest(ProceedingJoinPoint pjp)throws Throwable{
        //JoinPoint对象不能调用连接点所表示的方法 
        //ProceedingJoinPoint能调用连接点所表示的方法 pjp.proceed()
        System.out.println(pjp.getSignature().getName()+" is start..");
        
        //调用目标对象中的方法
        Object obj = pjp.proceed();
        
        System.out.println(pjp.getSignature().getName()+" is end..");
        return obj;
    }
    
    public void throwingTest(JoinPoint p,Exception ex){
        System.out.println(p.getSignature().getName()+" is throwing..."+ex.getMessage());
    }
}

 

 

<?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:context="http://www.springframework.org/schema/context"
    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/context
           http://www.springframework.org/schema/context/spring-context-4.3.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-4.3.xsd ">
    
    <bean name="account" class="com.cnblogs.aop.pojo.Account">
        <property name="id" value="6217" />
        <property name="name" value="jack" />
        <property name="balance" value="1000.00" />
    </bean>
    
    <!-- 目标对象 -->
    <bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
        <property name="account" ref="account" />
    </bean>
    
    <!-- 切面对象 -->
    <bean name="log" class="com.cnblogs.aop.aopConfig.XmlHandler">
    </bean>
    
    <!-- 配置aop的代理 -->
    <aop:config>
        <!-- 定义切入点名为myPointCut -->
        <aop:pointcut expression="execution(public * com.cnblogs.aop.service.*.*(..))" 
                    id="myPointCut"/>
        
        <!-- 定义切面类 以及需要使用的advice -->
        <aop:aspect id="aspect" ref="log">
            <!-- before表示会把切面类log中的beforeTest方法织入到名字
                   叫myPointCut的切入点前面 
            -->
            <aop:before method="beforeTest" pointcut-ref="myPointCut"/>

            <!-- after表示不管方法是否正常结束都会起作用 -->
            <aop:after method="afterTest" pointcut-ref="myPointCut"/>

            <!-- after-returning表示方法正常结束才会起作用(抛异常时候不起作用) -->
            <aop:after-returning method="afterReturningTest" pointcut-ref="myPointCut"/>

            <aop:around method="aroundTest" pointcut-ref="myPointCut"/>

            <!-- throwing="ex"表示throwingTest方法中接收异常对象的名字一定要是ex -->
            <aop:after-throwing method="throwingTest" pointcut-ref="myPointCut" throwing="ex"/>

        </aop:aspect>
    </aop:config>
</beans>

 

@Test
    public void aop2(){
        try {
            // 获取springIOC容器
            String path = "com/cnblogs/aop/aopConfig/config2.xml";
            ApplicationContext container = new ClassPathXmlApplicationContext(path);
            
            BankService target = (BankService) container.getBean("service");
            target.save(1000.0);
//            target.withdraw(20000.0);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

save before...
save is start..
存钱: 1000.0
save is end..
save afterReturning
save after...

 

 六:使用注解配置AOP

 1.定义切面类:

package com.cnblogs.aop.annotation;

import java.text.SimpleDateFormat;
import java.util.Date;

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;

@Component
@Aspect
public class AnnotationHandler {
 
    /**
     * 在一个方法上面加上注解来定义切入点
     * 这个切入点的名字就是这个方法的名字
     * 这个方法本身不需要有什么作用
     * 这个方法的意义就是:给这个 @Pointcut注解一个可以书写的地方
     * 因为注解只能写在方法、属性、类的上面,并且方法名作为切入点的名字
     */
    @Pointcut("execution(public * com.cnblogs.aop.service.impl.*.*(..))")
    public void myPoint(){}
    
    /**
     * 前置通知
     * 如果需要用到Joinpoint,可以在方法参数加上
     * @param j
     */
    @Before("myPoint()")
    public void beforeTest(){
        System.out.println("前置通知: " + new SimpleDateFormat().format(new Date()));
    }
    
    /**
     * 最终通知
     */
    @After("myPoint()")
    public void afterTest(){
        System.out.println("最终通知: " + new SimpleDateFormat().format(new Date()));
    }
    
    /**
     * 环绕通知
     * @throws Throwable 
     */
    @Around("myPoint()")
    public void aroundTest(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("环绕通知之前置: " + 
                new SimpleDateFormat().format(new Date()));    
        
        //调用连接点的方法去执行
        Object obj = pjp.proceed();
        
        System.out.println("环绕通知之后置: " + 
                new SimpleDateFormat().format(new Date()));    
    }
    
    /**
     * 成功返回才会执行的后置通知
     */
    @AfterReturning("myPoint()")
    public void afterReturningTest(){
        System.out.println("成功返回才执行的后置通知: " + new SimpleDateFormat().format(new Date()));    
    }
    
    /**
     * 抛出异常通知
     */
    @AfterThrowing(value="myPoint()",throwing="ex")
    public void throwTest(Exception ex){
        System.out.println("抛出异常才会执行的通知(1参): " + 
                ex.toString());    
    }
}

 

 

2.在BankServiceImpl上加 @Service("service") 注解,在account成员变量加 @Autowired注解。

在Account上加  @Component @Scope("prototype")。

 

3.在xml文件中加上

   <aop:aspectj-autoproxy/>
    <context:component-scan base-package="com.cnblogs.aop.pojo"/>
    <context:component-scan base-package="com.cnblogs.aop.service.*"/>
    <context:component-scan base-package="com.cnblogs.aop.annotation"/>

 

     <context:component-scan base-package=" " />

表示SpringIOC容器会扫描此包下加了@Component,@Service注解的类,并放入IOC容器。在上上篇随便springIOC的三种注入方式

有介绍。

 

4.最后一步就是测试了

@Test
    public void annotation(){
        try {
            // 获取springIOC容器
            String path = "com/cnblogs/aop/annotation/annotation.xml";
            ApplicationContext container = new ClassPathXmlApplicationContext(path);
            
            BankService service = (BankService) container.getBean("service");
//            service.getAccount().setBalance(10000.0);
            
            service.save(3000.0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 

环绕通知之前置: 19-5-29 下午8:41
前置通知: 19-5-29 下午8:41
存钱: 3000.0
环绕通知之后置: 19-5-29 下午8:41
最终通知: 19-5-29 下午8:41
成功返回才执行的后置通知: 19-5-29 下午8:41