Spring之AOP面向切面编程

十、AOP面向切面编程

目录:什么是AOP、AOP在Spring中的作用、Spring实现AOP

1.什么是AOP?

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现
程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的
一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使
得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

image

2.AOP在Spring中的作用

提供声明式事务,允许用户自定义切面。

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与业务逻辑无关的,但是需要关注的部分,就是横切关注点。如日志、安全、缓存、事务等等。

  • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。

  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。

  • 目标(Target):被通知对象。

  • 代理(Proxy):向目标对象应用通知之后创建的对象。

  • 切入点(PointCut):切面通知 执行的“地点”的定义。

  • 连接点(JointPoint):与切入点匹配的执行点。

    image

SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

image

即AOP在不改变原有代码的情况下,去增加新的功能。

3.Spring实现AOP

使用AOP织入,需要导入一个依赖包。

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.7</version>
</dependency>

第一种方式:通过Spring API实现
①首先编写业务接口和实现类。

public interface UserService { 
  public void add(); 
  public void delete(); 
  public void update(); 
  public void search(); 
}

public class UserServiceImpl implements UserService{ 
  @Override 
  public void add() { 
    System.out.println("增加用户"); 
  }
  @Override 
  public void delete() { 
    System.out.println("删除用户"); 
  }
  @Override 
  public void update() { 
    System.out.println("更新用户"); 
  }
  @Override 
  public void search() { 
  System.out.println("查询用户"); 
  } 
}

②编写两个增强类,一个前置增强,一个后置增强。
方法执行前通知

public class BeforeLog implements MethodBeforeAdvice {
    /**
     *
     * @param method 要执行的目标对象的方法
     * @param args   被调用的方法的参数
     * @param target 目标对象
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"类的"+method.getName()+"方法执行了");
    }
}

方法执行后通知

public class AfterLog implements AfterReturningAdvice {
    /**
     *
     * @param returnValue 方法返回值
     * @param method 要执行的目标对象的方法
     * @param args   被调用的方法的参数
     * @param target 目标对象
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被执行了!"+"返回值:"+returnValue);
    }
}

异常通知,方法需要自己写

public class ExceptionLog implements ThrowsAdvice {
    /**
     *
     * @param method 要执行的目标对象的方法
     * @param args   被调用的方法的参数
     * @param target 目标对象
     * @param ex     异常
     */
    public void  afterThrowing(Method method,Object[] args,Object target,Exception ex){
        System.out.println(target.getClass().getName()+"的"+method.getName()+"方法出现了异常");
    }

}

环绕通知,最接近动态代理

public class AroundLog implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //获取目标类
        Object target = invocation.getThis();
        //获取目标方法
        Method method = invocation.getMethod();
        //获取参数列表
        Object[] args = invocation.getArguments();
        //定义返回值
        Object returnValue = null;
        try {
            System.out.println("环绕方法执行前!");
            returnValue = invocation.proceed();
            System.out.println("环绕方法执行后!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("环绕通知的异常通知!");
        }
        return returnValue;
    }
}

③最后去spring的文件中注册,并实现aop切入实现,注意导入约束。
aop1.x

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:annotation-config/>
    <!--配置目标类-->
    <bean id="userServiceTarget" class="com.itany.spring.aop02.UserServiceImpl"/>
    <!--配置通知类-->
    <bean id="before" class="com.itany.spring.aop02.BeforeLog"/>
    <bean id="after" class="com.itany.spring.aop02.AfterLog"/>
    <bean id="exception" class="com.itany.spring.aop02.ExceptionLog"/>
    <bean id="around" class="com.itany.spring.aop02.AroundLog"/>
    
    <bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="userServiceTarget"/>
        <property name="proxyInterfaces" value="com.itany.spring.aop02.UserService"/>
        <property name="interceptorNames">
            <list>
                <value>before</value>
                <value>after</value>
                <value>exception</value>
                <value>around</value>
            </list>
        </property>
    </bean>

</beans>

aop2.x

<!--配置目标类-->
    <bean id="userServiceTarget" class="com.itany.spring.aop02.UserServiceImpl"/>
    <!--配置通知类-->
    <bean id="before" class="com.itany.spring.aop02.BeforeLog"/>
    <bean id="after" class="com.itany.spring.aop02.AfterLog"/>
    <bean id="exception" class="com.itany.spring.aop02.ExceptionLog"/>
    <bean id="around" class="com.itany.spring.aop02.AroundLog"/>

    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pointcut" expression="execution(* com.itany.spring.aop02.UserServiceImpl.*(..))"/>
        <aop:advisor advice-ref="before" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="after" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="exception" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="around" pointcut-ref="pointcut"/>
    </aop:config>

④测试。

public class MyTest { 
  @Test 
  public void test(){ 
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); 
    UserService userService = (UserService) context.getBean("userService"); 
    userService.search();
  } 
}

Spring的AOP就是将公共的业务(日志 , 安全等)和领域业务结合起来,当执行领域业务时,将会把公共业务加进来,实现公共业务的重复利用。领域业务更纯粹,开发人员专注领域业务,其本质还是动态代理。
第二种方式:自定义类来实现AOP
目标业务类依旧是userServiceImpl。
①编写一个切入类。

public class DiyPointcut { 
  public void before(){ 
    System.out.println("方法执行前"); 
  }
  public void after(){ 
    System.out.println("方法执行后"); 
  } 
}

②在Spring中进行配置。

<!--注册bean-->
<bean id="diy" class="com.ping.config.DiyPointcut"/>
<!--aop的配置-->
<aop:config>
  <!--第二种方式:使用AOP的标签实现-->
  <!--切面-->
  <aop:aspect ref="diy">
    <!--切入点-->
    <aop:pointcut id="diyPonitcut" expression="execution(* com.ping.service.UserServiceImpl.*(..))"/>
    <aop:before pointcut-ref="diyPonitcut" method="before"/>
    <aop:after pointcut-ref="diyPonitcut" method="after"/>
  </aop:aspect>
</aop:config>

③测试。

public class MyTest { 
  @Test 
  public void test(){ 
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); 
    UserService userService = (UserService) context.getBean("userService"); 
    userService.add(); 
  } 
}

带参数的aop

①编写一个切入类。

public class MyAdvice {
    public void before(JoinPoint jp){
        //获取目标类
        Object target = jp.getThis();
        //获取目标方法
        Signature signature = jp.getSignature();
        //获取目标方法参数列表
        Object[] args = jp.getArgs();
        System.out.println("前置通知:"+target+"中的"+signature.getName()+"方法即将执行!");
    }

    public void afterReturning(JoinPoint jp,Object returnValue){
        //获取目标类
        Object target = jp.getThis();
        //获取目标方法
        Signature signature = jp.getSignature();
        //获取目标方法参数列表
        Object[] args = jp.getArgs();
        System.out.println("正常返回通知:"+target+"中的"+signature.getName()+"方法执行完成,"+"返回值:"+returnValue);
    }

    public void afterThrow(JoinPoint jp,Exception e){
        //获取目标类
        Object target = jp.getThis();
        //获取目标方法
        Signature signature = jp.getSignature();
        //获取目标方法参数列表
        Object[] args = jp.getArgs();
        System.out.println("异常通知通知:"+target+"中的"+signature.getName()+"方法出现异常,异常为:"+e);
    }

    public void after(JoinPoint jp){
        //获取目标类
        Object target = jp.getThis();
        //获取目标方法
        Signature signature = jp.getSignature();
        //获取目标方法参数列表
        Object[] args = jp.getArgs();
        System.out.println("后置通知:"+target+"中的"+signature.getName()+"方法执行完毕!");
    }
public Object around(ProceedingJoinPoint pjp) throws Throwable{
        Object returnValue = null;

        try {
            System.out.println("环绕前置通知!");
            returnValue = pjp.proceed();
            System.out.println("环绕正常返回通知!"+"返回值:"+returnValue);
        } catch (Throwable throwable) {
            System.out.println("环绕异常通知!");
            throwable.printStackTrace();
        }finally {
            System.out.println("环绕后置通知!");
        }
        return returnValue;
    }
}

②在Spring中进行配置。

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd 
        http://www.springframework.org/schema/aop 
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:annotation-config/>
    <context:property-placeholder ignore-unresolvable="true" location="classpath:db.properties"/>
    <bean id="userService" class="com.itany.spring.aop05.UserServiceImpl"/>
    <bean id="myAdvice" class="com.itany.spring.aop05.MyAdvice"/>
    <aop:config>
        <aop:aspect ref="myAdvice">
            <aop:pointcut id="pointCut" expression="execution(* com.itany.spring.aop05.UserServiceImpl.*(..))"/>
            <aop:before method="before" pointcut-ref="pointCut"/>
            <aop:after method="after" pointcut-ref="pointCut"/>
            <aop:after-returning method="afterReturning" pointcut-ref="pointCut" returning="returnValue"/>
            <aop:after-throwing method="afterThrow" pointcut-ref="pointCut" throwing="e"/>
        </aop:aspect>
    </aop:config>
</beans>

③测试。

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("aop05.xml");
        UserService userService =(UserService) ac.getBean("userService");
        userService.addUser();
    }
}

第三种方式:使用注解实现
①编写一个注解实现的增强类。

@Component
@Aspect
public class LogAdvice {
    @Before("execution(* com.itany.spring.Annocation.UserServiceImpl.*(..))")
    public void before(JoinPoint jp){
        //获取目标类
        Object target = jp.getThis();
        //获取目标方法
        Signature signature = jp.getSignature();
        //获取目标方法参数列表
        Object[] args = jp.getArgs();
        System.out.println("前置通知:"+target+"中的"+signature.getName()+"方法即将执行!");
    }
    @AfterReturning(value = "execution(* com.itany.spring.Annocation.UserServiceImpl.*(..))",returning = "returnValue")
    public void afterReturning(JoinPoint jp,Object returnValue){
        //获取目标类
        Object target = jp.getThis();
        //获取目标方法
        Signature signature = jp.getSignature();
        //获取目标方法参数列表
        Object[] args = jp.getArgs();
        System.out.println("正常返回通知:"+target+"中的"+signature.getName()+"方法执行完成,"+"返回值:"+returnValue);
    }
    @AfterThrowing(value = "execution(* com.itany.spring.Annocation.UserServiceImpl.*(..))",throwing = "e")
    public void afterThrow(JoinPoint jp, Exception e){
        //获取目标类
        Object target = jp.getThis();
        //获取目标方法
        Signature signature = jp.getSignature();
        //获取目标方法参数列表
        Object[] args = jp.getArgs();
        System.out.println("异常通知通知:"+target+"中的"+signature.getName()+"方法出现异常,异常为:"+e);
    }
    @After("execution(* com.itany.spring.Annocation.UserServiceImpl.*(..))")
    public void after(JoinPoint jp){
        //获取目标类
        Object target = jp.getThis();
        //获取目标方法
        Signature signature = jp.getSignature();
        //获取目标方法参数列表
        Object[] args = jp.getArgs();
        System.out.println("后置通知:"+target+"中的"+signature.getName()+"方法执行完毕!");
    }
    @Around("execution(* com.itany.spring.Annocation.UserServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        Object returnValue = null;

        try {
            System.out.println("环绕前置通知!");
            returnValue = pjp.proceed();
            System.out.println("环绕正常返回通知!"+"返回值:"+returnValue);
        } catch (Throwable throwable) {
            System.out.println("环绕异常通知!");
            throwable.printStackTrace();
        }finally {
            System.out.println("环绕后置通知!");
        }
        return returnValue;
    }
}

②在Spring配置文件中,注册bean,并增加支持注解的配置。

<aop:aspectj-autoproxy/>

aspectj-autoproxy说明:
Ⅰ通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring在内部依旧采用 AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了。
Ⅱ<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

切点表达式

  • within
    • 匹配某个类中的所有方法
    • 语法:within(包名.类名)
  • execution
    • 匹配你想要的一切
    • 可以是类,可以是方法
    • 语法:execution(返回值类型 包名.类名.方法名(参数列表))
    • 支持通配符用法
      • *
        • 用法一:匹配0或者多个字符
        • 用法二:匹配一个参数
      • ..
        • 表示匹配0或者多个参数
  • 支持连接条件
    • 连接条件可以在within中使用,也可以在execution中使用
    • and:且的意思,多个条件必须同时满足
    • or:或的意思,多个条件只要满足任意一个即可
    • not:非的意思,匹配不满足条件的方法
      • 使用not前面必须有空格
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容