Spring/AOP

切面编程,其实就是动态代理,使用反射动态的构造一个新的Class,新的Class包含旧的Class,可以在旧的Class执行前后和中间插入代码。
基于XML Schema的AOP:
AOP代理就是AOP框架通过代理模式创建的对象,Spring AOP默认首先使用JDK动态代理来代理目标对象,如果目标对象没有实现任何接口将使用CGLIB代理,如果需要强制使用CGLIB代理,对于Schema风格配置切面使用如下方式来指定使用CGLIB代理:

    <aop:config proxy-target-class="true">  
    </aop:config>  

例子:

    //目标接口
    package cn.javass.spring.chapter6.service;  
    public interface IHelloWorldService {  
        public void sayHello();  
    }  

    //目标接口实现
    package cn.javass.spring.chapter6.service.impl;  
    import cn.javass.spring.chapter6.service.IHelloWorldService;  
    public class HelloWorldService implements IHelloWorldService {  
        @Override  
        public void sayHello() {  
            System.out.println("============Hello World!");  
        }  
    }  

    //切面支持类
    package cn.javass.spring.chapter6.aop;  
    public class HelloWorldAspect {  
        //前置通知  
        public void beforeAdvice() {  
            System.out.println("===========before advice");  
        }  
        //后置最终通知  
        public void afterFinallyAdvice() {  
            System.out.println("===========after finally 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:aop="http://www.springframework.org/schema/aop"  
            xsi:schemaLocation="  
               http://www.springframework.org/schema/beans  
               http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
               http://www.springframework.org/schema/aop  
               http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">  
    </beans>  

    <bean id="helloWorldService"  class="cn.javass.spring.chapter6.service.impl.HelloWorldService"/>  

    <bean id="aspect" class="cn.javass.spring.chapter6.aop.HelloWorldAspect"/>  
    <aop:config>  
    <aop:pointcut id="pointcut" expression="execution(* cn.javass..*.*(..))"/>  
        <aop:aspect ref="aspect">  
            <aop:before pointcut-ref="pointcut"                    method="beforeAdvice"/>  
            <aop:after  pointcut="execution(* cn.javass..*.*(..))" method="afterFinallyAdvice"/>  
        </aop:aspect>  
    </aop:config>  

    //测试
    package cn.javass.spring.chapter6;  
    import org.junit.Test;  
    import org.springframework.context.ApplicationContext;  
    import org.springframework.context.support.ClassPathXmlApplicationContext;  
    import cn.javass.spring.chapter6.service.IHelloWorldService;  
    import cn.javass.spring.chapter6.service.IPayService;  
    public class AopTest {  
        @Test  
        public void testHelloworld() {  
            ApplicationContext ctx =  new ClassPathXmlApplicationContext("chapter6/helloworld.xml");  
            IHelloWorldService helloworldService =  
            ctx.getBean("helloWorldService", IHelloWorldService.class);  
            helloworldService.sayHello();  
        }  
    }  

其他后置通知,后置返回通知,后置异常通知,环绕通知等参考:http://jinnianshilongnian.iteye.com/blog/1418598

基于@AspectJ的AOP:
在Spring和SpringMVC的配置文件中启用切面,注意Spring和SpringMVC分别起用
Spring配置文件 applicationContext.xml(JDK本身提供的DynamicProxy):

<beans .....>
       <aop:aspectj-autoproxy/>
</beans>

SpringMVC配置文件 spring-mvc.xml(由CGLib提供):

<beans .....>
      <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

正如开篇提到,所谓切面编程就是动态代理,那么动态代理有两种方式:

  • JDK本身提供的DynamicProxy。JDK的动态代理是针对接口的,某个Class首先要实现某个接口,可以对接口方法进行代理。
  • CGLib提供的。CGLib是针对具体的Class的,不需要你的Object去implement某个接口。在SpringMVC配置中使用cglib进行代理,这里注意如果你的pom需要有cglib的依赖。

选择哪种代理:
一般在Service切入AOP,使用JDK本身的动态代理,即<aop:aspectj-autoproxy/>。因为按照习惯我们编写的Service都会继承接口。
在Controller切入AOP,使用CGLib,即<aop:aspectj-autoproxy proxy-target-class="true"/>。Controller一般不继承接口。

下面以在Service层前后打印日志为例:
servie层代码:

package com.tengj.demo.service
public interface UserService {
    public void sayHello(String name);
}

servie实现类代码:

package com.tengj.demo.service.impl;
@Service("userService")
public class UserServiceImpl implements UserService{
    @Override
    public void sayHello(String name) {
        System.out.println("hello,"+name);
    }
}

切点:

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component //注入依赖
@Aspect //该注解标示该类为切面类
public class LogAspect {
    @Pointcut("execution(* com.tengj.demo.service.impl.UserServiceImpl.*(..))")
    public void logAop(){}

    @Before("logAop() && args(name)")
    public void logBefore(String name){
        System.out.println(name+"前置通知Before");
    }

    /**
     * 定义返回值的型参名称为r,并传入切面
     */
    @AfterReturning(value = "logAop()", returning = "r")
    public void logAfterReturning(JoinPoint joinPoint, Object r) {
        log.info("方法调用结束 返回值:{}",r.toString());
    }

    @After("logAop() && args(name)")
    public void logAfter(String name){
        System.out.println(name+"后置通知After");
    }

    /**
     * 定义抛出类的型参名称为ex,并传入切面
     */
    @AfterThrowing(value = "logAop()", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        String message = (ex.getMessage() == null)?ex.getClass().getName():ex.getMessage();
        log.warn("方法抛出异常: {} ",message);
    }
}

上面方法index()就是我们定义的切点,表示在哪里切入AOP:


后边的Before和After就很好理解,就是以下方法在动态代理的方法前还是之后执行,除了@Before和@After还有@Around和@AfterThrowing、@AfterReturning等。
@Pointcut注解是为了定义切面内重用的切点,也就是说把公共的东西抽出来,定义了任意的方法名称logAop,这样下面用到的各种类型通知就只要写成例如:@Before("logAop() && args(name)")。也可以直接在其余注解中使用表达式例如:@Before(value = “execution(public*com.company.service.impl..*.*(..))”)
@Before("logAop() && args(name)")这里多出来个&& args(name)
,这个是用来传递参数的,定义要跟Service里的方法sayHello参数名称一样就可以。

applicationContext配置文件:

<?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:mvc="http://www.springframework.org/schema/mvc"
       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.1.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-4.1.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd"
       default-lazy-init="true">
    <context:component-scan base-package="com.tengj.demo"/>
    <mvc:resources location="/WEB-INF/pages/" mapping="/pages/**"/>
    <!-- 默认的注解映射的支持 -->
    <mvc:annotation-driven/>
    <!--启用AspectJ自动代理-->
    ***<aop:aspectj-autoproxy/>***
    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

参考:
http://www.jianshu.com/p/d35e46f27187
http://alanli7991.github.io/2016/10/21/%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B%E4%B8%89AspectJ%E4%B8%8EShiro%E4%B8%8D%E5%85%BC%E5%AE%B9%E5%92%8CSpring%E4%BA%8C%E6%AC%A1%E4%BB%A3%E7%90%86%E9%94%99%E8%AF%AF%E5%88%86%E6%9E%90/

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

推荐阅读更多精彩内容

  • 0.前言 本文主要想阐述的问题如下:什么动态代理(AOP)以及如何用JDK的Proxy和InvocationHan...
    SYFHEHE阅读 6,818评论 1 7
  • title: Spring_AOP源码分析date: 2016-11-03 01:15:11categories:...
    raincoffee阅读 5,692评论 2 36
  • 在理解Spring AOP以及理清它与Aspect和cglib之间关系之前,有很多基础工作要做,比如,先对代理模式...
    maxwellyue阅读 5,115评论 0 5
  • 1. Spring AOP介绍 AOP(Aspect-Oriented Programming),面向切面的编程,...
    郭寻抚阅读 5,572评论 1 11
  • 1、AOP concepts(AOP术语) Aspect/Advisors(切面)一个关注点的模块化,这个关注点可...
    codersm阅读 5,339评论 0 5