spring系列:八、spring aop开发

概述

  • aop(aspect oriented programming),面向切面编程。通过预编译方式和运行期动态代理实现程序维护的统一编程模式。利用aop可以对业务逻辑的各个部分进行隔离,降低业务各部分的耦合度。
  • aop是oop(面向对象编程)的延续
  • aop是一个概念,没有特定的语言实现。spring2.0之后,整合了aspectj第三方aop技术框架。
  • aspectj定义了aop语法,有一个专门的编译器用来生成遵守java字节编码规范的class文件。

功能

日志记录、性能统计、安全控制、事务处理、异常处理等

术语

  • 目标对象 target
    需要被增强的对象。spring aop是通过代理模式实现的,这个对象永远是指被代理的对象
  • 连接点 join point
    被具体拦截到的点。在spring中是指方法,因为spring只支持方法类型的连接点
  • 切入点 pointcut
    pointcut是指一组join point,这些join point通过某种逻辑关系集中起来。它定义了相应的advice将要发生的地方。
  • 通知 advice
    advice就是指拦截到join point之后要做的事情,分为前置通知、后置通知、异常通知、最终通知、环绕通知
  • 引介 introduction
    introduction是一种特殊的通知,在不修改类代码的前提下,introduction可以在运行期动态的为类添加一些方法和属性
  • 切面 aspect
    切入点pointcut + 通知advice
  • 织入 weaving
    weaving是一个过程,就是将切面aspect应用到目标对象target从而创建出aop代理对象的过程。织入weaving可以在编译器、类装载器、运行期进行。
    spring 采用动态织入,aspectj采用静态织入
  • 代理proxy
    一个类被aop织入增强后,就会产生一个结果代理类

aop实现

aop分为动态aop和静态aop
动态aop是指spring实现的aop,它是将切面代码动态织入到java类文件中。实现的技术:jdk提供的动态代理和cglib(动态字节码增强技术)两种技术。注意:如果目标对象有接口,spring优先使用jdk动态代理;如果目标对象没有接口,使用cglib动态代理
静态aop是指aspectj实现的aop,它是将切面代码直接编译到java类文件中。

动态代理

jdk动态代理

在运行期,在jvm内部动态生成class字节码对象
jdk动态代理,只针对于接口操作

public Object createProxy() {
    /**
     * 第一个参数:目标类的类加载器对象
     * 第二个参数:目标类的实现集合数组class[]
     * 第三个参数:invocationHandler,接口,它的作用是是代理实例的调用处理程序
     *
     * 返回值:一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
     */
    return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new
            InvocationHandler() {
        // 匿名内部类,增强部分
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("日志操作");
            return method.invoke(target, args);
        }
    });
}

cglib动态代理

cglib,开源,一个强大、高性能、高质量的代码生成类库。它可以在运行期扩展java类和实现java接口。cglib底层是使用一个小而快的字节码处理框架asm
注意:spring-core.jar已经集成了cglib和asm
注意2:cglib既可以为接口,也可以为类进行代理操作

public class CglibProxyFactory implements MethodInterceptor {

    /** 目标对象 */
    private Object target;

    /**
     * 使用构造方法传递目标对象
     */
    public CglibProxyFactory(Object target) {
        this.target = target;
    }

    /**
     * 创建代理对象
     */
    public Object createProxy() {
        // 创建Enhancer对象
        Enhancer enhancer = new Enhancer();

        // 传递目标对象
        enhancer.setSuperclass(target.getClass());

        // 设置回调操作
        enhancer.setCallback(this);

        // 返回值
        return enhancer.create();
    }

    /**
     * 增强操作
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("日志操作");
        return method.invoke(target, objects);
        /*return methodProxy.invokeSuper(o,objects);*/
    }
    
}

spring aop 传统编程

传统spring aop开发支持五种增强:
前置通知:org.springframework.aop.MethodBeforeAdvice
后置通知:org.springframework.aop.AfterReturningAdvice
环绕通知:org.aopalliance.intercept.MethodInterceptor
异常抛出通知:org.springframework.aop.ThrowsAdvice
引介通知:在目标类添加一些新的方法或者属性,org.aopalliance.intercept.IntroductionInterceptor

基于动态代理的经典aop开发

第一步:目标类

public class UserServiceImpl implements UserService {
    @Override
    public void login() {
        System.out.println("login···");
    }

    @Override
    public void register() {
        System.out.println("register···");
    }
}

第二步:增强

public class UserHelper implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor {
    /**
     * 前置通知
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("前置通知···");
    }

    /**
     * 通知后置
     */
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("后置通知···");
    }

    /**
     * 环绕通知
     */
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        System.out.println("环绕前···");
        Object value = mi.proceed();
        System.out.println("环绕后···");
        return value;
    }

}

配置

<!-- 经典的基于代理的aop开发 -->
<import resource="aop01.xml"/>
<!-- 目标target -->
<bean id="userService" class="cn.ade.service.impl.UserServiceImpl"></bean>

<!-- 通知advice -->
<bean id="userServiceAdvice" class="cn.ade.advice.UserHelper"></bean>

<!-- 切入点pointcut -->
<bean id="userServicePointCut" class="org.springframework.aop.support.NameMatchMethodPointcut">
    <property name="mappedNames">
        <list>
            <!-- 连接点 join point -->
            <value>login</value>
            <value>register</value>
        </list>
    </property>
</bean>

<!-- 切面:通知+切点 -->
<bean id="userServiceAspect" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    <!-- 通知 -->
    <property name="advice" ref="userServiceAdvice"/>
    <!-- 切点 -->
    <property name="pointcut" ref="userServicePointCut"/>
</bean>

<!-- 手动代理 -->
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!-- 目标 -->
    <property name="target" ref="userService"/>
    <!-- 切面 -->
    <property name="interceptorNames" value="userServiceAspect"/>
    <!-- 代理接口:有接口写接口,没有接口默认去找cjlib动态代理来实现 -->
    <property name="proxyInterfaces" value="cn.ade.service.UserService"/>
</bean>

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ProxyAopDepTest {

    @Resource(name = "userServiceProxy")       // 因为aop.xml中配置了两个userService,所以需要指定名称
    private UserService userService;

    @Test
    public void test() {
        userService.login();
        userService.register();
    }

}

基于aspectj切面的经典aop开发

<!-- 目标target -->
<bean id="userService" class="cn.ade.service.impl.UserServiceImpl"/>

<!-- 通知advice -->
<bean id="userServiceAdvice" class="cn.ade.advice.UserHelper"/>

<!--使用aop标签来声明切面和切点-->
<aop:config>
    <!--声明切点,也就是声明对哪些方法进行拦截-->
    <!-- 切点表达式 -->
    <aop:pointcut id="userServicePointCut" expression="execution(* cn.ade.service.UserService.*(..))"/>

    <!--声明传统aop的切面,只能包含一个切点和一个增强-->
    <aop:advisor advice-ref="userServiceAdvice" pointcut-ref="userServicePointCut"/>

    <!--aspectJ框架的切面,可以包含多个切点和增强-->
    <!--<aop:aspect></aop:aspect>-->
</aop:config>

spring 整合 aspectj 实现 aop

spring整合aspectj的aop开发支持六种增强:
前置通知
后置通知
环绕通知
异常抛出通知
引介通知
最终通知:不管异常抛出通知是否执行,最终通知都会执行

基于xml方式

  • 创建目标target
public class UserServiceImpl implements UserService {

    @Override
    public void login() {
        System.out.println("login···");
    }

    @Override
    public void register() {
        System.out.println("register···");
    }
}
  • 创建通知advice
public class UserHelper {

    // 使用前置通知完成日志记录、权限控制
    /**
     * 无参的前置通知
     */
    public void before() {
        System.out.println("前置通知(无参)···");
    }

    /**
     * 有参的前置通知
     */
    public void before1(JoinPoint jp) {
        System.out.println("拦截的目标类:" + jp.getSignature().getDeclaringTypeName());
        System.out.println("拦截的方法名称:" + jp.getSignature().getName());
        System.out.println("前置通知(有参)···");
    }

    /**
     * 无参的后置通知
     */
    public void afterReturning() {
        System.out.println("后置通知(无参)···");
    }

    /**
     * 有参的后置通知
     * 注意:需要在配置文件中声明val
     */
    public void afterReturning1(JoinPoint jp, Object val) {
        // val:目标方法的返回值
        System.out.println(val);
        System.out.println("后置通知(有参)···");
    }

    // 应用最多,可以完成日志记录、权限控制、性能监测、事务管理
    /**
     * 环绕通知
     */
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前···");
        Object val = pjp.proceed();     // 执行目标行为
        System.out.println("环绕后···");
        return val;
    }

    /**
     * 无参的异常抛出通知
     */
    public void afterThrowing() {
        System.out.println("无参的异常抛出通知···");
    }

    /**
     * 有参的异常抛出通知
     * 注意:需要在配置文件中声明ex
     */
    public void afterThrowing1(JoinPoint jp, Throwable ex) {
        System.out.println(ex);     // 接收抛出的异常
        System.out.println("有参的异常抛出通知···");
    }

    // 资源释放
    /**
     * 无参的最终通知
     */
    public void after() {
        System.out.println("无参的最终通知···");
    }

    /**
     * 有参的最终通知
     */
    public void after1(JoinPoint jp) {
        System.out.println(jp.getSignature().getDeclaringTypeName());
        System.out.println(jp.getSignature().getName());
        System.out.println("有参的最终通知···");
    }

}
  • 文件配置
<!-- 目标target -->
<bean id="userService" class="cn.ade.service.impl.UserServiceImpl"/>

<!-- 通知advice -->
<bean id="userServiceAdvice2" class="cn.ade.advice.UserHelper2"/>

<!--声明对aop进行配置-->
<!--使用proxy-target-class="true"来确定使用cglib实现代理,false使用proxy代理-->
<aop:config>
    <!--设置aspectj框架的切面-->
    <aop:aspect ref="userServiceAdvice2">
        <!--设置切入点-->
        <aop:pointcut id="loginPointCut" expression="execution(* cn.ade.service.UserService.*(..))"/>
        <!--设置前置通知(无参)-->
        <aop:before method="before" pointcut-ref="loginPointCut"/>
        <!--设置前置通知(有参)-->
        <aop:before method="before1" pointcut-ref="loginPointCut"/>

        <!--设置后置通知(无参)-->
        <aop:after-returning method="afterReturning" pointcut-ref="loginPointCut"/>
        <!--设置后置通知(有参)-->
        <aop:after-returning method="afterReturning1" pointcut-ref="loginPointCut" returning="val"/>

        <!--设置环绕通知-->
        <aop:around method="around" pointcut-ref="loginPointCut"/>

        <!--设置异常抛出通知(无参)-->
        <aop:after-throwing method="afterThrowing" pointcut-ref="loginPointCut"/>
        <!--设置异常抛出通知(有参)-->
        <aop:after-throwing method="afterThrowing1" pointcut-ref="loginPointCut" throwing="ex"/>

        <!--设置最终通知(无参)-->
        <aop:after method="after" pointcut-ref="loginPointCut"/>
        <!--设置最终通知(有参)-->
        <aop:after method="after1" pointcut-ref="loginPointCut"/>
    </aop:aspect>
</aop:config>

基于annotation方式

  • 创建目标target
@Service("customerService")
public class CustomerServiceImpl implements CustomerService {

    @Override
    public String add() {
        System.out.println("add···");
        return "返回值:add";
    }

    @Override
    public void show() {
        System.out.println("show···");
    }

    @Override
    public void update() {
        System.out.println("update···");
    }

    @Override
    public void delete() {
        System.out.println("delete···");
    }

}
  • 创建通知advice
@Component
@Aspect         // 声明当前的bean就是一个切面
public class CustomerHelper {

    /**
     * 前置通知
     *
     * @param jp
     */
    @Before("execution(* cn.ade.service.CustomerService.*(..))")
    public void before(JoinPoint jp) {
        System.out.println(jp.getSignature().getDeclaringTypeName());
        System.out.println(jp.getSignature().getName());
        System.out.println("前置通知");
    }

    /**
     * 后置通知
     *
     * @param jp
     * @param value
     */
    @AfterReturning(value = "execution(* cn.ade.service.CustomerService.*(..))",returning = "value")
    public void afterReturning(JoinPoint jp, Object value) {
        System.out.println("目标方法的返回值是:"+value);
        System.out.println("后置通知");
    }

    /**
     * 环绕通知
     *
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("execution(* cn.ade.service.CustomerService.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前···");
        Object value = pjp.proceed();
        System.out.println("环绕后···");
        return value;
    }

    /**
     * 异常抛出通知
     *
     * @param jp
     * @param ex
     */
    @AfterThrowing(value = "execution(* cn.ade.service.CustomerService.*(..))",throwing = "ex")
    public void afterThrowing(JoinPoint jp, Throwable ex) {
        System.out.println("抛出的异常:"+ex);
        System.out.println("异常抛出通知···");
    }

    /**
     * 最终通知
     */
    @After("execution(* cn.ade.service.CustomerService.*(..))")
    public void after() {
        System.out.println("最终通知");
    }

}
  • 配置文件
<!--配置注解扫描的位置-->
<context:component-scan base-package="cn.ade"/>

<!--开启aspectj注解自动代理-->
<!--使用proxy-target-class="true"来确定使用cglib实现代理,false使用proxy代理-->
<aop:aspectj-autoproxy proxy-target-class="false" />
  • 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AspectJAopDepTest {

    @Autowired
    private CustomerService customerService;
 
    @Test
    public void test01() {
        customerService.add();
    }

}
发布了48 篇原创文章 · 获赞 1 · 访问量 1048

猜你喜欢

转载自blog.csdn.net/laonxs/article/details/104998906
今日推荐