Spring-AOP内容详解

提示:以下是本篇文章正文内容,下面案例可供参考

一、AOP 简介

        AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程OOP的升华。OOP是纵向对一个 事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属 性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程


二、基于XML

1、快速构建

1)导入AOP相关坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>springDemo06-xmlAOP</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
    </dependencies>
</project>
2)准备目标类、通知类
//通知类
public class MyAdvice {
    
    public void beforeAdvice() {
        System.out.println("通知方法执行....");
    }
}


//目标类
public class UserServiceImpl implements UserService{
    public void save() {
        System.out.println("save方法执行了....");
    }
}
3)配置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: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
">

    <bean id="userService" class="com.jh.service.UserServiceImpl"/>
    <bean id="myAdvice" class="com.jh.advice.MyAdvice"/>

    <!--  AOP 配置  -->
    <aop:config>
        <!--配置切面表达式,目的是指定哪些方法被增强-->
        <aop:pointcut id="myPointcut" expression="execution(* com.jh.service.*.*( ))"/>
        <!--切面=切点+通知-->
        <aop:aspect ref="myAdvice">
            <!--指定通知方法是beforeAdvice-->
            <aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>

</beans>

 

 2、切点表达式

1)配置方式

第一种在快速构建中已经体现,使用<aop:pointcut>配置切点

第二种将切点直接在通知中配置,例如:

        <!--  AOP 配置  -->
    <aop:config>
        <!--切面=切点+通知-->
        <aop:aspect ref="myAdvice">
            <!--指定前置通知方法是beforeAdvice-->
            <!--切点直接配置-->
            <aop:before method="beforeAdvice" pointcut="execution(* com.jh.service.*.*( ))"/>
            <!--指定后置通知方法是afterAdvice-->
            <aop:after-returning method="afterAdvice" pointcut="execution(* com.jh.service.*.*( ))"/>
        </aop:aspect>
    </aop:config>
 2)切点表达式语法

execution( [访问修饰符] 返回值类型 包名.类名.方法名(参数) )

⚫ 访问修饰符可以省略不写;

⚫ 返回值类型、某一级包名、类名、方法名 可以使用 * 表示任意;

⚫ 包名与类名之间使用单点 . 表示该包下的类,使用双点 .. 表示该包及其子包下的类;

⚫ 参数列表可以使用两个点 .. 表示任意参数。

例:

//表示访问修饰符为public、无返回值、在com.jh.aop包下的User类的无参方法show execution(public void com.jh.aop.User.show())

//表述com.jh.aop包下的User类的任意方法

execution(* com.jh.aop.User.*(..))

//表示com.jh.aop包下的任意类的任意方法

execution(* com.jh.aop.*.*(..))

//表示com.jh.aop包及其子包下的任意类的任意方法

execution(* com.jh.aop..*.*(..))

//表示任意包中的任意类的任意方法

execution(* *..*.*(..))

 3、通知类型

1)AspectJ通知五种类型
通知类型配置标签执行方式
前置通知< aop:before >在目标方法执行之前执行

后置通知

< aop:after-returning >在目标方法执行之后执行,目标方法异常时,不会执行
环绕通知< aop:around >在目标方法执行的前后执行,目标方法异常时,环绕后方法不会执行
异常通知< aop:after-throwing >只会在目标方法抛出异常时执行
最终通知< aop:after >不管目标方法是否有异常,最终都会执行

2.)代码场景
    <!--  AOP 配置  -->
    <aop:config>
        <!--配置切面表达式,目的是指定哪些方法被增强-->
        <aop:pointcut id="myPointcut" expression="execution(* com.jh.service.*.*( ))"/>
        <!--切面通知-->
        <aop:aspect ref="myAdvice">
            <!--指定前置通知方法是before-->
            <aop:before method="before" pointcut-ref="myPointcut"/>
            <!--指定环绕通知方法是around-->
            <aop:around method="around" pointcut-ref="myPointcut"/>
            <!--指定后置通知方法是afterReturning-->
            <aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
            <!--指定异常通知方法是afterThrowing-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
            <!--指定最终通知方法是after-->
            <aop:after method="after" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>

public class MyAdvice {

    /**
     * 前置通知
     */
    public void before() {
        System.out.println("前置通知....");
    }

    /**
     * 环绕通知
     * @param joinPoint
     * @throws Throwable
     */
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        //环绕前
        System.out.println("环绕前通知");
        //目标方法
        joinPoint.proceed();
        ///环绕后
        System.out.println("环绕后通知");
    }

    /**
     * 后置通知
     */
    public void afterReturning() {
        System.out.println("后置通知...");
    }

    /**
     * 异常通知
     */
    public void afterThrowing(){
        System.out.println("目标方法抛出异常了,后置通知和环绕后通知不在执行");
    }

    /**
     * 最终通知
     */
    public void after(){
        System.out.println("不管目标方法有无异常,我都会执行");
    }
}

 4、通知方法参数

参数对象作用
JoinPoint任何通知都可使用,可以获得当前目标对象、目标方法参数等信息
ProceedingJoinPointJoinPoint子类对象,主要是在环绕通知中执行proceed(),进而执行目标方法
Throwable异常对象,使用在异常通知中,需要在配置文件中指出异常对象名称
1)JoinPoint对象

此处以后置通知为例对save方法增强:

public class App {

    public static void main(String[] args) {
        //加载配置文件
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        //加载bean
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.save("success");
    }
}

public class UserServiceImpl implements UserService{

    public String save(String name) {

        return name;
    }
}
    /**
     * 后置通知
     */
    public void afterReturning(JoinPoint joinPoint) {
        //获得目标方法的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("参数:"+args[0]);
        //获得目标对象
        System.out.println("目标对象"+joinPoint.getTarget());
        //获得精确的切点表达式信息
        System.out.println("切点表达式信息"+joinPoint.getStaticPart());
        System.out.println("后置通知执行....");
    }

 控制台:

2)ProceedingJoinPoint对象

与JoinPoint差异不大,主要在环绕通知中使用

    /**
     * 环绕通知
     *
     * @param joinPoint
     * @throws Throwable
     */
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println(joinPoint.getArgs());//获得目标方法的参数
        System.out.println(joinPoint.getTarget());//获得目标对象
        System.out.println(joinPoint.getStaticPart());//获得精确的切点表达式信息
        Object result = joinPoint.proceed();//执行目标方法
        return result;//返回目标方法返回值
    }
3)Throwable对象

注意:throwing="throwable"不要忘了配置

 <!--  AOP 配置  -->
    <aop:config>
        <!--配置切面表达式,目的是指定哪些方法被增强-->
        <aop:pointcut id="myPointcut" expression="execution(* com.jh.service.*.*(*))"/>
        <!--切面=切点+通知-->
        <aop:aspect ref="myAdvice">
            <!--异常通知方法,如果有入参Throwable对象需要配置-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="throwable"/>
        </aop:aspect>
    </aop:config>
public class UserServiceImpl implements UserService{

    public String save(String name) {
        //此处会产生运算异常
        int i = 1/0;
        return name;
    }
}
    /**
     * 异常通知
     */
    public void afterThrowing(JoinPoint joinPoint,Throwable throwable) {
        //获得异常信息
        System.out.println("异常对象是:"+throwable+"异常信息是:"+throwable.getMessage());
        System.out.println("目标方法抛出异常了,后置通知和环绕后通知不在执行");
    }

 控制台

 

5、扩展点Advisor

AOP的另一种配置方式,该方式需要通知类实现Advice的子功能接口

package org.aopalliance.aop;

/**
 * Tag interface for Advice. Implementations can be any type
 * of advice, such as Interceptors.
 *
 * @author Rod Johnson
 * @version $Id: Advice.java,v 1.1 2004/03/19 17:02:16 johnsonr Exp $
 */
public interface Advice {

}
 1)Advice接口继承关系

 2)代码场景

可以看到在xml中没有像AspectJ通知那样标明各个通知类型,只配置了切点,这是因为通知类实现了Advice子接口,Spring框架自动能够识别

  <bean id="myAdvice2" class="com.jh.advice.MyAdvice2"/>
    <aop:config>
        <!--配置切面表达式,目的是指定哪些方法被增强-->
        <aop:pointcut id="myPointcut" expression="execution(* com.jh.service.*.*(*))"/>
        <aop:advisor advice-ref="myAdvice2" pointcut-ref="myPointcut"/>
    </aop:config>
/**
 * 实现Advice子接口的自定义通知类
 */
public class MyAdvice2 implements MethodBeforeAdvice, AfterReturningAdvice , MethodInterceptor {

    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("相当于前置通知....");
    }

    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("相当于前置通知....");
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("相当于环绕前....");
        //目标方法
        //invocation.getThis() 目标方法对象
        //invocation.getArguments() 目标方法参数
        Object invoke = invocation.getMethod().invoke(invocation.getThis(), invocation.getArguments());
        System.out.println("相当于环绕=后....");
        return null;
    }
}
 3)与AspectJ的差异

⚫ 配置语法不同

AspectJ通知需要标明通知类型,Advisor不需要,因为实现了Advice子接口

⚫ 通知类的定义要求不同

Advisor需要的通知类需要实现Advice的子功能接口,aspect 不需要通知类实现任何接口,在配置的时候指定哪些方法属于哪种通知类型即可

⚫ 配置的切面数量不同

一个Advisor只能配置一个固定通知和一个切点表达式,一个aspect可以配置多个通知和多个切点表达式任意组合

⚫ 使用场景

  1. 如果通知类型多、允许随意搭配情况下可以使用aspect进行配置
  2. 如果通知类型单一、且通知类中通知方法一次性都会使用到的情况下可以使用advisor进行配置
  3. 在通知类型已经固定,不用人为指定通知类型时,可以使用advisor进行配置,例如Spring事务控制的配置
  4. 实际开发中,自定义aop功能的配置大多使用aspect的配置方式

 

6、xml方式AOP原理剖析

xml方式配置AOP时,由于引入了AOP的命名空间,我们先去找aop标签解析器,在spring-aop包下的META-INF文件夹下找spring.handlers文件

 spring.handlers中配置的是:

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

 打开AopNamespaceHandler类:

public class AopNamespaceHandler extends NamespaceHandlerSupport {

	/**
	 * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
	 * '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
	 * and '{@code scoped-proxy}' tags.
	 */
	@Override
	public void init() {
		// In 2.0 XSD as well as in 2.5+ XSDs
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

		// Only in 2.0 XSD: moved to context namespace in 2.5+
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}

}

Handler的init方法中注册了config标签对应的解析器

registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());

从类ConfigBeanDefinitionParser中进去主要看parse方法

 在进行源码剖析后,发现最终会注册一个AspectJAwareAdvisorAutoProxyCreator 进入到Spring容器中

 AspectJAwareAdvisorAutoProxyCreator类的继承关系:

AspectJAwareAdvisorAutoProxyCreator其实就是BeanPostProcessor(bean后处理器)的实现类

AspectJAwareAdvisorAutoProxyCreator 的上上级父类AbstractAutoProxyCreator中的 postProcessAfterInitialization方法中(其实就是BeanPostProcessor的实现方法),进行判断是否需要增强,需要的话就返回一个Proxy对象

//参数bean:为目标对象
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
//如果需要被增强,则wrapIfNecessary方法最终返回的就是一个Proxy对象
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}

 查看wrapIfNecessary方法源码找到最终通过proxyFactory.getProxy(getProxyClassLoader())

方法获取一个代理对象,代理对象的产生方式就是下图中的两种

 

 AopProxy接口有两个实现类,如上图,这两种 都是动态生成代理对象的方式,一种就是基于JDK的,一种是基于Cglib的

 


三、基于注解

1、注解方式AOP

1)启用AspectJ自动代理

Spring的AOP提供了注解方式配置,使用相应的注解替代之前的xml配置

 注意:使用注解方式配置AOP,Spring核心配置文件中需要配置aspectj的自动代理

<aop:aspectj-autoproxy/>

 如果使用配置类的话,使用注解@EnableAspectJAutoProxy配置aspectj的自动代理

@Configuration
@ComponentScan("com.jh")
@EnableAspectJAutoProxy //第三步
public class ApplicationContextConfig {

}
 2)代码场景

通知类:

@Component
@Aspect //此注解表示该类是通知类
public class MyAdvice {
//切点表达式抽取
@Pointcut("execution(* com.jh.*.*.*(..))")
public void pointcut(){}
//前置通知
@Before("pointcut()")
public void before(JoinPoint joinPoint){}
}

五种注解方式通知类型:

@Component
@Aspect //此注解表示该类是通知类
public class MyAdvice {

//切点表达式抽取
//@Pointcut("execution(* com.jh.*.*.*(..))")
//public void pointcut(){}

//切点表达式直接配置在通知方法中

//前置通知
@Before("execution(* com.jh.*.*.*(..))")
public void before(JoinPoint joinPoint){}
//后置通知
@AfterReturning("execution(* com.jh.*.*.*(..))")
public void AfterReturning(JoinPoint joinPoint){}
//环绕通知
@Around("execution(* com.jh.*.*.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {}
//异常通知
@AfterThrowing("execution(* com.jh.*.*.*(..))")
public void AfterThrowing(JoinPoint joinPoint){}
//最终通知
@After("execution(* com.jh.*.*.*(..))")
public void After(JoinPoint joinPoint){}
}

2、注解方式AOP原理剖析

通过注解@EnableAspectJAutoProxy追踪源码

@Configuration
@ComponentScan("com.jh")
@EnableAspectJAutoProxy
public class SpringConfig {
}

 @Import(AspectJAutoProxyRegistrar.class),Import注解导入了解析类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	/**
	 * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
	 * to standard Java interface-based proxies. The default is {@code false}.
	 */
	boolean proxyTargetClass() default false;

	/**
	 * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
	 * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
	 * Off by default, i.e. no guarantees that {@code AopContext} access will work.
	 * @since 4.3.1
	 */
	boolean exposeProxy() default false;

}

 注册AnnotationAwareAspectJAutoProxyCreator类到Spring容器

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	/**
	 * Register, escalate, and configure the AspectJ auto proxy creator based on the value
	 * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
	 * {@code @Configuration} class.
	 */
	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        //去注册AnnotationAwareAspectJAutoProxyCreator类到Spring容器
		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}

}

查看AnnotationAwareAspectJAutoProxyCreator类的继承关系可知,它也是

BeanPostProcessor(bean后处理器)的实现类,那么基本上就与xml方式的底层逻辑大同小异了

 

 


四、两种动态代理方式

方式使用条件
JDK动态代理目标类有接口,是基于接口动态生成实现类的代理对象
CGLib动态代理目标类无接口且不能使用final修饰,是基于被代理对象动态生成子对象为代理对象

1、JDK动态代理

//todo


2、CGLib动态代理

1)编写目标对象
/**
 * 通知类
 */
public class MyAdvice {

    public void before(){
        System.out.println("前置方法....");
    }

    public void after(){
        System.out.println("后置方法....");
    }
}


/**
 * 目标类
 */
public class UserServiceImpl {
    public void show(){
        System.out.println("show方法执行....");
    }
}
 2)编写Cglib代码
public class MyCglibProxy {
    public static void main(String[] args) {
        //增强器对象
        Enhancer enhancer = new Enhancer();
        //设置增强器
        //设置父类
        enhancer.setSuperclass(UserServiceImpl.class);
        //增强器设置回调
        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object o, java.lang.reflect.Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //目标对象
                UserServiceImpl userService = new UserServiceImpl();
                //通知对象
                MyAdvice myAdvice = new MyAdvice();
                //增强方法
                myAdvice.before();
                
                Object result = method.invoke(userService, objects);

                myAdvice.after();
                return result;
            }
        });
        //创建代理对象
        UserServiceImpl targetProxy = (UserServiceImpl) enhancer.create();
        //测试
        targetProxy.show();
    }
}


End

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值