使用Spring实现AOP

基于annotation的AOP

Spring使用的AOP注解分为三个层次:

1、@Aspect放在类头上,把这个类作为一个切面。
2、@Pointcut放在方法头上,定义一个可被别的方法引用的切入点表达式。
3、5种通知

  • @Before,前置通知,放在方法头上。
  • @After,后置【finally】通知,放在方法头上。
  • @AfterReturning,后置【try】通知,放在方法头上,使用returning来引用方法返回值,即可以访问到方法的返回值。
  • @AfterThrowing,后置【catch】通知,也叫抛出通知,放在方法头上,可以访问到异常对象,可以指定在出现特定异常时在执行通知代码。
  • @Around,环绕通知,放在方法头上,这个方法要决定真实的方法是否执行,而且必须有返回值

要想实现基于annotation的AOP,首先应该在配置文件打开annotation AOP的支持。配置如下:

<!--启用Spring对@AspectJ的支持-->
<aop:aspectj-autoproxy />
<!--使Spring可以扫描到aop类的annotation-->
<!--这里我的aop的所有类都放在了com.AOPExercise.aop包下-->
<context:component-scan base-package="com.AOPExercise.aop" />

然后我们就可以在代码中使用AOP的annotation:

@Component  
@Aspect  //声明这个类是一个切面类
public class LogAspect {  
  
    /** 
     * 定义Pointcut(在哪里做的集合)
     * 一个Pointcut定义由Pointcut表示式和Pointcut签名组成
     */  
    //Pointcut表示式
    @Pointcut("execution(public * com.service.impl..*.*(..))")
    //Point签名,此方法不能有返回值,该方法只是一个标示 。
    public void recordLog() {}  
  
    @AfterReturning(pointcut = "recordLog()")  //引用命名切入点
    public void simpleAdvice() {  
        LogUtil.info("AOP后处理成功");  
    }  
  
    @Around("recordLog()")  
    public void aroundLogCalls(ProceedingJoinPoint jp) throws Throwable {  
        LogUtil.info("正常运行");
        jp.proceed();  //执行程序
        LogUtil.info("运行结束");
    }  
  
    @Before("recordLog()")  
    //如果希望获取相应的调用信息,可以通过JoinPoint这个参数进行传递
    public void before(JoinPoint jp) {  
        String className = jp.getThis().toString();  
        String methodName = jp.getSignature().getName(); // 获得方法名  
        LogUtil.info("位于:" + className + "调用" + methodName + "()方法-开始!");  
        Object[] args = jp.getArgs(); // 获得参数列表  
        if (args.length <= 0) {  
            LogUtil.info("====" + methodName + "方法没有参数");  
        } else {  
            for (int i = 0; i < args.length; i++) {  
                LogUtil.info("====参数  " + (i + 1) + ":" + args[i]);  
            }  
        }  
        LogUtil.info("=====================================");  
    }  
  
    @AfterThrowing("recordLog()")  
    public void catchInfo() {  
        LogUtil.info("异常信息");  
    }  
  
    @After("recordLog()")  
    public void after(JoinPoint jp) {  
        LogUtil.info("" + jp.getSignature().getName() + "()方法-结束!");  
        LogUtil.info("=====================================");  
    }  
}  

我们也可以直接在@Before、@Around中定义切点:

@Component("logAspect")   //让这个切面类被spring所管理
@Aspect                   //声明这个类是一个切面类
public class LogAspect{

       @Before("execution(* org.zyt.init.spring.dao.*.add*(..))||"+
               "execution(* org.zyt.init.spring.dao.*.delete*(..))||")
       public void logStart(){
          Logger.info("加入日志");
       }
}

带参数的Pointcut
  如果只要访问目标方法的参数,spring还提供了一种更简单的方法:我们可以在程序中使用args来绑定目标方法的参数。如果在一个args表达式中指定了一个或多个参数,则该切入点将只匹配具有对应形参的方法,且目标方法的参数值将被传入增强处理方法。下面以一个例子说明。

【示例】
UserController.java

@Controller
@RequestMapping("/user")
public class UserController {

    @Resource
    public UserService userService;

    @RequestMapping(value="/login", method= RequestMethod.POST)
    public String login(@RequestParam(required = true) String userName,
                                    @RequestParam(required = true) String password){
        String result = "login successfully!";
        return result;
    }
}

verifyUserAspect.java

@Aspect
@Component
public class verifyUserAspect{

    @Resource
    public UserDao userDao;
    @Resource
    public LogService logService;

    //可以通过“argNames”属性指定参数名
    @Pointcut("execution(public * com.AOPExercise.controller.UserController.*(String,String)) && args(userName,password)")
    public void userPointcut(String userName,String password){}

    @Around(value="userPointcut(userName,password)")
    public Object verifyUser(ProceedingJoinPoint pjp, String userName, String password) throws Throwable {
        Object result = null;
        User u = userDao.findUserByName(userName);
        if(u==null){
            result="login invalid!";
        }else{
            if(!u.getPassword().equals(password)){
                result="login invalid!";
                System.out.println("验证不合法1");
            }else{
                //记录用户登录日志
                SimpleDateFormat sdFormatter = new SimpleDateFormat("yyyy-MM-dd");
                String retStrFormatNowDate = sdFormatter.format(new Date(System.currentTimeMillis()));
                String message = "用户"+userName+"在"+retStrFormatNowDate+"登录";
                logService.addLog(message);
                //执行login()方法
                result = pjp.proceed();
            }
        }
        return result;
    }
}

请求结果
传输错误的密码结果如下:


传输正确的密码结果如下:

基于xml的AOP

beans.xml中AOP的配置:

<bean id="serviceAspect" class="com.myspring.app.aop.MyAdvice"/> //切面代码

<!--  配置事务传播特性 -->
<tx:advice id="TestAdvice" transaction-manager="transactionManager">
  <tx:attributes>
    <tx:methodname="save*" propagation="REQUIRED"/>
    <tx:methodname="del*" propagation="REQUIRED"/>
    <tx:methodname="update*" propagation="REQUIRED"/>
    <tx:methodname="add*" propagation="REQUIRED"/>
    <tx:methodname="find*" propagation="REQUIRED"/>
    <tx:methodname="get*" propagation="REQUIRED"/>
    <tx:methodname="apply*" propagation="REQUIRED"/>
  </tx:attributes>
</tx:advice>

<!--  配置参与事务的类 -->
<aop:config>
 <!-- 声明一个切面,并注入切面Bean(serviceAspect),相当于@Aspect -->
 <aop:aspect id="simpleAspect" ref="serviceAspect">
  <!-- 配置一个切入点(在哪里做),相当于@Pointcut -->
  <aop:pointcut id="simplePointcut" expression="execution(* com.test.testAda.test.model.service.*.*(..))"/>
  <!-- 配置通知,相当于@Before、@After、@AfterReturn、@Around、@AfterThrowing ,  
   pointcut-ref要和上面的一致,method里面是使用@Before、@After、@AfterReturn、@Around、@AfterThrowing方法的名称-->
  <aop:before pointcut-ref="simplePointcut" method="before"/>
  <aop:after pointcut-ref="simplePointcut" method="after"/>
  <aop:after-returning pointcut-ref="simplePointcut" method="afterReturn"/>
  <aop:after-throwing pointcut-ref="simplePointcut" method="afterThrow" throwing="ex"/>
  <!--定义向导,引起切点和通知-->
  <!--<aop:advisor pointcut-ref="allTestServiceMethod" advice-ref="TestAdvice"/>-->
 </aop:aspect>
</aop:config>

【说明】
(1)aop:pointcut标签配置切点,表示哪些位置要使用增强。
  由于是在Service中进行数据库业务操作,配的应该是包含那些作为事务的方法的Service类。首先应该特别注意的是id的命名,同样由于每个模块都有自己事务切面,所以我觉得初步的命名规则因为 all+模块名+ServiceMethod。而且每个模块之间不同之处还在于以下一句:

 expression="execution(...)"

织入点语法
1、 无返回值、com.zoy.dao.UserDaoImpl.save方法、参数为User

execution(public void com.zoy.dao.UserDaoImpl.save(com.model.User))

2、 任何包、任何类、任何返回值、任何方法的任何参数

execution(public * *(..))

3、 任何包、任何类、任何返回值、任何set开头方法的任何参数

//第一个*表示任意返回值
execution(* set*(..))

4、 任何返回值、com.zoy.service.AccountService类中的任何方法、任何参数

execution(* com.zoy.service.AccountService.*(..))

5、 任何返回值、com.zoy.service包中任何类中的任何方法、任何参数

execution(* com.zoy.service.*.*(..))

6、 任何返回值、com.zoy.service包中任何层次子包(..)、任何类、任何方法、任何参数

execution(* com.zoy.service..*.*(..))

7、 void 和 !void(非void)

execution(public void com.zoy.service..*.*(..))
execution(public !void com.zoy.service..*.*(..))

aop:pointcut标签也可以为pointcut配置参数。如下例所示:

 <aop:pointcut expression="execution(* cn.g.model.*.get*(..)) and args(arg1, arg2, arg3)" id="acut"/>

(2)<aop:before>、<aop:after>、<aop:after-returning>、 <aop:after-throwing>用来配置通知(增强),其中pointcut-ref指向作用的pointCut标签。
  我们也可以直接在<aop:before>、<aop:after>、<aop:after-returning>、 <aop:after-throwing>中直接配置pointcut而不用pointcut-ref。如下例所示:

<aop:before method="before" pointcut="execution(* cn.xxxx..*.*(..))"/>    

如果在aop:pointcut标签中配置了参数,且增强方法想要使用其中的参数的话,则用arg-names属性进行配置:

<bean class="cn.g.model.User" id="user">  
    <property name="username" value="zhangshan"></property>  
</bean>  
<bean id="aopTest" class="cn.g.aop.TestAop"></bean>  
<aop:config proxy-target-class="true">  
    <aop:pointcut expression="execution(* cn.g.model.*.get*(..)) and args(arg1, arg2, arg3)" id="acut"/>  
    <aop:aspect ref="aopTest">  
        <aop:before method="before" pointcut-ref="acut" arg-names="arg1, arg2, arg3"/>  
        <aop:after method="after" pointcut-ref="acut" arg-names="arg1, arg2, arg3"/>
        <!--配置另外的pointcut-->
        <aop:around method="around" pointcut="execution(* cn.g.model.*.get*(..))"/>  
        <aop:after-returning method="afterReturning" pointcut="execution(* cn.g.model.*.get*(..))" returning="arg" arg-names="arg"/>  
        <aop:after-throwing method="afterThrowing" pointcut="execution(* cn.g.model.*.get*(..))"  throwing="arg" arg-names="arg"/>  
    </aop:aspect>  
</aop:config>  

(3)aop:advisor 与 aop:aspect的区别
  在面向切面编程时,我们会使用< aop:aspect>;在进行事务管理时,我们会使用< aop:advisor>。那么,对于< aop:aspect>与< aop:advisor>的区别,具体是怎样的呢?
  其实Adivisor只持有一个Pointcut和一个advice,而Aspect可以多个Pointcut和多个advice,所以Adivisor是一种特殊的Aspect。

1、实现方式不同
  < aop:aspect>定义切面时,只需要定义一般的bean就行,而定义< aop:advisor>中引用的通知时,通知必须实现Advice接口。下面我们举例说明。
  首先,我们定义一个接口Sleepable和这个接口的实现Human,代码如下:

public interface Sleepable {
    public void sleep();
}

public class Human implements Sleepable {
    @Override
    public void sleep() {
        System.out.println("我要睡觉了!");
    }
}

下面是< aop:advisor>的实现方式:

//定义通知
public class SleepHelper implements MethodBeforeAdvice,AfterReturningAdvice{
    @Override
    public void before(Method arg0, Object[] arg1, Object arg2)
            throws Throwable {
        System.out.println("睡觉前要脱衣服!");
    }

    @Override
    public void afterReturning(Object arg0, Method arg1, Object[] arg2,
            Object arg3) throws Throwable {
        System.out.println("起床后要穿衣服!");
    }
}

//aop配置
<bean id="sleepHelper" class="com.ghs.aop.SleepHelper"></bean>

<aop:config>
    <aop:pointcut expression="execution(* *.sleep(..))" id="sleepPointcut"/>
    <aop:advisor advice-ref="sleepHelper" pointcut-ref="sleepPointcut"/>
</aop:config>

<bean id="human" class="com.ghs.aop.Human"/>

下面是< aop:aspect>的实现方式:

//定义切面
public class SleepHelper2{
    public void beforeSleep(){
        System.out.println("睡觉前要脱衣服!");
    }

    public void afterSleep(){
        System.out.println("起床后要穿衣服!");
    }
}

//aop配置
<bean id="sleepHelper" class="com.ghs.aop.SleepHelper"></bean>

<aop:config>
    <aop:aspect ref="sleepHelper">
     <aop:pointcut expression="execution(* *.sleep(..))" id="sleepPointcut"/>
     <aop:before pointcut-ref="sleepPointcut" method="beforeSleep"/>
     <aop:after pointcut-ref="sleepPointcut" method="afterSleep"/>
    </aop:aspect>
</aop:config>

<bean id="human" class="com.ghs.aop.Human"/>

测试代码如下:

public class TestAOP {
    public static void main(String[] args) {
        method1();
//      method2();
    }

    private static void method1() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
        Sleepable sleeper = (Sleepable) context.getBean("human");
        sleeper.sleep();
    }

    private static void method2() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
        Sleepable sleeper = (Sleepable) context.getBean("human");
        sleeper.sleep();
    }
}

2、使用场景不同
  <aop:advisor>大多用于事务管理。


  从上述中可知,aop:advisor标签中的advice-ref属性可以指向一个切面实现的bean,也可以指向一个<tx:advice>。
  当advice-ref属性指向一个切面实现的bean时,配置的是切面实现的增强;当advice-ref属性指向一个<tx:advice>时,<tx:advice/>标签会创建一个事务处理通知,即此时<aop:advisor>把我们所配置的事务管理和切点两部分属性整合起来作为整个事务管理,比如在事务前后加上begin()、commit()等方法。
  < aop:aspect>大多用于日志,缓存。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容