Spring之AOP简介

定义

AOP: 全称是 Aspect Oriented Programming 即: 面向切面编程
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强

AOP 的作用及优势

作用:在程序运行期间,不修改源码对已有方法进行增强。
优势
减少重复代码
提高开发效率
维护方便
实现方式
使用动态代理技术

术语

Joinpoint(连接点)

所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点

ProceedingJoinPoint的使用和作用

ProceedingJoinPoint继承了Joinpoint,所有ProceedingJoinPoint具有连接点的特性
注意:ProceedingJoinPoint只能在循环通知中使用,并且将结果返回,如果不返回相当于结果再此被拦截,请求方收到的就是空数据
使用:ProceedingJoinPoint作为通知方法的参数被传入
代码案例:

import com.dianping.cat.Cat;
import com.dianping.cat.message.Transaction;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class CatAopService {

	@Around(value = "@annotation(catAnnotation)")
	public Object aroundMethod(ProceedingJoinPoint pjp, CatAnnotation catAnnotation) throws Throwable {
		MethodSignature joinPointObject = (MethodSignature) pjp.getSignature();
		Method method = joinPointObject.getMethod();
		Transaction t = Cat.newTransaction(method.getName(), method.getName());
		try {
			Object res = pjp.proceed();
			t.setSuccessStatus();
			return res;
		} catch (Throwable e) {
			t.setStatus(e);
			Cat.logError(e);
			throw e;
		} finally {
			t.complete();
		}
	}
}
  1. 获取切入点方法的名字:getSignature());是获取到这样的信息 :修饰符+ 包名+组件名(类名) +方法名
  2. 获取方法上的注解:
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();

if (method != null)
{
    xxxxxx annoObj= method.getAnnotation(xxxxxx.class);
}
  1. 获取方法的参数:Object[] args = joinPoint.getArgs(); ,此处是获取方法上的参数,而不是HttpServletRequest对象的请求参数,所以GET和POST方法的请求参数都可以通过此方法获取
  2. 放行并获取返回结果:Object result = proceedingJoinPoint.proceed();

Pointcut(切入点)

所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义

切入点表达式说明

表达式语法: execution([修饰符] 返回值类型 包名.类名.方法名(参数))
全匹配方式
public void com.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)

  • 访问修饰符可以省略
  • 包名可以使用*号,表示任意包,但是有几级包,需要写几个*,使用..来表示当前包,及其子包
  • 类名可以使用*号,表示任意类
  • 方法名可以使用*号,表示任意方法
  • 参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数,也可以使用..表示有无参数均可,有参数可以是任意类型
  • 全通配方式:* *..*.*(..)
  • 通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。execution(* com.service.impl.*.*(..))
注解的方式

使用within表达式匹配包类型

//匹配ProductServiceImpl类里面的所有方法
@Pointcut("within(com.aop.service.impl.ProductServiceImpl)")
public void matchType() {}

//匹配com.aop.service包及其子包下所有类的方法
@Pointcut("within(com.aop.service..*)")
public void matchPackage() {}

使用this、target、bean表达式匹配对象类型

//匹配AOP对象的目标对象为指定类型的方法,即ProductServiceImpl的aop代理对象的方法
@Pointcut("this(com.aop.service.impl.ProductServiceImpl)")
public void matchThis() {}

//匹配实现ProductService接口的目标对象
@Pointcut("target(com.aop.service.ProductService)")
public void matchTarget() {}

//匹配所有以Service结尾的bean里面的方法
@Pointcut("bean(*Service)")
public void matchBean() {}

使用args表达式匹配参数

//匹配第一个参数为Long类型的方法
@Pointcut("args(Long, ..) ")
public void matchArgs() {}

使用@annotation、@within、@target、@args匹配注解

//匹配标注有AdminOnly注解的方法,比较灵活,也常用
@Pointcut("@annotation(com.aop.annotation.AdminOnly)")
public void matchAnno() {}

//匹配标注有Beta的类底下的方法,要求annotation的Retention级别为CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void matchWithin() {}

//匹配标注有Repository的类底下的方法,要求annotation的Retention级别为RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void matchTarget() {}

//匹配传入的参数类标注有Repository注解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")
public void matchArgs() {}

Advice(通知/增强)

所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。

Target(目标对象)

代理的目标对象

Weaving(织入)

是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入

Proxy(代理)

一个类被 AOP 织入增强后,就产生一个结果代理类

Aspect(切面)

是切入点和通知(引介)的结合

代码案例

注解的方式

需要用到的依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<!-- AOP注解用到的 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.0.6.RELEASE</version>
</dependency>

applicationContext.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       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
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--开启包扫描-->
    <context:component-scan base-package="com.springtest"></context:component-scan>
    <bean id="chechAgeAspect" class="com.aspect.ChechAgeAspect"></bean>
    <!--下面这段是强制使用CGLIB代理模式
    分步讲解:aop:aspectj-autoproxy的作用开启AOP的注解
    proxy-target-class="true"是强制使用CGLIB创建代理
    expose-proxy="true"实现类的内部调用也可以增强方法,
    不过就不能直接调用内部方法了,
    需要用((Student) AopContext.currentProxy()).internalMethod();,(Student)为需要类本身,internalMethod()为Student的内部类,当Student的其他方法调用internalMethod方法,
    且internalMethod方法要实现增强-->
    <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
</beans>

切面类

@Component
@Aspect
public class ChechAgeAspect {
    @Pointcut("execution(* com.springtest.*.*(..))")
    public void expressionMethod() {
    }
    @Before("expressionMethod()") // 表达式的方式。此种方式下面的方法中不能有参数,两种方式都可以
	//@annotation属性的参数可以引用方法中的参数,必须和方法中的参数名一致,也可以使用全限定类名
    @Before("@annotation(myAspectAnnotation)")// 注解的方式
    public void pointCutMethod(MyAspectAnnotation myAspectAnnotation) {
        System.out.println("执行了切面中的方法");
    }
}

Student测试类

@Data
@Component
public class Student {
    @MyAspectAnnotation
    public void testAspect() {
        System.out.println("测试方法");
    }
}

自定义注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspectAnnotation {
}

测试类

public class SpringMainClass {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Teacher student = context.getBean(Teacher.class);
        student.invokeTestAspect();
    }
}

xml配置文件的方式

applicationContext.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       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 http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--开启包扫描-->
    <context:component-scan base-package="com.springtest"></context:component-scan>
    <!--切面类-->
    <bean id="checkAspect" class="com.aspect.CheckAspect"></bean>
    <!--切面配置-->
    <aop:config>
        <!--id将被aop:before的pointcut-ref属性值引用-->
        <aop:pointcut id="adviseMethod" expression="execution(* com.springtest..*.*(..))"/>
        <!--id起的有意义即可,ref引用的是切面类-->
        <aop:aspect id="checkAspectAdviseClass" ref="checkAspect">
            <!--method即增强的方法,pointcut-ref引用的是aop:pointcut的id属性值-->
            <aop:before method="pointCutMethod" pointcut-ref="adviseMethod"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

参考:https://www.cnblogs.com/liantdev/p/10125284.html

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值