使用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>大多用于日志,缓存。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容