Spring Boot 2.0 读书笔记_02:AOP

版权声明:未经博主本人同意,请勿私自转发分享。 https://blog.csdn.net/Nerver_77/article/details/84142695

写在开头,自该专栏建立起,9月初发布第一篇文章后,两个月过去了,专栏文章没啥进度。处于个人实习原因,以及同步的Vue专栏,所以关于SpringBoot 2.0 的读书笔记专栏暂时搁置了。

虽然博客专栏搁置更新,但是技术的使用每天都在使用。基于Spring Boot 2.0 的新特性也即将进行更新,主要参考《Spring Boot 2精髓》以及码云上维护的配套案例Demo。 详情:https://gitee.com/xiandafu/Spring-Boot-2.0-Samples

李家智,《Spring Boot 2精髓》作者,Beetl模板引擎作者

形式上以读书笔记的形式进行,针对作者在码云上的配套案例进行分析学习,主要重温下Spring框架以及SpringBoot 2.0 的新特性学习。

读书笔记建立在本人技术认知以上进行记录,废话不多数,开始第一章:AOP

1. AOP:面向切面编程

谈到AOP,我们就会想到很多实际场景。比如,日志、事务、公共操作(缓存、数据库)等,所谓面向切面编程就是在原有的纵向业务处理过程中添加一个切面,切入些公有、特定的处理操作。

我们可以这样认为:AOP将应用中的业务逻辑和系统级服务进行了分离,采用AOP可以在应用过程中动态的在需要的地方进行"织入"公共服务代码。

之前在学习Guns脚手架工具时候,遇到了很多AOP的例子,日志记录、数据源切换、权限控制等。那么在这里结合配套案例中的ch1、ch2进行分析学习,下面是案例目录结构:
在这里插入图片描述
在我阅读这本书的时候,开篇来了个访问控制的例子,的确很开门见山啊,我们也结合这个例子进行详细的分析。

首先入门前我们需要了解几个基本概念:

名称 含义
Aspect(切面) 通常是一个类,里面可以定义切入点和通知。
JointPoint(连接点) 程序执行过程中明确的点,一般是方法的调用。
Advice(通知) AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around。
Pointcut(切入点) 带有通知的连接点,在程序中主要体现为书写切入点表达式。
AOP代理 AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。

关于ch1、ch2,由于ch2案例中的内容在ch1中以及有所体现,这里整合到一起进行说明。

案例核心是:认证控制,我们对Function、RoleAcessConfig、以及HelloworldController进行分析。

HelloworldController:控制器,请求处理

@RequestMapping("/sayhello.html")
@Function()
public @ResponseBody String say(String name){
	return "hello "+name;
}

常规控制器方法:@RequestMapping 标识请求映射路径,@ResponseBody 标识将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象,常用来返回JSON数据。

那么这个 @Function() 是什么东东呢?--------- 自定义注解,请向下看:

Function:自定义注解

在解决这个问题之前,我们先来了解一下: 注解

扫描二维码关注公众号,回复: 4403029 查看本文章

Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。
Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。

元数据(metadata):关于数据的数据,可以用来创建文档,跟踪代码的依赖性,执行编译时格式检查,代替已有的配置文件。

根据使用方法和用途对注解分类:系统注解,元注解,自定义注解

系统注解:

1. @Override:用于重写父类的方法 或者是写接口实现类时用到该注解。
2. @Deprecated:用于表示该方法是一个过期的方法。
3. @suppressWarnings:表示该方法在编译时自动忽略警告。

元注解:

1. @Target:用于描述注解的使用范围。
    ElementType取值:
    1. CONSTRUCTOR:用于描述构造器
    2. FIELD:用于描述域
    3. LOCAL_VARIABLE:用于描述局部变量
    4. METHOD:用于描述方法
    5. PACKAGE:用于描述包
    6. PARAMETER:用于描述参数
    7. TYPE:用于描述类、接口(包括注解类型) 或enum声明
    例:@Target({ElementType.METHOD}) 用于描述方法。
2. @Retention:用于描述注解的生命周期。
    RetentionPoicy取值
    1. SOURCE:在源文件中有效
    2. CLASS:在class文件中有效
    3. RUNTIME:在运行时有效
    例:@Retention(RetentionPolicy.RUNTIME) 运行时有效
3. @Documented:声明需要加入JavaDoc。
4. @Inhrited:表明了被标注的类型是被继承的。
   如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

自定义注解:@interface用来声明一个自定义注解,自动继承java.lang.annotation.Annotation接口。

自定义注解格式:public @interface 注解名{注解体}
自定义注解体格式:基本数据类型/String/Class/enum/Annotation
例:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String value() default "";
}
注意:自定义注解访问修饰符只有public和default。

这里给出一个关于元注解 @Inhrited 的小例子

首先我们自定义一个注解:具体自定义注解内容会在下面指出

//自定义注解:标注被继承、运行时有效           
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String value() default "";
}
//测试类
public class Test1 {

    @TestAnnotation
    class A{}

	// B 继承 A 类
    class B extends A{}

    public static void main(String[] args) {
        System.out.println(B.class.isAnnotationPresent(TestAnnotation.class));
    }
}

测试结果:true 证实上述说法

这里给出关于注解的思维导图:

那么我们再看Function 这个自定义注解

@Retention(RetentionPolicy.RUNTIME)
public @interface Function {
	public String value() default "";
}

运行时生效,注解参数value,类型String,默认值" ",分析完毕 over

注解 Function 是自定义一个注解,接受一个字符串,表示 Controller 方法对应的业务功能
用户是否能访问" user.add " 功能将在数据库中配置。

RoleAcessConfig:切面类,认证控制

我们先看看这个切面类:@Aspect,声明这是一个切面类;@Configuration,声明配置类;

@Aspect
@Configuration
public class RoleAcessConfig {
	//内部内容省略		
}

关于@Configuration注解,它与@Component注解,两者都是将被注解的类交给Spring容器进行管理,但是两者是存在区别的:

这里比较浅的谈一下两者区别,详细的区别有大牛分析贴,详情见:https://blog.csdn.net/isea533/article/details/78072133?locationNum=7&fps=1

两者区别:这里需要注意,这两个注解都是用于类上的注解

1. @Component注解的范围最广,所有类都可以注解。

Spring 2.5 中除了提供 @Component 注释外,还定义了几个拥有特殊语义的注释,它们分别是:@Repository、@Service 和 @Controller

在目前的 Spring 版本中,这 3 个注释和 @Component 是等效的,但是从注释类的命名上,很容易看出这 3 个注释分别和持久层、业务层和控制层(Web 层)相对应。

虽然目前这 3 个注释和 @Component 相比没有什么新意,但 Spring 将在以后的版本中为它们添加特殊的功能。

所以,如果 Web 应用程序采用了经典的三层分层结构的话,最好在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释,用 @Component 对那些比较中立的类进行注释

2. @Configuration注解一般注解在配置类上:具有@Value注解的成员变量和@Bean注解的方法。

该部分的代码主要分为两块:

1. 针对含有@Function的方法进行切面处理

	/**
	 * 所有使用Function的注解的方法,且在Controller注解标注的类里
	 */
	@Around("within(@org.springframework.stereotype.Controller *) && @annotation(function)")
	public Object functionAccessCheck(final ProceedingJoinPoint pjp, Function function) throws Throwable {
		if (function != null) {
			String functionName = function.value();
			if (!canAccess(functionName)) {
				MethodSignature ms = (MethodSignature) pjp.getSignature();
				throw new RuntimeException("Can not Access " + ms.getMethod());
			}
		}
		// 继续处理原有调用
		Object o = pjp.proceed();
		return o;

	}

	protected boolean canAccess(String functionName) {
		if (functionName.length() == 0) {
			return true;
		} else {
			// 取出当前用户对应的所有角色,从数据库查询角色是否有访问functionName的权限。
			return false;
		}
	}

面对上述代码:

  • @Around:环绕通知方式,在方法执行前后调用Advice,这是Spring框架和应用系统一种最为常用的方式。

  • @within:within(@org.springframework.stereotype.Controller *),对所有使用@Controller注解的类进行切面逻辑(AOP)。

  • @annotation:@annotation(function),对具有@Function注解的方法进行切面逻辑(AOP)。

    @within、@annotation均为切点表达式,即上述提到的@PointCut,因此还可以这样去写。

      @Pointcut(value = "@annotation(function)")
      public void cutService() {}
    
      @Around("cutService()")
    
  • ProceedingJoinPoint:JoinPoint对象的子类,封装了AOP切面中方法的信息,在切面方法中添加JoinPoint参数,就可以获取封装该方法信息的JoinPoint对象。

JoinPoint常用API:

方法名 功能
Signature getSignature() 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs() 获取传入目标方法的参数对象
Object getTarget() 获取被代理的对象
Object getThis() 获取代理对象

ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中。此外,在此对象中添加了两个新方法:

方法名 功能
Object proceed() throws Throwable 执行目标方法
Object proceed(Object[] args) throws Throwable 传入的新的参数去执行目标方法

该补充的内容补充好了,我们看看这个functionAccessCheck方法都做了什么吧!

方法参数上,接收了ProceedingJoinPoint、Function两个参数。

方法体:首先做了Function对象的判空操作,若Function对象不为空,取Function对象的value成员变量,进行认证判断。

canAccess方法:接收function.value()参数,做了长度判断length(),表示若未携带参数 (@Function()),表示权限通过。相反,携带参数 (@Function(value = “user.add”)),表示该方法是必须具备相应权限的操作用户才可进行访问。

这里我们可以脑补一下认证逻辑:
首先假设已经登录该系统
	获取当前登录用户信息
	查询数据库中用户信息所属角色组
	到所属角色组下查看时候具有相应的user.add权限(基于URL进行权限数据存储)
	返回权限检查结果(通过,方法执行;不通过,抛出异常)
这里提到了权限控制的相关内容,权限管理三要素:用户 - 角色 - 权限

做个演示:我们不添加认证逻辑,默认返回false(模拟通不过认证的情况)

@RequestMapping("/sayhello.html")
@Function(value = "user.add")
public @ResponseBody String say(String name){
	return "hello "+name;
}

启动项目,观察Debug模式下方法执行过程,查看浏览器结果:
方法执行过程:
在这里插入图片描述
浏览器异常信息:
在这里插入图片描述
当方法上添加@Function(value = “user.add”)的时候,标识该方法受到了权限控制,只有用户符合该权限角色组才可进行该方法内部的业务调用。通过切面 + 自定义注解的方式实现了简单的认证逻辑。

2. 针对含有@controller声明的类中全部方法进行切面处理

	/**
	 * 所有Controller方法
	 */
	@Around("@within(org.springframework.stereotype.Controller) ")
	public Object simpleAop(final ProceedingJoinPoint pjp) throws Throwable {
		try {
			Object[] args = pjp.getArgs();
			System.out.println("args:" + Arrays.asList(args));
			Object o = pjp.proceed();
			System.out.println("return :" + o);
			return o;

		} catch (Throwable e) {
			throw e;
		}
	}

这个切面方法做的操作时针对所有被@Controller注解的类下的方法,在pjp.proceed()方法前后做了方法参数及返回值的获取。

我们在Controller中添加个方法做个测试:

@RequestMapping("/sayhello.html")
public @ResponseBody String say(String name){
	return "hello "+name;
}

@RequestMapping("/test")
@ResponseBody
public String test() {
	return "test";
}

启动项目,分别请求/sayHello.html、/test,查看控制台信息:
在这里插入图片描述
可以看出:所有的方法都会进入参与该切面逻辑(记录方法参数及返回值)

猜你喜欢

转载自blog.csdn.net/Nerver_77/article/details/84142695