深入浅出spring boot2.x 学习笔记(五) 基于AspectJ注解的Spring AOP

AOP简单介绍

AOP也算是一种基于约定的编程。在Spring中,AOP可以实现一些OOP无法实现的逻辑,也可以将业务逻辑织入相应的流程中,还可以将一些通用的逻辑抽取出来,比如:数据库资源的打开、关闭,事务的提交和回滚;这样可以使代码更加简短,同时可维护性也会有相应的提高。Spring的AOP是基于方法的,它是通过动态代理技术来实现的。动态代理的简单介绍请点击: 动态代理简单介绍.

AOP的相关术语

  • 连接点(join point)
    需要被拦截的指定的方法。假设我们现在要增强Hello类的helloworld()方法,在该方法执行前后加入特定的功能,那么这个helloworld()方法就是一个连接点。
  • 切点(cut point)
    有时候我们不只会对某个类中的某一个单一方法进行拦截,比如需要拦截Hello类中的helloworld()方法以及Hello2类中的helloworld()2方法,此时需要通过正则式或者指示器的规则来进行定义来进行多个连接点的匹配。切点就是为我们提供了这个功能。
  • 通知(advice)
    分为前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)、事后返回通知(afterRetuming advice )和异常通知
    (afterThrowing advice)。如动态代理中在执行委托对象的方法前后我们执行的自定方法一样,通知可以用来增强委托对象的方法。
  • 目标对象(target)
    目标对象就是我们的委托对象,即被代理的对象。
  • 引入(introduction)
    加入新的类或是方法来增强目标对象的功能。
  • 织入(weaving)
    通过动态代理技术,为目标对象生成代理对象, 然后将与切点定义匹配的连接点拦截,并按约定将各类通知织入约定流程的过程。
  • 切面(aspect)
    是一个可以定义切点、各类通知和引入的内容, SpringAOP 将通过它的信息来增强目标对象的功能或者将对应的方法织入流程。

代码实现

  1. 定义接口并添加实现类
public interface HelloService {
	public void sayHello(String name);
}
public class HelloServiceImpl implements HelloService {

	@Override
	public void sayHello(String name) {
		if (null == name || name.trim() == "") {
			throw new RuntimeException("param is null!");
		}
		System.out.println("hello"+name);
	}

}

2.定义切面,即我们要找到HelloServiceImpl类的sayHello()方法并对它做哪些增强。

package com.example.demo.aspect;

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 此处也可以不使用该注解,但需要在启动类里告知spring这是一个我们需要的bean。
/*
@Bean(name="myAspect")
	public MyAspect initMyAspect() {
		return new MyAspect();
	}
*/
@Aspect
public class MyAspect {

	/*
	 * @Pointcut将pointCut()方法定义为切点,切点中指定了切入的方法。
	 * execution表示在执行的时候,拦截()中的方法;第一个* 表示任意返回类型的方法
	 * com.example.demo.service.impl.UserServiceImpl指定目标对象,.printUser(..)指定方法
	 * (..)表示任何参数都可以匹配
	 * 
	 */

	@Pointcut("execution(* "
			+ "com.example.demo.service.impl.HelloServiceImpl.sayHello(..))")
	public void pointCut() {
		
	}
	/*
	 * @Before、@After、@AfterReturning、@AfterThrowing
	 * 指定在目标方法printUser(..)执行前后或返回异常等状态下将执行的自定义方法。
	 */
	@Before("pointCut()")
	public void before() {
		System.out.println("before........");
	}
	@After("pointCut()")
	public void after() {
		System.out.println("after.....");
	}
	@AfterReturning("pointCut()")
	public void afterReturning() {
		System.out.println("afterReturning......");
	}
	@AfterThrowing("pointCut()")
	public void afterThrowing() {
		System.out.println("afterRunning....");
	}
}

3.测试及结果

@Autowired
	HelloService helloService;
	
	@RequestMapping("/aTest")
	@ResponseBody
	public String aTest() {
		helloService.sayHello("dom");
		return "aspect Test";
	}

控制台打印结果:

2019-02-28 15:23:16.421  INFO 12612 --- [nio-8090-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-02-28 15:23:16.422  INFO 12612 --- [nio-8090-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-02-28 15:23:16.437  INFO 12612 --- [nio-8090-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 15 ms
before........
hellodom
after.....
afterReturning......
  1. @Around 环绕通知
    环绕通知是所有通知中功能最强大的通知,强大就意味着难以控制。应用场景:需要大幅度修改原有目标对象的服务逻辑。环绕通知取代了原有目标对象的方法,什么意思呢,我们来做个测试。还是在之前的MyAspect中加入@Around修饰的方法。
@Around("pointCut()")
	public void around(ProceedingJoinPoint pj) throws Throwable {
		System.out.println("around before.....");
		//pj.proceed();
		System.out.println("around after.....");
	}

环绕通知的方法中引入了一个ProceedingJoinPoint类型的对象pj,这个对象的proceed()方法可以回调原有目标对象的方法。我们先把他注释掉看看结果。

2019-03-04 17:26:52.188  INFO 18472 --- [nio-8090-exec-2] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-03-04 17:26:52.199  INFO 18472 --- [nio-8090-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 11 ms
around before.....
around after.....
after.....
afterReturning......

从结果中看出程序执行了环绕通知,但是并没有执行原目标对象的sayHello()方法,我们把注释掉的代码解开,再看结果。

2019-03-04 17:29:45.948  INFO 19052 --- [nio-8090-exec-3] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-03-04 17:29:45.958  INFO 19052 --- [nio-8090-exec-3] o.s.web.servlet.DispatcherServlet        : Completed initialization in 10 ms
around before.....
before........
hellodom
around after.....
after.....
afterReturning......

加上pj.proceed()这个方法后实现了对原有目标对象方法的调用。
在上述例子的测试中,使用了springaop 5.1.4版本的jar包,仍出现了书中描述的问题:环绕通知的around before总会在前置通知before之前执行。而在不加入环绕通知时,所有的运行结果都是我们期待的结果。因而在使用时尽量避免使用环绕通知。
5. 使用其他类的方法来增强目标方法的功能(引入)
我们增加一个判断name是否为空的接口和实现类:

接口:
public interface HelloValidator {
	boolean isEmpty(String str);
}
实现:
public class HelloValidatorImpl implements HelloValidator{
	@Override
	public boolean isEmpty(String str) {
		System.out.println("引入了了新的方法");
		if (str.isEmpty()) {
			return true;
		}
		return false;
	}

}

然后在MyAspect中加入如下代码:

@DeclareParents(value = "com.example.demo.service.impl.HelloServiceImpl",
			defaultImpl=com.example.demo.service.impl.HelloValidatorImpl.class)
	public HelloValidator helloValidator;

@DeclareParents注解用来引入新的类。value属性用来指定要增强的目标对象,defaultImpl用来指定提供功能的类。
这里有一点需要注意,在value属性中书中在HelloServiceImpl后面有一个“+”,按照该配置应用启动时会报错:

//这是之前测试时候报出的错误,此处可以做一个参考。在去掉“+”后容器可以正常启动,网上查阅相关资料是将HelloServiceImpl写为接口+,即HelloService+表示增强该接口下的所有实现类。单启动时仍然报错。
org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration':
Unsatisfied dependency expressed through method 'setConfigurers' parameter 0; 
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'org.springframework.data.web.config.SpringDataWebConfiguration': 
Unsatisfied dependency expressed through field 'pageableResolverCustomizer'; 
nested exception is org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'pageableCustomizer' defined in class path resource 
[org/springframework/boot/autoconfigure/data/web/SpringDataWebAutoConfiguration.class]: 
Initialization of bean failed; 
nested exception is org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException: 
warning can't determine implemented interfaces of missing type org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration$$Lambda$326.209845522


Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'org.springframework.data.web.config.SpringDataWebConfiguration': 
Unsatisfied dependency expressed through field 'pageableResolverCustomizer'; 
nested exception is org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'pageableCustomizer' defined in class path resource 
[org/springframework/boot/autoconfigure/data/web/SpringDataWebAutoConfiguration.class]: 
Initialization of bean failed; nested exception is org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException: 
warning can't determine implemented interfaces of missing type org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration$$Lambda$326.209845522


只写成value = "com.example.demo.service.impl.HelloServiceImpl"后,改写controller如下:

@Autowired
	HelloService helloService;
	
	@RequestMapping("/aTest")
	@ResponseBody
	public String aTest() {
		String str = "dom";
		//此处做了一个强制转换
		HelloValidator helloValidator = (HelloValidator) helloService;
		if (!helloValidator.isEmpty(str)) {
			helloService.sayHello(str);
		}
		//helloService.sayHello("dom");
		return "aspect Test";
	}

运行后结果如下:


    2019-03-04 17:53:40.919  INFO 19412 --- [nio-8090-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 18 ms
引入了了新的方法
around before.....
before........
hellodom
around after.....
after.....
afterReturning......

可以看到,实现了对目标对象的增强。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值