Spring(四)——AOP(Aspect Oriented Program面向切面编程)

AOP 即 Aspect Oriented Program 面向切面编程
首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能
所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
所谓的周边功能,比如性能统计,日志,事务管理等等

周边功能在Spring的面向切面编程AOP思想里,即被定义为切面

在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发
然后把切面和核心业务功能 "编织" 在一起,这就叫AOP
clipboard.png

名词概念

  1. JoinPoint 连接点,程序运行中的某个(被拦截的)阶段点
  2. PointCut 切点。Pointcut是JoinPoint的集合,即一个或多个方法被拦截点的集合
  3. Aspect 切面。和核心业务功能编织在一起的周边功能的模块。
  4. Advice Aspect中应用在切入点中的代码或者说方法,如,切面类中被@before(....)等注释了的成员函数

注意:Spring会根据是否实现了接口自动切换JDK动态代理和CGLib动态代理
1.如果是使用Jdk动态代理实现Spring AOP,Spring容器的getBean方法获得的对象是不能转型成该Bean定义的Class类型。
假设有Service接口和实现该接口的ServiceImpl类,使用Jdk动态代理实现Spring AOP,getBean获得的对象是Jdk动态代理生成的代理类的对象,这个代理类只是实现了Service接口,而没有继承ServiceImpl。 使用CGLib动态代理不会有这个问题,因为CGLib动态代理生成的代理类是继承我们的目标类的,而不是实现目标接口。

2.如果使用CGLib动态代理实现Spring AOP,通过Spring容器的getBean方法获得的对象不能直接引用目标类的公有属性,读取或者修改公有属性。
假设Service类没有实现任何接口,使用Spring容器的getBean方法时转型成Service类的对象service,但我们不能调用service.type来获得或者修改type属性。
这是因为使用CGLib动态代理实现的Spring AOP,调用service.type是引用CGLib代理类对象的属性,而不是目标对象的type属性。

总之:如果实现了接口,getBean要强转成接口,如果没有实现接口,则可以转成实现类。

注解方式

首先了解一下几种注解的作用:
@Aspect 将某个类声明成一个切面

@Pointcut("切入点表达式") 声明一个切点,我们可利用方法签名来编写切入点表达式。最典型的切入点表达式是根据方法的签名来匹配各种方法:

  • execution (* cn.itcast.service..*.*(..)) 匹配cn.itcast.service包及其所有子包下的任何返回值的任何方法
  • execution (* cn.itcast.service.impl.PersonServiceImpl.*(..)):匹配PersonServiceImpl类中声明的所有方法。第一个代表任意修饰符及任意返回值类型,第二个代表任意方法,..匹配任意数量任意类型的参数,若目标类与该切面在同一个包中,可以省略包名。
  • execution public * cn.itcast.service.impl.PersonServiceImpl.*(..):匹配PersonServiceImpl类中的所有公有方法。
  • execution public double cn.itcast.service.impl.PersonServiceImpl.*(..):匹配PersonServiceImpl类中返回值类型为double类型的所有公有方法。
  • execution public double cn.itcast.service.impl.PersonServiceImpl.*(double, ..):匹配PersonServiceImpl类中第一个参数为double类型,后面不管有无参数的所有公有方法,并且该方法的返回值类型为double类型。
  • execution public double cn.itcast.service.impl.PersonServiceImpl.*(double, double):匹配PersonServiceImpl类中参数类型为double,double类型的,并且返回值类型也为double类型的所有公有方法。

@Before("切入点表达式或已经声明的切入点")前置通知方法。 前置通知方法在目标方法开始之前执行。

@AfterReturning("")返回前执行

@AfterThrowing("") 抛出异常前执行

@After("") 后置通知。方法执行完后一定会执行。类似finaly

@Around("") 环绕通知。被拦截(被切入)方法的调用由环绕通知决定。切面方法需要传入一个ProceedingJointPoint对象,该对象用于启用被拦截方法。实例如下:

public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("start log:" + joinPoint.getSignature().getName());//前置
    Object object = joinPoint.proceed();//启用被拦截的核心业务方法
    System.out.println("end log:" + joinPoint.getSignature().getName());//后置
    return object;
 }

单个Aspect情况:
clipboard.png


多个Aspect情况:

clipboard.png

为了便于理解,还可以参考如下的图:
clipboard.png

将切面想象成同心圆,@Order(n)中n越小圆越大,越先执行。
而先执行的后退出。


示例程序:
aspect:

package com.myspring.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
 
@Aspect
@Component//这里务必记得要把aspect也加入到容器里
public class Log{ 
//实际中尽量不要复用切入点表达式,而是声明一个切入点方法
    @After("execution(* com.myspring.imple..*(..))")
        public void after() {
            System.out.println("after");
        }
    @Around("execution(* com.myspring.imple..*(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("start log:" + joinPoint.getSignature().getName());
        Object object = joinPoint.proceed();
        System.out.println("end log:" + joinPoint.getSignature().getName());
        return object;
    }
    @Before( "execution(* com.myspring.imple..*(..))")
    public void before() {
        System.out.println("before");
    }
   
}

service:

package com.myspring.imple;

import org.springframework.stereotype.Component;

@Component("s")
public class ServiceImpl 
{
    public void save()
    {
        System.out.println("save run");    
    }    
}

applicationContext.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"
    <!--添加aop命名空间-->
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    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
   http://www.springframework.org/schema/tx 
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/context      
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">
  
    <context:component-scan base-package="com.myspring.imple"/>
    <context:component-scan base-package="com.myspring.aspect"/>   
     <!-- spring调用了aspectj——一个面向切面的框架的自动代理 -->
    <aop:aspectj-autoproxy/>    
</beans>

test:

package com.myspring.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.myspring.imple.ServiceImpl;

public class test
{
    public static void main(String[] args)
    {
        // TODO 自动生成的方法存根
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //如果核心业务实现了接口则为JDK动态代理,需要强转为接口,否则为CGLib动态代理,强转为实现类
        ServiceImpl imp1 = (ServiceImpl)context.getBean("s");
        imp1.save();
    }
}

配置文件方式

applicationContext.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"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    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
   http://www.springframework.org/schema/tx 
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/context      
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">
  <!--首先将核心功能和周边功能的Bean加载到容器里-->
  <bean id="logAspect" class="com.how2java.aspect.LoggerAspect"></bean>
  <bean id="service" class="com.how2java.service.ProductService"></bean> 
  <aop:config>
      <!-- 声明一个切入点 -->
      <aop:pointcut expression="execution(* com.how2java.service.ProductService.*(..))" id="logPointcut"/>
      <!-- 声明切面 -->
      <aop:aspect ref="logAspect" id="log" >
          <!-- 设定aspect中advice的方法与绑定的切入点 -->
              <!--使用已经定义好的pointcut-->
          <aop:around method="around" pointcut-ref="logPointcut"/>
          <aop:before method="before" pointcut-ref="logPointcut"/>
              <!--使用切入点表达式定义Pointcut-->
          <aop:after method="after" pointcut="execution(* com.how2java.service.ProductService.*(..))"/>
      </aop:aspect>
  </aop:config>
  
</beans>

名字太多记不住?我们来理一遍。
首先加载两个bean:
作为切面的bean——logAspect以及作为核心功能的bean——service。对应注解模式的@Component
然后开始配置aop:
拦截住核心功能的一个方法(声明一个pointcut),名字叫logPointcut,对应注解@Pointcut
接着将作为切面的bean——logAspect声明为一个切面,名字是log,对应注解@Aspect
现在切入点和切面都有了,最后将他们编织在一起:
在切面中设置advice类型和周边功能方法,并且与相应的切入点——logPointcut绑定,对应注解@Before(切入点表达式或已定义的切入点)等

ps:Pointcut定义在Aspect外则所有Aspect都可以捕获,定义在某个Aspect里则只有该Aspect内的Advice可以捕获

当我们使用别人写好的周边功能与我们写的核心业务编织时,应当使用配置文件方式;当我们独立完成并且封装整个系统时才考虑使用注解方式。因此推荐使用配置文件方式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值