热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

详解Spring学习总结——Spring实现AOP的多种方式

这篇文章主要介绍了详解Spring学习总结——Spring实现AOP的多种方式,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

目录

一、基于XML配置的Spring AOP

二、使用注解配置AOP

三、AspectJ切点函数

四、AspectJ通知注解

五、零配置实现Spring IoC与AOP

AOP(Aspect Oriented Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的横向多模块统一控制的一种技术。AOP是OOP的补充,是spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP可以分为静态织入与动态织入,静态织入即在编译前将需织入内容写入目标模块中,这样成本非常高。动态织入则不需要改变目标模块。Spring框架实现了AOP,使用注解配置完成AOP比使用XML配置要更加方便与直观。

一、基于XML配置的Spring AOP

在讲注解实现AOP功能前先用前面学习过的使用xml配置Spring AOP功能,这样是为了对比以便更好的理解。

1.1、新建一个Maven项目,添加引用,项目的pom.xml文件如下:


 4.0.0

 com.zhangguo
 Spring052
 0.0.1-SNAPSHOT
 jar

 Spring052
 http://maven.apache.org

 
  UTF-8
  4.3.0.RELEASE
 
 
  
   junit
   junit
   test
   4.10
  
  
   org.springframework
   spring-context
   ${spring.version}
  
  
   org.aspectj
   aspectjweaver
   1.8.9
  
  
   cglib
   cglib
   3.2.4
  
 


1.2、创建要被代理的Math类,代码如下:

package com.zhangguo.Spring052.aop01;

/**
 * 被代理的目标类
 */
public class Math{
 //加
 public int add(int n1,int n2){
  int result=n1+n2;
  System.out.println(n1+"+"+n2+"="+result);
  return result;
 }
 
 //减
 public int sub(int n1,int n2){
  int result=n1-n2;
  System.out.println(n1+"-"+n2+"="+result);
  return result;
 }
 
 //乘
 public int mut(int n1,int n2){
  int result=n1*n2;
  System.out.println(n1+"X"+n2+"="+result);
  return result;
 }
 
 //除
 public int div(int n1,int n2){
  int result=n1/n2;
  System.out.println(n1+"/"+n2+"="+result);
  return result;
 }
}

1.3、编辑AOP中需要使用到的通知类Advices.Java代码如下:

package com.zhangguo.Spring052.aop01;

import org.aspectj.lang.JoinPoint;

/**
 * 通知类,横切逻辑
 *
 */
public class Advices {
 
 public void before(JoinPoint jp){
  System.out.println("----------前置通知----------");
  System.out.println(jp.getSignature().getName());
 }
 
 public void after(JoinPoint jp){
  System.out.println("----------最终通知----------");
 }
}

1.4、配置容器初始化时需要的XML文件,aop01.xml文件内容如下:

<&#63;xml version="1.0" encoding="UTF-8"&#63;>

  
 
 
 
 
 
 
 
 
  
  
   
   
   
   
   
  
 



1.5、测试代码Test.java如下:

package com.zhangguo.Spring052.aop01;

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

public class Test {

 public static void main(String[] args) {
  ApplicationContext ctx = new ClassPathXmlApplicationContext("aop01.xml");
  Math math = ctx.getBean("math", Math.class);
  int n1 = 100, n2 = 5;
  math.add(n1, n2);
  math.sub(n1, n2);
  math.mut(n1, n2);
  math.div(n1, n2);
 }

}

运行结果:

二、使用注解配置AOP

2.1、在上一个示例中修改被代理的类Math,为了实现IOC扫描在Math类上注解了@Service并命名bean为math。相当于上一个示例中在xml配置文件中增加了一个bean,,Math类的代码如下:

package com.zhangguo.Spring052.aop02;

import org.springframework.stereotype.Service;

/**
 * 被代理的目标类
 */
@Service("math")
public class Math{
 //加
 public int add(int n1,int n2){
  int result=n1+n2;
  System.out.println(n1+"+"+n2+"="+result);
  return result;
 }
 
 //减
 public int sub(int n1,int n2){
  int result=n1-n2;
  System.out.println(n1+"-"+n2+"="+result);
  return result;
 }
 
 //乘
 public int mut(int n1,int n2){
  int result=n1*n2;
  System.out.println(n1+"X"+n2+"="+result);
  return result;
 }
 
 //除
 public int div(int n1,int n2){
  int result=n1/n2;
  System.out.println(n1+"/"+n2+"="+result);
  return result;
 }
}

 2.2、修改通知类Advices,代码中有3个注解,@Component表示该类的实例会被Spring IOC容器管理;@Aspect表示声明一个切面;@Before表示before为前置通知,通过参数execution声明一个切点,Advices.java代码如下所示:

package com.zhangguo.Spring052.aop02;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 通知类,横切逻辑
 *
 */
@Component
@Aspect
public class Advices {
 @Before("execution(* com.zhangguo.Spring052.aop02.Math.*(..))")
 public void before(JoinPoint jp){
  System.out.println("----------前置通知----------");
  System.out.println(jp.getSignature().getName());
 }
 
 @After("execution(* com.zhangguo.Spring052.aop02.Math.*(..))")
 public void after(JoinPoint jp){
  System.out.println("----------最终通知----------");
 }
}

 上面的代码与下面的配置基本等同

 
 
 
 
 
  
  
   
   
   
   
   
  
 

2.3、新增配置文件aop02.xml,在配置IOC的基础上增加了aop:aspectj-autoproxy节点,Spring框架会自动为与AspectJ切面配置的Bean创建代理,proxy-target-class="true"属性表示被代理的目标对象是一个类,而非实现了接口的类,主要是为了选择不同的代理方式。

<&#63;xml version="1.0" encoding="UTF-8"&#63;>

  
  
  


2.4、测试运行代码Test.java如下:

package com.zhangguo.Spring052.aop02;

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

public class Test {

 public static void main(String[] args) {
  ApplicationContext ctx = new ClassPathXmlApplicationContext("aop02.xml");
  Math math = ctx.getBean("math", Math.class);
  int n1 = 100, n2 = 5;
  math.add(n1, n2);
  math.sub(n1, n2);
  math.mut(n1, n2);
  math.div(n1, n2);
 }

}

运行结果:

三、AspectJ切点函数

切点函数可以定位到准确的横切逻辑位置,在前面的示例中我们只使用过execution(* com.zhangguo.Spring052.aop02.Math.*(..)),execution就是一个切点函数,但该函数只什么方法一级,如果我们要织入的范围是类或某个注解则execution就不那么好用了,其实一共有9个切点函数,有不同的针对性。

@AspectJ使用AspectJ专门的切点表达式描述切面,Spring所支持的AspectJ表达式可分为四类:

方法切点函数:通过描述目标类方法信息定义连接点。

方法参数切点函数:通过描述目标类方法入参信息定义连接点。

目标类切点函数:通过描述目标类类型信息定义连接点。

代理类切点函数:通过描述代理类信息定义连接点。

常见的AspectJ表达式函数:

  • execution():满足匹配模式字符串的所有目标类方法的连接点
  • @annotation():任何标注了指定注解的目标方法链接点
  • args():目标类方法运行时参数的类型指定连接点
  • @args():目标类方法参数中是否有指定特定注解的连接点
  • within():匹配指定的包的所有连接点
  • target():匹配指定目标类的所有方法
  • @within():匹配目标对象拥有指定注解的类的所有方法
  • @target():匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
  • this():匹配当前AOP代理对象类型的所有执行方法

最常用的是:execution(<修饰符模式>&#63;<返回类型模式><方法名模式>(<参数模式>)<异常模式>&#63;)切点函数,可以满足多数需求。

为了展示各切点函数的功能现在新增一个类StrUtil,类如下:

package com.zhangguo.Spring052.aop03;

import org.springframework.stereotype.Component;

@Component("strUtil")
public class StrUtil {
 public void show(){
  System.out.println("Hello StrUtil!");
 }
}

测试代码如下:

package com.zhangguo.Spring052.aop03;

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

public class Test {

 public static void main(String[] args) {
  ApplicationContext ctx = new ClassPathXmlApplicationContext("aop03.xml");
  IMath math = ctx.getBean("math", Math.class);
  int n1 = 100, n2 = 5;
  math.add(n1, n2);
  math.sub(n1, n2);
  math.mut(n1, n2);
  math.div(n1, n2);
  
  StrUtil strUtil=ctx.getBean("strUtil",StrUtil.class);
  strUtil.show();
 }

}

3.1、切点函数execution,通知与切面的定义如下:

package com.zhangguo.Spring052.aop03;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 通知类,横切逻辑
 *
 */
@Component
@Aspect
public class Advices {
 @Before("execution(* com.zhangguo.Spring052.aop03.Math.*(..))")
 public void before(JoinPoint jp){
  System.out.println("----------前置通知----------");
  System.out.println(jp.getSignature().getName());
 }
 
 //execution切点函数
 //com.zhangguo.Spring052.aop03包下所有类的所有方法被切入
 @After("execution(* com.zhangguo.Spring052.aop03.*.*(..))")
 public void after(JoinPoint jp){
  System.out.println("----------最终通知----------");
 }
}

运行结果如下:

execution(<修饰符模式>&#63;<返回类型模式><方法名模式>(<参数模式>)<异常模式>&#63;)

3.2、切点函数within

 //within切点函数
 //com.zhangguo.Spring052.aop03包下所有类的所有方法被切入
 @After("within(com.zhangguo.Spring052.aop03.*)")
 public void after(JoinPoint jp){
  System.out.println("----------最终通知----------");
 }

3.3、this切点函数

 //this切点函数
 //实现了IMath接口的代理对象的任意连接点
 @After("this(com.zhangguo.Spring052.aop03.IMath)")
 public void after(JoinPoint jp){
  System.out.println("----------最终通知----------");
 }

3.4、args切点函数

 //args切点函数
 //要求方法有两个int类型的参考才会被织入横切逻辑
 @After("args(int,int)")
 public void after(JoinPoint jp){
  System.out.println("----------最终通知----------");
 }

如果参数类型不是基本数据类型则需要包名。

3.5、@annotation切点函数

先自定义一个可以注解在方法上的注解

package com.zhangguo.Spring052.aop03;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnno {
}


 //@annotation切点函数
 //要求方法必须被注解com.zhangguo.Spring052.aop03.MyAnno才会被织入横切逻辑
 @After("@annotation(com.zhangguo.Spring052.aop03.MyAnno)")
 public void after(JoinPoint jp){
  System.out.println("----------最终通知----------");
 }

package com.zhangguo.Spring052.aop03;

import org.springframework.stereotype.Component;

@Component("strUtil")
public class StrUtil {
 @MyAnno
 public void show(){
  System.out.println("Hello StrUtil!");
 }
}

运行结果:

其它带@的切点函数都是针对注解的

四、AspectJ通知注解

AspectJ通知注解共有6个,常用5个,引介少用一些。

先解决定义切点复用的问题,如下代码所示,切点函数的内容完全一样:

package com.zhangguo.Spring052.aop04;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 通知类,横切逻辑
 *
 */
@Component
@Aspect
public class Advices {
 @Before("execution(* com.zhangguo.Spring052.aop04.Math.*(..))")
 public void before(JoinPoint jp){
  System.out.println("----------前置通知----------");
  System.out.println(jp.getSignature().getName());
 }
 
 @After("execution(* com.zhangguo.Spring052.aop04.Math.*(..))")
 public void after(JoinPoint jp){
  System.out.println("----------最终通知----------");
 }
}

可以先定义一个切点然后复用,如下所示:

package com.zhangguo.Spring052.aop04;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 通知类,横切逻辑
 */
@Component
@Aspect
public class Advices {
 //切点
 @Pointcut("execution(* com.zhangguo.Spring052.aop04.Math.*(..))")
 public void pointcut(){
 }
 
 @Before("pointcut()")
 public void before(JoinPoint jp){
  System.out.println("----------前置通知----------");
  System.out.println(jp.getSignature().getName());
 }
 
 @After("pointcut()")
 public void after(JoinPoint jp){
  System.out.println("----------最终通知----------");
 }
}

修改Advices.java文件,增加各种通知类型如下:

package com.zhangguo.Spring052.aop04;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 通知类,横切逻辑
 */
@Component
@Aspect
public class Advices {
 //切点
 @Pointcut("execution(* com.zhangguo.Spring052.aop04.Math.a*(..))")
 public void pointcut(){
 }
 
 //前置通知
 @Before("pointcut()")
 public void before(JoinPoint jp){
  System.out.println(jp.getSignature().getName());
  System.out.println("----------前置通知----------");
 }
 
 //最终通知
 @After("pointcut()")
 public void after(JoinPoint jp){
  System.out.println("----------最终通知----------");
 }
 
 //环绕通知
 @Around("execution(* com.zhangguo.Spring052.aop04.Math.s*(..))")
 public Object around(ProceedingJoinPoint pjp) throws Throwable{
  System.out.println(pjp.getSignature().getName());
  System.out.println("----------环绕前置----------");
  Object result=pjp.proceed();
  System.out.println("----------环绕后置----------");
  return result;
 }
 
 //返回结果通知
 @AfterReturning(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.m*(..))",returning="result")
 public void afterReturning(JoinPoint jp,Object result){
  System.out.println(jp.getSignature().getName());
  System.out.println("结果是:"+result);
  System.out.println("----------返回结果----------");
 }
 
 //异常后通知
 @AfterThrowing(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.d*(..))",throwing="exp")
 public void afterThrowing(JoinPoint jp,Exception exp){
  System.out.println(jp.getSignature().getName());
  System.out.println("异常消息:"+exp.getMessage());
  System.out.println("----------异常通知----------");
 }
}

运行结果:

五、零配置实现Spring IoC与AOP

为了实现零配置在原有示例的基础上我们新增一个类User,如下所示:

package com.zhangguo.Spring052.aop05;

public class User {
 public void show(){
  System.out.println("一个用户对象");
 }
}

该类并未注解,容器不会自动管理。因为没有xml配置文件,则使用一个作为配置信息,ApplicationCfg.java文件如下:

package com.zhangguo.Spring052.aop05;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration //用于表示当前类为容器的配置类,类似
@ComponentScan(basePackages="com.zhangguo.Spring052.aop05") //扫描的范围,相当于xml配置的结点
@EnableAspectJAutoProxy(proxyTargetClass=true) //自动代理,相当于
public class ApplicationCfg {
 //在配置中声明一个bean,相当于
 @Bean
 public User getUser(){
  return new User();
 }
}

该类的每一部分内容基本都与xml 配置有一对一的关系,请看注释,这样做要比写xml方便,但不便发布后修改。测试代码如下:

package com.zhangguo.Spring052.aop05;

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

public class Test {

 public static void main(String[] args) {
  // 通过类初始化容器
  ApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationCfg.class);
  Math math = ctx.getBean("math", Math.class);
  int n1 = 100, n2 = 0;
  math.add(n1, n2);
  math.sub(n1, n2);
  math.mut(n1, n2);
  try {
   math.div(n1, n2);
  } catch (Exception e) {
  }
  
  User user=ctx.getBean("getUser",User.class);
  user.show();
 }

}

 advices.java 同上,没有任何变化,运行结果如下:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Maven快照版本管理及更新策略详解
    本文深入探讨了Maven中的快照版本管理和更新策略,解释了快照版本与正式版本的区别,并提供了如何配置快照更新策略的方法,以确保项目依赖始终保持最新。 ... [详细]
  • 如何使用Maven将依赖插件一并打包进JAR文件
    本文详细介绍了在使用Maven构建项目时,如何将所需的依赖插件一同打包进最终的JAR文件中,以避免手动部署依赖库的麻烦。 ... [详细]
  • Maven + Spring + MyBatis + MySQL 环境搭建与实例解析
    本文详细介绍如何使用MySQL数据库进行环境搭建,包括创建数据库表并插入示例数据。随后,逐步指导如何配置Maven项目,整合Spring框架与MyBatis,实现高效的数据访问。 ... [详细]
  • 从理想主义者的内心深处萌发的技术信仰,推动了云原生技术在全球范围内的快速发展。本文将带你深入了解阿里巴巴在开源领域的贡献与成就。 ... [详细]
  • Android与JUnit集成测试实践
    本文探讨了如何在Android项目中集成JUnit进行单元测试,并详细介绍了修改AndroidManifest.xml文件以支持测试的方法。 ... [详细]
  • 本文详细探讨了在Web开发中常见的UTF-8编码问题及其解决方案,包括HTML页面、PHP脚本、MySQL数据库以及JavaScript和Flash应用中的乱码问题。 ... [详细]
  • 本文详细介绍了 Java 中 org.w3c.dom.Node 类的 isEqualNode() 方法的功能、参数及返回值,并通过多个实际代码示例来展示其具体应用。此方法用于检测两个节点是否相等,而不仅仅是判断它们是否为同一个对象。 ... [详细]
  • 在尝试启动Java应用服务器Tomcat时,遇到了org.apache.catalina.LifecycleException异常。本文详细记录了异常的具体表现形式,并提供了有效的解决方案。 ... [详细]
  • 本文介绍了一种在 Android 开发中动态修改 strings.xml 文件中字符串值的有效方法。通过使用占位符,开发者可以在运行时根据需要填充具体的值,从而提高应用的灵活性和可维护性。 ... [详细]
  • Docker安全策略与管理
    本文探讨了Docker的安全挑战、核心安全特性及其管理策略,旨在帮助读者深入理解Docker安全机制,并提供实用的安全管理建议。 ... [详细]
  • 理解浏览器历史记录(2)hashchange、pushState
    阅读目录1.hashchange2.pushState本文也是一篇基础文章。继上文之后,本打算去研究pushState,偶然在一些信息中发现了锚点变 ... [详细]
  • 本文介绍如何在阿里云环境中利用 Docker 容器化技术部署一个简单的 Flask Web 应用,并确保其可通过互联网访问。内容涵盖 Python 代码编写、Dockerfile 配置、镜像构建及容器运行等步骤。 ... [详细]
  • 深入理解Dockerfile及其作用
    Dockerfile是一种文本格式的配置文件,用于定义构建Docker镜像所需的步骤。通过使用`docker build`命令,用户可以将Dockerfile中的一系列指令转换成一个可执行的Docker镜像。 ... [详细]
  • 2017年软件开发领域的七大变革
    随着技术的不断进步,2017年对软件开发人员而言将充满挑战与机遇。本文探讨了开发人员需要适应的七个关键变化,包括人工智能、聊天机器人、容器技术、应用程序版本控制、云测试环境、大众开发者崛起以及系统管理的云迁移。 ... [详细]
  • 本文详细介绍如何在华为鲲鹏平台上构建和使用适配ARM架构的Redis Docker镜像,解决常见错误并提供优化建议。 ... [详细]
author-avatar
郭绍玲刚珍雅瑜_658
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有