Spring AOP在web应用中的使用方法实例

前言

之前的aop是通过手动创建代理类来进行通知的,但是在日常开发中,我们并不愿意在代码中硬编码这些代理类,我们更愿意使用DI和IOC来管理aop代理类。Spring为我们提供了以下方式来使用aop框架

一、以声明的方式配置AOP(就是使用xml配置文件)

1.使用ProxyFactoryBean的方式:

ProxyFactoryBean类是FactoryBean的一个实现类,它允许指定一个bean作为目标,并且为该bean提供一组通知和顾问(这些通知和顾问最终会被合并到一个AOP代理中)它和我们之前的ProxyFactory都是Advised的实现。

以下是一个简单的例子:一个学生和一个老师,老师会告诉学生应该做什么。

public class Student {

 public void talk() {
  System.out.println("I am a boy");
 }

 public void walk() {
  System.out.println("I am walking");
 }

 public void sleep() {
  System.out.println("I want to sleep");
 }
}

老师类

public class Teacher {

 private Student student;

 public void tellStudent(){
  student.sleep();
  student.talk();
 }

 public Student getStudent() {
  return student;
 }

 public void setStudent(Student student) {
  this.student = student;
 }
}

我们创建一个通知类,这个和之前是一样的SpringAOP中的通知类型以及创建

package cn.lyn4ever.aop;

import org.aspectj.lang.JoinPoint;

public class AuditAdvice implements MethodBeforeAdvice {
 @Override
 public void before(Method method, Object[] objects, @Nullable Object o) throws Throwable {
  System.out.println("这个方法被通知了" + method.getName());
 }
}

然后就使用spring的IOC来管理这个通知类,在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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/util
  https://www.springframework.org/schema/util/spring-util.xsd">

 <!--注入student-->
 <bean name="student" class="cn.lyn4ever.aop.aopconfig.Student">
 </bean>

 <!--注入teacher-->
 <bean name="teacher" class="cn.lyn4ever.aop.aopconfig.Teacher">
  <!--注意,这个student的属性要是上边的代理类,而不是能student-->
  <!--<property name="student" ref="student"/>-->
  <property name="student" ref="proxyOne"/>
 </bean>

 <!--注入我们创建的通知类-->
 <bean id="advice" class="cn.lyn4ever.aop.aopconfig.AuditAdvice"></bean>

 <!--创建代理类,使用前边写的通知进行通知,这样会使这个类上的所有方法都被通知-->
 <bean name="proxyOne" class="org.springframework.aop.framework.ProxyFactoryBean" p:target-ref="student"
   p:interceptorNames-ref="interceptorNames">
  <!--因为interceptorNames的属性是一个可变参数,也就是一个list-->
 </bean>

 <!--在上边引入了util的名称空间,简化了书写-->
 <util:list id="interceptorNames">
  <value>advice</value>
 </util:list>
</beans>

测试类

 public static void main(String[] args) {
  GenericXmlApplicationContext context = new GenericXmlApplicationContext();
  context.load("application1.xml");
  context.refresh();

  Teacher teacher = (Teacher) context.getBean("teacherOne");
  teacher.tellStudent();

 }

运行结果没有问题

以上是通过直接创建通知的方式,接下来我们试一个创建一个切入点(因为以上是对类中所有方法都进行通知,这时我们使用切入点只对其中部分方法进行通知),在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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/util
  https://www.springframework.org/schema/util/spring-util.xsd">

 <!--注入student-->
 <bean name="student" class="cn.lyn4ever.aop.aopconfig.Student">
 </bean>

 <!--注入teacher-->
 <bean name="teacherOne" class="cn.lyn4ever.aop.aopconfig.Teacher">
  <!--注意,这个student的属性要是上边的代理类,而不是能student-->
  <!--<property name="student" ref="student"/>-->
  <property name="student" ref="proxyOne"/>
 </bean>

 <!--注入我们创建的通知类-->
 <bean id="advice" class="cn.lyn4ever.aop.aopconfig.AuditAdvice"></bean>

 <!--创建代理类,使用前边写的通知进行通知,这样会使这个类上的所有方法都被通知-->
 <bean name="proxyOne" class="org.springframework.aop.framework.ProxyFactoryBean" p:target-ref="student"
   p:interceptorNames-ref="interceptorNames">
  <!--因为interceptorNames的属性是一个可变参数,也就是一个list-->
 </bean>

 <!--在上边引入了util的名称空间,简化了书写-->
 <util:list id="interceptorNames">
  <value>advice</value>
 </util:list>

 <!--以下是使用切入点的方式来进行通知,上边的代码和上一个配置文件一样,没有修改-->
 <!--sutdent基本bean,我们继续使用-->

 <bean name="teacherTwo" p:student-ref="proxyTwo" class="cn.lyn4ever.aop.aopconfig.Teacher"/>

 <bean id="proxyTwo" class="org.springframework.aop.framework.ProxyFactoryBean"
   p:target-ref="student" p:interceptorNames-ref="interceptorAdvisorNames">
 </bean>

 <util:list id="interceptorAdvisorNames">
  <value>advisor</value>
 </util:list>

 <!--配置切入点bean-->
 <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
   p:advice-ref="advice">
  <property name="pointcut">
   <!--这个切入点我们用了一个匿名bean来写aspectJ的表达式,当然也可以用其他的类型切入点,这个在上边链接中能看到-->
   <bean class="org.springframework.aop.aspectj.AspectJExpressionPointcut"
     p:expression="execution(* talk*(..))"/>
  </property>

 </bean>

</beans>

上图中的那个aspectj表达式写错了,在代码中有正确的

2.使用aop名称空间

在xml中引入如下的名称空间,为了不被影响,我册了其他多余的名称空间。然后很普通地注入我们之前那三个bean

<?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.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop.xsd
 ">

 <!--通过普通的方式来注入三个bean-->
 <!--注入student-->
 <bean name="student" class="cn.lyn4ever.aop.aopconfig.Student"/>
 <!--注入teacher-->
 <bean name="teacherOne" class="cn.lyn4ever.aop.aopconfig.Teacher">
  <property name="student" ref="student"/>
 </bean>
 <!--注入我们创建的通知类-->
 <bean id="advice" class="cn.lyn4ever.aop.proxyfactory.BeforeAdvice"/>

 <aop:config>
  <aop:pointcut id="talkExecution" expression="execution(* talk*(..))"/>
  <aop:aspect ref="advice">
   <!--这个方法就是我们在自定义通知类中之写的方法-->
   <aop:before method="beforeSaySomething" pointcut-ref="talkExecution"/>
   <!--当然,还可以配置其他通知类型-->
  </aop:aspect>
 </aop:config>

</beans>

在这个配置中,我们还可以配置其他类型的通知,但是这个method属性一定要写我们自定义的那个通知类中的方法

在aop:pointcut中写expression时还支持如下语法:

<aop:pointcut id="talkExecution" expression="execution(* talk*(..)) and args(String) and bean(stu*)"/>
<!--
中间的这个and表示和,也可以用or来表示或
args(String) 意思是参数类型是string,也可是自定义的类,这个后边有例子
bean(stu*) 意思是bean的id是以stu开头的,常用的就是用bean(*Service*)来表示服务层的bean
-->

3.使用@AspectJ样式注解方式

虽然是通过注解的方式来声明注解类,但是还是需要在xml中配置一点点内容(通过注解的方式也可以配置,但是在springboot中要使用的话有更方便的方式)

为了方便,就只写了一个HighStudent,而且直接调用它的方法,不依赖于外部的teacher实例来调用

package cn.lyn4ever.aop.aspectj;

import cn.lyn4ever.aop.aopconfig.Teacher;
import org.springframework.stereotype.Component;

/**
 * 声明这是一个SpringBean,由Spring来管理它
 */
@Component
public class HighStudent {

 public void talk() {
  System.out.println("I am a boy");
 }

 public void walk() {
  System.out.println("I am walking");
 }

 /**
  * 这个方法添加一个teacher来做为参数,为了配置后边切入点中的args()
  * @param teacher
  */
 public void sleep(Teacher teacher) {
  System.out.println("I want to sleep");
 }
}

创建切面类

package cn.lyn4ever.aop.aspectj;

import cn.lyn4ever.aop.aopconfig.Teacher;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
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 //声明交由spring管理
@Aspect //表示这是一个切面类
public class AnnotatedAdvice {

 /*
 创建切入点,当然也可以是多个
  */
 @Pointcut("execution(* talk*(..))")
 public void talkExecution(){}

 @Pointcut("bean(high*)")//这里为什么是high,因为我们这回测试bean是highStudent
 public void beanPoint(){}

 @Pointcut("args(value)")
 public void argsPoint(Teacher value){}

 /*
 创建通知,当然也可以是多个
 这个注解的参数就是上边的切入点方法名,注意有的还带参数
 这个通知方法的参数和之前一样,榀加JoinPoint,也可不加
  */
 @Before("talkExecution()")
 public void doSomethingBefore(JoinPoint joinPoint){
  System.out.println("before: Do Something"+joinPoint.getSignature().getName()+"()");
 }

 /**
  * 环绕通知请加上ProceedingJoinPoint参数 ,它是joinPoint的子类
  * 因为你要放行方法的话,必须要加这个
  * @param joinPoint
  * @param teacher
  */
 @Around("argsPoint(teacher) && beanPoint()")
 public Object doSomethindAround(ProceedingJoinPoint joinPoint, Teacher teacher) throws Throwable {
  System.out.println("Around: Before Do Something"+joinPoint.getSignature().getName()+"()");
  Object proceed = joinPoint.proceed();
  System.out.println("Around: After Do Something"+joinPoint.getSignature().getName()+"()");

  return proceed;
 }

}

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:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

 <!--通知Spring扫描@Aspect注解-->
 <aop:aspectj-autoproxy/>

 <!--配置扫描包,扫描@Component-->
 <context:component-scan base-package="cn.lyn4ever.aop.aspectj"/>

</beans>

使用Java注解配置的方式配置扫描注解

@Configuration //声明这是一个配置类
@ComponentScan("cn.lyn4ever.aop.aspectj")
@EnableAspectJAutoProxy(proxyTargetClass = true)//相当于xml中的<aop:aspectj-autoproxy/>
public class BeanConfig {
}

测试方法

package cn.lyn4ever.aop.aspectj;

import cn.lyn4ever.aop.aopconfig.Teacher;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class AspectMain {
 public static void main(String[] args) {
//  xmlConfig();
  javaConfig();

 }

 private static void javaConfig() {
  GenericApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
  HighStudent student = (HighStudent) context.getBean("highStudent");
  student.sleep(new Teacher());//应该被环绕通知
  System.out.println();

  student.talk();//前置通知
  System.out.println();

  student.walk();//不会被通知
  System.out.println();
 }

 private static void xmlConfig(){
  GenericXmlApplicationContext context = new GenericXmlApplicationContext();
  context.load("application_aspect.xml");
  context.refresh();

  HighStudent student = (HighStudent) context.getBean("highStudent");
  student.sleep(new Teacher());//应该被环绕通知
  System.out.println();

  student.talk();//前置通知
  System.out.println();

  student.walk();//不会被通知
  System.out.println();
 }
}

项目代码地址,如果觉得还不错的话,给个star吧

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • Spring Boot中使用AOP统一处理web层异常的方法

    在springboot错误默认是跳转到 请求返回渲染路径中的error/错误页面中. 源码分析:DefaultErrorViewResolver.java private ModelAndView resolve(String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName; TemplateAvailabilityProvider provider = th

  • spring boot如何使用AOP统一处理web请求

    为了保证服务的高可用,及时发现问题,迅速解决问题,为应用添加log是必不可少的. 但是随着项目的增大,方法增多,每个方法加单独加日志处理会有很多冗余 那在SpringBoot项目中如何统一的处理Web请求日志? 基本思想: 采用AOP的方式,拦截请求,写入日志 AOP 是面向切面的编程,就是在运行期通过动态代理的方式对代码进行增强处理 基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率.

  • SpringAOP+RabbitMQ+WebSocket实战详解

    背景 最近公司的客户要求,分配给员工的任务除了有微信通知外,还希望PC端的网页也能实时收到通知.管理员分配任务是在我们的系统A,而员工接受任务是在系统B.两个系统都是现在已投入使用的系统. 技术选型 根据需求我们最终选用SpringAOP+RabbitMQ+WebSocket. SpringAOP可以让我们不修改原有代码,直接将原有service作为切点,加入切面.RabbitMQ可以让A系统和B系统解耦.WebSocket则可以达到实时通知的要求. SpringAOP AOP称为面向切面编程,

  • 详解Spring Boot中使用AOP统一处理Web请求日志

    在spring boot中,简单几步,使用spring AOP实现一个拦截器: 1.引入依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframewo

  • Spring AOP在web应用中的使用方法实例

    前言 之前的aop是通过手动创建代理类来进行通知的,但是在日常开发中,我们并不愿意在代码中硬编码这些代理类,我们更愿意使用DI和IOC来管理aop代理类.Spring为我们提供了以下方式来使用aop框架 一.以声明的方式配置AOP(就是使用xml配置文件) 1.使用ProxyFactoryBean的方式: ProxyFactoryBean类是FactoryBean的一个实现类,它允许指定一个bean作为目标,并且为该bean提供一组通知和顾问(这些通知和顾问最终会被合并到一个AOP代理中)它和我

  • 解决Spring AOP拦截抽象类(父类)中方法失效问题

    目录 背景 原因分析 解决方案 后记 背景 最近工作中需要对组内各个系统依赖的第三方接口进行监控报警,对于下游出现问题的接口能够及时感知.首先我们写了一个Spring AOP注解,用于收集调用第三方时返回的信息.而我们调用第三方的类抽象出一个父类.并在父类的方法中加入我们的自定义注解用于监控日志并打印日志. 很多子类继承了这个父类并使用父类中的方法.如: 当调用子类的doSomething方法时问题出现了,发现Spring AOP没有拦截doPost()方法.而将注解加在子类方法上时,Sprin

  • Spring Boot在Web应用中基于JdbcRealm安全验证过程

    目录 正文 01-RBAC 基于角色的访问控制 02-Shiro 中基于 JdbcRealm 实现用户认证.授权 03-集成到 Spring Boot Web 应用中 04-总结 正文 在安全领域,Subject 用来指代与系统交互的实体,可以是用户.第三方应用等,它是安全认证框架(例如 Shiro)验证的主题. Principal 是 Subject 具有的属性,例如用户名.身份证号.电话号码.邮箱等任何安全验证过程中关心的要素. Primary principal 指能够唯一区分 Subje

  • spring AOP自定义注解方式实现日志管理的实例讲解

    今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在applicationContext-mvc.xml中要添加的 <mvc:annotation-driven /> <!-- 激活组件扫描功能,在包com.gcx及其子包下面自动扫描通过注解配置的组件 --> <context:component-scan base-package=&qu

  • spring中FactoryBean中的getObject()方法实例解析

    本文研究的主要是spring中FactoryBean中的getObject()方法的相关内容,具体如下. FactoryBean接口定义了以下3个接口方法: Object getObject():返回有FactoryBean创建的Bean实例,如果isSingleton()返回true,则该实例会放到Spring容器的单实例缓存池中. boolean isSingleton():确定由FactoryBean创建Bean的作用域是singleton还是prototype. Class getObj

  • Kotlin 语言中调用 JavaScript 方法实例详解

    Kotlin 语言中调用 JavaScript 方法实例详解 Kotlin 已被设计为能够与 Java 平台轻松互操作.它将 Java 类视为 Kotlin 类,并且 Java 也将 Kotlin 类视为 Java 类.但是,JavaScript 是一种动态类型语言,这意味着它不会在编译期检查类型.你可以通过动态类型在 Kotlin 中自由地与 JavaScript 交流,但是如果你想要 Kotlin 类型系统的全部威力 ,你可以为 JavaScript 库创建 Kotlin 头文件. 内联 J

  • jQuery中ajax - get() 方法实例详解

    在jquery中使用get,post和ajax方法给服务器端传递数据,在上篇文章给大家分享了jquery中ajax-post()方法实例,下面通过本文继续学习jQuery中ajax - get() 方法,具体介绍请看下文. jQuery Ajax 参考手册 实例 使用 AJAX 的 GET 请求来改变 div 元素的文本: $("button").click(function(){ $.get("demo_ajax_load.txt", function(resul

  • jsp 中HttpClient中的POST方法实例详解

    jsp 中HttpClient中的POST方法实例详解 POST方法用来向目的服务器发出请求,要求它接受被附在请求后的实体,并把它当作请求队列(Request-Line)中请求URI所指定资源的附加新子项.POST被设计成用统一的方法实现下列功能: 对现有资源的注释 向电子公告栏.新闻组,邮件列表或类似讨论组发送消息 提交数据块,如将表单的结果提交给数据处理过程 通过附加操作来扩展数据库 调用HttpClient中的PostMethod与GetMethod类似,除了设置PostMethod的实例

  • java中的 toString()方法实例代码

    前言: toString()方法 相信大家都用到过,一般用于以字符串的形式返回对象的相关数据. 最近项目中需要对一个ArrayList<ArrayList<Integer>> datas  形式的集合处理. 处理要求把集合数据转换成字符串形式,格式为 :子集合1数据+"#"+子集合2数据+"#"+....+子集合n数据. 举例: 集合数据 :[[1,2,3],[2,3,5]]  要求转成为 "[1,2,3]#[2,3,5]"

  • JavaScript 中调用 Kotlin 方法实例详解

    JavaScript 中调用 Kotlin 方法实例详解 Kotlin 编译器生成正常的 JavaScript 类,可以在 JavaScript 代码中自由地使用的函数和属性 .不过,你应该记住一些微妙的事情. 用独立的 JavaScript 隔离声明 为了防止损坏全局对象,Kotlin 创建一个包含当前模块中所有 Kotlin 声明的对象 .所以如果你把模块命名为 myModule,那么所有的声明都可以通过 myModule 对象在 JavaScript 中可用.例如: fun foo() =

随机推荐