使用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的支持。配置如下:






然后我们就可以在代码中使用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;
    }
}

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

使用Spring实现AOP_第1张图片

传输正确的密码结果如下:
使用Spring实现AOP_第2张图片

基于xml的AOP

beans.xml中AOP的配置:

 //切面代码



  
    
    
    
    
    
    
    
  




 
 
  
  
  
  
  
  
  
  
  
 

【说明】
(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配置参数。如下例所示:

 

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

    

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

  
      
  
  
  
      
      
          
        
        
          
          
          
      
  

(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配置



    
    



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

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

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

//aop配置



    
     
     
     
    



测试代码如下:

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、使用场景不同
  大多用于事务管理。

使用Spring实现AOP_第3张图片

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

你可能感兴趣的:(使用Spring实现AOP)