• Spring AOP注解开发详解


    1. Spring中AOP的术语

    • Joinpoint(连接点) : 连接点是指那些被拦截到的方法。
    • Pointcut(切入点) : 切入点是指我们要对哪些Joinpoint进行拦截的定义。
    • Advice(通知/增强) : 通知是指拦截到Joinpoint之后所要做的事情。通知的类型包括:前置通知,后置通知,异常通知,最终通知,环绕通知。
    • Introduction(引介) : 引介是一种特殊的通知,在不修改类代码的前提下, 可以在运行期为类动态地添加方法或Field。
    • Target(目标对象) : 代理的目标对象。
    • Weaving(织入) : 指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
    • Proxy(代理): 一个类被AOP织入增强后,就产生一个结果代理类。
    • Aspect(切面) : 是切入点和通知(引介)的结合。

    2. Spring AOP 注解驱动开发入门案例

    需求:在执行service方法时输出执行日志。(除了业务层外,表现层和持久层也可以实现)

    代码如下:

    • 实体类
    /**
     * @author hao
    */
    public class User implements Serializable {
    	private String id;
    	private String username;
    	private String password;
    	private String email;
    	private Date birthday;
    	private String gender;
    	private String mobile;
    	private String nickname;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 业务层接口
    /**
     * @author hao
    */
    public interface UserService {
    	/**
    	* 保存用户
    	* @param user
    	*/
    	void save(User user);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 业务层实现类
    /**
     * @author hao
    */
    @Service("userService")
    public class UserServiceImpl implements UserService{
    	@Override
    	public void save(User user) {
    		System.out.println("保存用:"+user);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 日志工具类
    /**
     * @author hao
    */
    @Component
    @Aspect
    public class LogUtil {
    	/**
    	* 通用切入点表达式
    	*/
    	@Pointcut("execution(* com.test.service.impl.*.*(..))")
    	private void pt1(){}
    	
    	/**
    	* 前置通知
    	*/
    	@Before("pt1()")
    	public void beforeLog(){
    		System.out.println("执行切入点方法前记录日志");
    	}
    	
    	/**
    	* 后置通知
    	*/
    	@AfterReturning("pt1()")
    	public void afterReturningLog(){
    		System.out.println("正常执行切入点方法后记录日志");
    	}
    	
    	/**
    	* 异常通知
    	*/
    	@AfterThrowing("pt1()")
    	public void afterThrowingLog(){
    		System.out.println("执行切入点方法产生异常后记录日志");
    	}
    	
    	/**
    	* 最终通知
    	*/
    	@After("pt1()")
    	public void afterLog(){
    		System.out.println("无论切入点方法执行是否有异常都记录日志");
    	}
    	
    	/**
    	* 环绕通知
    	*/
    	@Around("pt1()")
    	public Object arountPrintLog(ProceedingJoinPoint pjp){
    		//1.定义返回值
    		Object rtValue = null;
    		try{
    			//前置通知
    			System.out.println("执行切入点方法前记录日志");
    			//2.获取方法执行所需的参数
    			Object[] args = pjp.getArgs();
    			//3.执行切入点方法
    			rtValue = pjp.proceed(args);
    			//后置通知
    			System.out.println("正常执行切入点方法后记录日志");
    		}catch (Throwable t){
    			//异常通知
    			System.out.println("执行切入点方法产生异常后记录日志");
    		}finally {
    			//最终通知
    			System.out.println("无论切入点方法执行是否有异常都记录日志");
    		}
    		return rtValue;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 配置类
    /**
     * @author hao
    */
    @Configuration
    @ComponentScan("com.test")
    @EnableAspectJAutoProxy
    public class SpringConfiguration {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 测试类
    /**
    * @author hao
    */
    public class SpringAOPTest {
    	public static void main(String[] args) {
    		//1.获取容器
    		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
    		//2.获取bean对象
    		UserService userService = ac.getBean("userService",UserService.class);
    		//3.准备数据
    		User user = new User();
    		user.setId("1");
    		user.setUsername("test");
    		user.setNickname("泰斯特");
    		//4.执行方法
    		userService.save(user);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3. AOP常用注解分析

    3.1 @EnableAspectJAutoProxy

    3.1.1 源码

    @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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.1.2 说明

    • 作用:
      表示开启spring对aop的注解支持。它有两个属性,分别是指定采用的代理方式和是否暴露代理对象,通过AopContext可以进行访问。
    • 属性:
      proxyTargetClass:指定是否采用cglib进行代理。默认值是false,表示使用jdk的代理。
      exposeProxy:指定是否暴露代理对象,通过AopContext可以进行访问。
    • 使用场景:
      当我们注解驱动开发时,在需要使用aop实现某些功能的情况下,都需要用到此注解。

    3.1.3 示例

    /**
    * @author hao
    */
    @Configuration
    @ComponentScan("com.itheima")
    @EnableAspectJAutoProxy
    public class SpringConfiguration {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.2 @Aspect

    3.2.1 源码

    /**
    * Aspect declaration
    *
    * @author Alexandre Vasseur
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Aspect {
    	/**
    	* Per clause expression, defaults to singleton aspect
    	* 

    * Valid values are "" (singleton), "perthis(...)", etc */ public String value() default ""; }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.2.2 说明

    • 作用:声明当前类是一个切面类。
    • 属性:
      value: 默认我们的切面类应该为单例的。当切面类为一个多例类时,指定预处理的切入点表达式。用法是 perthis(切入点表达式),它支持指定切入点表达式,或者是用@Pointcut修饰的方法名称(要求全限定方法名)
    • 使用场景:此注解也是一个注解驱动开发aop的必备注解。

    3.2.3 示例

    /**
    * @author hao
    */
    @Component
    @Scope("prototype")  //注意:通常情况下我们的切面类是不需要多例的。
    @Aspect(value="execution(* com.test.service.impl.*.*(..))")
    public class LogUtil {
    	/**
    	* 用于配置当前方法是一个前置通知
    	*/
    	@Before("execution(* com.itheima.service.impl.*.*(..))")
    	public void printLog(){
    		System.out.println("执行打印日志的功能");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.3 @Pointcut

    3.3.1 源码

    /**
    * Pointcut declaration
    *
    * @author Alexandre Vasseur
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Pointcut {
    	/**
    	* The pointcut expression
    	* We allow "" as default for abstract pointcut
    	*/
    	String value() default "";
    	/**
    	* When compiling without debug info, or when interpreting pointcuts at runtime,
    	* the names of any arguments used in the pointcut are not available.
    	* Under these circumstances only, it is necessary to provide the arg names in
    	* the annotation ‐ these MUST duplicate the names used in the annotated method.
    	* Format is a simple comma‐separated list.
    	*/
    	String argNames() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    3.3.2 说明

    • 作用:此注解是用于指定切入点表达式的。
    • 属性:
      value : 用于指定切入点表达式。
      argNames : 用于指定切入点表达式的参数。参数可以是execution中的,也可以是args中的。通常情况下不使用此属性也可以获得切入点方法参数。
    • 使用场景:
      在实际开发中,当我们的多个通知需要执行,同时在增强规则确定的情况下,就可以把切入点表达式通用化。此注解就是代替xml中的aop:pointcut标签,实现切入点表达式的通用化。

    3.3.3 示例

    @Component
    @Aspect
    public class LogUtil {
    	/**
    	* 通用切入点表达式
    	* 在value属性的中使用了&&符号,表示并且的关系。
    	* &&符号后面的args和execution一样,都是切入点表达式支持的关键字,表示匹配参数。指定的内容可以是全限定类名,或者是名称。当指定参数名称时,要求与方法中形参名称相同。
    	* argNames属性,是定义参数的名称,该名称必须和args关键字中的名称一致。
    	*/
    	@Pointcut(value = "execution(* com.test.service.impl.*.*
    	(com.test.domain.User))&& args(user)",argNames = "user")
    	private void pt1(User user){}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3.4 @Before

    3.4.1 源码

    /**
     * Before advice
     *  * @author Alexandre Vasseur
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Before {
    	/**
    	* The pointcut expression where to bind the advice
    	*/
    	String value();
    	
    	/**
    	* When compiling without debug info, or when interpreting pointcuts at runtime,
    	* the names of any arguments used in the advice declaration are not available.
    	* Under these circumstances only, it is necessary to provide the arg names in
    	* the annotation ‐ these MUST duplicate the names used in the annotated method.
    	* Format is a simple comma‐separated list.
    	*/
    	String argNames() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.4.2 说明

    • 作用:被此注解修饰的方法称为前置通知。前置通知是在切入点方法之前执行。
    • 属性:
      value : 用于指定切入点表达式,可以是表达式,也可以是表达式的引用。
      argNames : 用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称一致。通常不指定也可以获取切入点方法的参数内容。
    • 使用场景:
      在实际开发中,我们需要对切入点方法执行之前进行增强, 此时就用到了前置通知。在通知(增强的方法)中需要获取切入点方法中的参数进行处理时,就要配合切入点表达式参数来使用。

    3.4.3 示例

    /**
    * 前置通知
    */
    @Before(value = "pt1(user)",argNames = "user")
    public void beforeLog(User user){
    	System.out.println("执行切入点方法前记录日志"+user);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.5 @AfterReturning

    3.5.1 源码

    /**
    * After returning advice
    * @author Alexandre Vasseur
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface AfterReturning {
    	/**
    	* The pointcut expression where to bind the advice
    	*/
    	String value() default "";
    	
    	/**
    	* The pointcut expression where to bind the advice, overrides "value" when specified
    	*/
    	String pointcut() default "";
    	
    	/**
    	* The name of the argument in the advice signature to bind the returned value to
    	*/
    	String returning() default "";
    	
    	/**
    	* When compiling without debug info, or when interpreting pointcuts at runtime,
    	* the names of any arguments used in the advice declaration are not available.
    	* Under these circumstances only, it is necessary to provide the arg names in
    	* the annotation ‐ these MUST duplicate the names used in the annotated method.
    	* Format is a simple comma‐separated list.
    	*/
    	String argNames() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    3.5.2 说明

    • 作用:用于配置后置通知,后置通知的执行是在切入点方法正常执行之后执行。需要注意的是,由于基于注解配置时,spring创建通知方法的拦截器链时,后置通知在最终通知之后,所以会先执行@After注解修饰的方法。
    • 属性:
      value : 用于指定切入点表达式,可以是表达式,也可以是表达式的引用。
      pointcut : 它的作用和value是一样的。
      returning : 指定切入点方法返回值的变量名称。它必须和切入点方法返回值名称一致。
      argNames : 用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称
      一致。通常不指定也可以获取切入点方法的参数内容。
    • 使用场景:
      此注解是用于配置后置增强切入点方法。被此注解修饰方法会在切入点方法正常执行之后执行。在我们实际开发中,像提交事务,记录访问日志,统计方法执行效率等等都可以利用后置通知实现。

    3.5.3 示例

    // 切入点方法:
    @Override
    public User findById(String id) {
    	System.out.println("切入点方法开始执行。。。");
    	User user = new User();
    	user.setId(id);
    	user.setUsername("heima");
    	user.setNickname("黑马小王子");
    	return user;
    }
    
    /**
    * 后置通知
    */
    @AfterReturning(value = "execution(* com.test.service.impl.*.*
    (..))&&args(param)",returning = "user")
    public void afterReturningLog(String param,Object user){
    	System.out.println("正常执行切入点方法后记录日志,切入点方法的参数是:"+param);
    	System.out.println("正常执行切入点方法后记录日志,切入点方法的返回值是:"+user);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.6 @AfterThrowing

    3.6.1 源码

    /**
    * After throwing advice
    * @author Alexandre Vasseur
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface AfterThrowing {
    	/**
    	* The pointcut expression where to bind the advice
    	*/
    	String value() default "";
    	
    	/**
    	* The pointcut expression where to bind the advice, overrides "value" when specified
    	*/
    	String pointcut() default "";
    	
    	/**
    	* The name of the argument in the advice signature to bind the thrown exception to
    	*/
    	String throwing() default "";
    	
    	/**
    	* When compiling without debug info, or when interpreting pointcuts at runtime,
    	* the names of any arguments used in the advice declaration are not available.
    	* Under these circumstances only, it is necessary to provide the arg names in
    	* the annotation ‐ these MUST duplicate the names used in the annotated method.
    	* Format is a simple comma‐separated list.
    	*/
    	String argNames() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    3.6.2 说明

    • 作用:用于配置异常通知。
    • 属性:
      value : 用于指定切入点表达式,可以是表达式,也可以是表达式的引用。
      pointcut : 它的作用和value是一样的。
      throwing: 指定切入点方法执行产生异常时的异常对象变量名称,它必须和异常变量名称一致。
      argNames : 用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称一致。通常不指定也可以获取切入点方法的参数内容。
    • 使用场景:
      此注解修饰的方法执行时机是在切入点方法执行产生异常之后执行。

    3.6.3 示例

    // 切入点方法:
    @Override
    public User findById(String id) {
    	System.out.println("切入点方法开始执行。。。");
    	User user = new User();
    	user.setId(id);
    	user.setUsername("heima");
    	user.setNickname("黑马小王子");
    	int i=1/0;
    	return user;
    }
    
    /**
    * 异常通知
    */
    @AfterThrowing(value = "execution(* com.test.service.impl.*.*
    (..))&&args(param)",throwing = "e")
    public void afterThrowingLog(String param,Throwable e){
    	System.out.println("执行切入点方法产生异常后记录日志,切入点方法的参数是:"+param);
    	System.out.println("执行切入点方法产生异常后记录日志,切入点方法的异常是:"+e);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.7 @After

    3.7.1 源码

    /**
     * After finally advice
     * @author Alexandre Vasseur
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface After {
    	/**
    	* The pointcut expression where to bind the advice
    	*/
    	String value();
    	
    	/**
    	* When compiling without debug info, or when interpreting pointcuts at runtime,
    	* the names of any arguments used in the advice declaration are not available.
    	* Under these circumstances only, it is necessary to provide the arg names in
    	* the annotation ‐ these MUST duplicate the names used in the annotated method.
    	* Format is a simple comma‐separated list.
    	*/
    	String argNames() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.7.2 说明

    • 作用:用于指定最终通知。
    • 属性:
      value : 用于指定切入点表达式,可以是表达式,也可以是表达式的引用。
      argNames : 用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称
      一致。通常不指定也可以获取切入点方法的参数内容。
    • 使用场景:最终通知的执行时机是在切入点方法执行完成之后执行,无论切入点方法执行是否产生异常最终通知都会执行。所以被此注解修饰的方法,通常都是做一些清理操作。

    3.7.3 示例

    /**
    * 最终通知
    */
    @After(value = "execution(* com.test.service.impl.*.*(..))")
    public void afterLog(){
    	System.out.println("无论切入点方法执行是否有异常都记录日志");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.8 @Around

    3.8.1 源码

    /**
    * Around advice
    * @author Alexandre Vasseur
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Around {
    	/**
    	* The pointcut expression where to bind the advice
    	*/
    	String value();
    	
    	/**
    	* When compiling without debug info, or when interpreting pointcuts at runtime,
    	* the names of any arguments used in the advice declaration are not available.
    	* Under these circumstances only, it is necessary to provide the arg names in
    	* the annotation ‐ these MUST duplicate the names used in the annotated method.
    	* Format is a simple comma‐separated list.
    	*/
    	String argNames() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.8.2 说明

    • 作用:用于指定环绕通知。
    • 属性:
      value: 用于指定切入点表达式,可以是表达式,也可以是表达式的引用。
      argNames: 用于指定切入点表达式参数的名称。它要求和切入点表达式中的参数名称
      一致。通常不指定也可以获取切入点方法的参数内容。
    • 使用场景:环绕通知有别于前面介绍的四种通知类型。它不是指定增强方法执行时机的,而是
      spring为我们提供的一种可以通过编码的方式手动控制增强方法何时执行的机制。

    3.8.3 示例

    /**
    * 环绕通知
    */
    @Around("execution(* com.test.service.impl.*.*(..))")
    public Object arountPrintLog(ProceedingJoinPoint pjp){
    	//1.定义返回值
    	Object rtValue = null;
    	try{
    		//前置通知
    		System.out.println("执行切入点方法前记录日志");
    		//获取方法执行所需的参数
    		Object[] args = pjp.getArgs();
    		//执行切入点方法
    		rtValue = pjp.proceed(args);
    		//后置通知
    		System.out.println("正常执行切入点方法后记录日志");
    	}catch (Throwable t){
    		//异常通知
    		System.out.println("执行切入点方法产生异常后记录日志");
    	}finally {
    		//最终通知
    		System.out.println("无论切入点方法执行是否有异常都记录日志");
    	}
    	return rtValue;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    3.9 @DeclareParents

    3.9.1 源码

    /**
    * Declare parents mixin annotation
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface DeclareParents {
    	/**
    	* The target types expression
    	*/
    	String value();
    	
    	/**
    	* Optional class defining default implementation
    	* of interface members (equivalent to defining
    	* a set of interface member ITDs for the
    	* public methods of the interface).
    	*/
    	Class defaultImpl() default DeclareParents.class;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.9.2 说明

    • 作用:用于给被增强的类提供新的方法。(实现新的接口)
    • 属性:
      value : 用于指定目标类型的表达式。当在全限定类名后面跟上+时,表示当前类及其子类。
      defaultImpl : 指定提供方法或者字段的默认实现类。
    • 使用场景:
      当我们已经完成了一个项目某个阶段的开发,此时需要对已完成的某个类加入新方法,我们首先想到的是写一个接口,然后让这些需要新方法的类实现此接口。但如果目标类非常复杂,改动的话可能非常麻烦。此时就可以使用此注解,然后建一个代理类,同时代理该类和目标类。

    3.9.3 示例

    /**
    * 业务层接口
    * @author hao
    */
    public interface UserService {
    	/**
    	* 模拟保存用户
    	* @param user
    	*/
    	void saveUser(User user);
    }
    
    /**
    * @author hao
    */
    @Service("userService")
    public class UserServiceImpl implements UserService {
    	@Override
    	public void saveUser(User user) {
    		System.out.println("执行了保存用户" + user);
    	}
    }
    
    /**
    * 需要加入的新方法
    * @author hao
    */
    public interface ValidateService {
    	boolean checkUser(User user);
    }
    
    
    /**
    * @author hao
    */
    public class ValidateServiceImpl implements ValidateService {
    	@Override
    	public boolean checkUser(User user) {
    		if(user.getNickname().contains("孙子")){
    			return false;
    		}
    		return true;
    	}
    }
    
    /**
    * 记录日志的工具类
    * @author hao
    */
    @Component
    @Aspect
    public class LogUtil {
    	@DeclareParents(value ="com.test.service.UserService+",defaultImpl = ValidateServiceImpl.class)
    	private ValidateService validateService;
    	
    	/**
    	* 用于配置当前方法是一个前置通知
    	*/
    	@Before(value = "com.test.pointcuts.MyPointcut.pointcut1() && args(user) && this(validateService)")
    	public void printLog(User user,ValidateService validateService){
    		//第二种触发方式
    		boolean check = validateService.checkUser(user);
    		if(check) {
    			System.out.println("执行打印日志的功能");
    		}else {
    			throw new IllegalStateException("名称非法");
    		}
    	}
    }
    
    /**
    * spring核心配置类
    * @author hao
    */
    @Configuration
    @ComponentScan("com.test")
    @EnableAspectJAutoProxy
    public class SpringConfiguration {
    }
    
    /**
    * 测试类
    * 有两种触发方式:
    * 第一种触发方式:在使用时自行强转新引入接口类型,然后调用方法。例如:测试类中的代码
    * 第二种触发方式:在通知类中,使用this关键字,引入新目标类对象,调用方法触发。例如:切面类
    * @author hao
    */
    public class SpringPointcutTest {
    	public static void main(String[] args) {
    		//1.创建容器
    		AnnotationConfigApplicationContext ac = new
    		AnnotationConfigApplicationContext(SpringConfiguration.class);
    		//2.获取对象
    		UserService userService = ac.getBean("userService",UserService.class);
    		//3.执行方法
    		User user = new User();
    		user.setId("1");
    		user.setUsername("test");
    		user.setNickname("孙子1");
    		userService.saveUser(user);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102

    3.10 @EnableLoadTimeWeaving

    3.10.1 源码

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(LoadTimeWeavingConfiguration.class)
    public @interface EnableLoadTimeWeaving {
    	/**
    	* Whether AspectJ weaving should be enabled.
    	*/
    	AspectJWeaving aspectjWeaving() default AspectJWeaving.AUTODETECT;
    	
    	/**
    	* AspectJ weaving enablement options.
    	*/
    	enum AspectJWeaving {
    		/**
    		* Switches on Spring‐based AspectJ load‐time weaving.
    		*/
    		ENABLED,
    		
    		/**
    		* Switches off Spring‐based AspectJ load‐time weaving (even if a
    		* "META‐INF/aop.xml" resource is present on the classpath).
    		*/
    		DISABLED,
    		
    		/**
    		* Switches on AspectJ load‐time weaving if a "META‐INF/aop.xml" resource
    		* is present in the classpath. If there is no such resource,then AspectJ
    		* load‐time weaving will be switched off.
    		*/
    		AUTODETECT;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    3.10.2 说明

    • 作用:用于切换不同场景下实现增强。
    • 属性:
      aspectjWeaving:是否开启LTW的支持。ENABLED 开启LTW;DISABLED 不开启LTW;
      AUTODETECT 如果类路径下能读取到META‐INF/aop.xml文件,则开启LTW,否则关闭。
    • 使用场景:
      在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入、类加载期织入和运行期织入。编译期织入是指在Java编译期,采用特殊的编译器,将切面织入到Java类中;而类加载期织入则指通过特殊的类加载器,在类字节码加载到JVM时,织入切面;运行期织入则是采用CGLib工具或JDK动态代理进行切面的织入。AspectJ提供了两种切面织入方式,第一种通过特殊编译器,在编译期将AspectJ语言编写的切面类织入到Java类中,可以通过一个Ant或Maven任务来完成这个操作;第二种方式是类加载期织入,也简称为LTW(Load Time Weaving)

    3.10.3 示例

    /**
    * 切面类
    * @author hao
    */
    //@Component
    @Aspect
    public class LoadTimeWeavingAspect {
    	/**
    	* 增强方法
    	* @param pjp
    	* @return
    	* @throws Throwable
    	*/
    	@Around("pointcut()")
    	public Object profile(ProceedingJoinPoint pjp) throws Throwable {
    		//1.创建秒表对象
    		StopWatch sw = new StopWatch(getClass().getSimpleName());
    		try {
    			//2.记录执行
    			sw.start(pjp.getSignature().getName());
    			//3.调用切入点方法并返回
    			return pjp.proceed();
    		} finally {
    			//4.停止计时
    			sw.stop();
    			//5.输出
    			System.out.println(sw.prettyPrint());
    		}
    	}
    	/**
    	* 切入点表达式
    	*/
    	@Pointcut("execution(* com.test.service.impl.*.*(..))")
    		public void pointcut() {
    	}
    }
    
    
    /**
    * 配置类
    * @author hao
    */
    @Configuration
    @ComponentScan("com.itheima")
    //@EnableAspectJAutoProxy
    @EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
    public class SpringConfiguration {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    4. AOP注解执行过程

      1. 加载@EnableAspectJAutoproxy注解
      1. 解析切入点表达式
      1. 解析通知注解
      • 3.1 初始化通知注解的Map。在执行容器初始化创建时,spring把和通知相关的注解都放到一个受保护的内部类中了。
      • 3.2 构建通知的拦截器链
      1. 执行方法。spring在初始化容器时已经把要执行的通知都存入了一个集合中,接下来当执行切入点方法时,spring会按照通知的类型,顺序调用。

    5. 切入点表达式总结

    5.1 概念

    • 概念:指的是遵循特定的语法用于捕获每一个种类的可使用连接点的语法。
    • 作用:用于对符合语法格式的连接点进行增强。

    5.2 按用途分类

    主要的种类:

    • 方法执行:execution(MethodSignature)
    • 方法调用:call(MethodSignature)
    • 构造器执行:execution(ConstructorSignature)
    • 构造器调用:call(ConstructorSignature)
    • 类初始化:staticinitialization(TypeSignature)
    • 属性读操作:get(FieldSignature)
    • 属性写操作:set(FieldSignature)
    • 例外处理执行:handler(TypeSignature)
    • 对象初始化:initialization(ConstructorSignature)
    • 对象预先初始化:preinitialization(ConstructorSignature)

    5.3 切入点表达式关键字

    支持的AspectJ切入点指示符如下:

    • execution:用于匹配方法执行的连接点;
    • within:用于匹配指定类型内的方法执行;
    • this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口类型匹配;
    • target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口类型匹配;
    • args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
    • @within:用于匹配所以持有指定注解类型内的方法;
    • @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
    • @args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
    • @annotation:用于匹配当前执行方法持有指定注解的方法;
    • bean:Spring AOP扩展的,用于匹配特定名称的Bean对象的 执行方法;
    • reference pointcut:表示引用其他命名切入点,只支持@ApectJ风格,Schema风格不支持。

    5.4 切入点表达式通配符

    AspectJ类型匹配的通配符:

    • *:匹配任何数量字符;
    • .. :重复匹配任何数量字符,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
    • +:匹配指定类型的子类型,仅作为后缀放在类型模式后边。

    说明:
    java.lang.String :匹配String类型;
    java.*.String : 匹配java包下的任何“一级子包”下的String类型,如匹配java.lang.String,但不匹配java.lang.ss.String
    java..* : 匹配java包及任何子包下的任何类型,如匹配java.lang.String、java.lang.annotation.Annotation
    java.lang.*ing : 匹配任何java.lang包下的以ing结尾的类型;
    java.lang.Number+ : 匹配java.lang包下的任何Number的自类型,如匹配java.lang.Integer,也匹配java.math.BigInteger

  • 相关阅读:
    微服务实战之领域事件
    NanoPC-T4 RK3399:DTS之io-domain,FAN
    代码随想录 动态规划 判断子序列,不同的子序列
    Plant Communications|高质量的基因组组装和泛基因组研究促进了绿豆的基因发现及其改进
    MySQL 分表查询
    分享两个模糊照片修复方法,轻松实现照片修复
    Jenkins插件安装失败时这么做就搞定啦
    C语言的查找
    实现批量图像对PSNR、SSIM的计算
    python命令行传递参数的两种方式!
  • 原文地址:https://blog.csdn.net/xiaotiaoza/article/details/138155880