在云笔记项目的过程中,需要检查各个业务层的执行快慢,如登录、注册、展示笔记本列表,展示笔记列表等,如果在每个业务层方法里都写一段代码用来检查时间并打印,不仅仅显得代码重复,而且当项目很大的时候,将大大加大工作量。这个时候AOP的概念引入了,本文在引用其他大牛博文的基础上,对AOP知识进行了简单整理,今后可以参考使用。

什么是AOP

AOP(Aspect Oriented Programming),即面向切面编程,底层使用了动态代理技术,在不改变原有业务逻辑的基础上,横向添加业务逻辑。就云笔记项目来说,任何一个Action,都会按照浏览器<-->控制层<-->业务层<-->持久层<-->数据库的顺序执行,每一个小功能都是按照此路程进行,这个可以理解为纵向逻辑。然后上面说的检查业务层方法执行快慢的功能,因为其分布在各个业务层里,像横切了一刀,因此形象的叫做切面。AOP是对OOP(Object Oriented Programming)的补充,它利用"横切"的技术,解开封装对象的内部,将影响到多个类的公共行为(如测试某个业务响应时间、事务管理、日志记录、异常处理等),封装到一个可以重用的模块,并将其取名“Aspect”,即切面。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点发生在核心关注点的多个地方,功能基本相似,如上述的权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。另外AOP实现了"高内聚",而IOC和DI实现了"低耦合"。

主要概念

(1)横切关注点

对哪些方法进行拦截,拦截后如何处理,这些关注点为横切关注点

(2)连接点(Joinpoint)

在Spring中连接点就是被拦截到的方法,也可以是字段或者构造器(暂时没实践过)

(3)切入点(Pointcut)

需配合切入表达式使用,切入点是符合切入规则的连接点,可以对选择出来的连接点进行功能的增强。主要有bean组件切入点,类切入点和方法切入点:

(a)bean组件切入点,语法 bean(beanid),以云笔记为例,bean(userService) 切入一个类,bean(userService)||bean(noteService)||bean(noteBookService) 切入三个类,bean(userService)||bean(noteService)||bean(noteBookService) 切入三个类,bean(*Service) 切入所有后缀名字为Service的类。
(b)类切入点,语法 within(包名.类名),以云笔记为例,如within(com.boe.Service.UserServiceImpl),within(com.boe.Service.UserServiceImpl)||within(com.boe.Service.noteBookServiceImpl),within(com.boe.*.*ServiceImpl)。
  (c)  方法切入点,语法 execution(返回值类型   包名.类名.方法名(参数类型)),比如execution(* com.boe.Service.UserServiceImpl.login(..)),代表返回类型不限定,com.boe.Service包下类UserServiceImpl,方法名为login,参数个数和类型不限定。

以上三种切入点方式,只有方法切入点是细粒度的,可以更加精细的定位到切入点。

(4)切面(Aspect)

事物的横切面,是对横切关注点的抽象,简单理解就是Spring将拦截下来的切入点交给一个处理类来处理,进行功能的增强,这个处理类就是一个横切面。

(5)通知(Advice)

Spring拦截到连接点变成切入点交给切面类,切面类中有处理切入点的方法,这些方法就是通知(也称增强方法),按照类型来划分主要有@Before,@After,@AfterReturning,@AfterThrowing和@Around五种。

五种通知的大致执行顺序可以根据try-catch-finally来理解,不过不是完全相同,如发生异常,@After注解的方法会执行,@AfterReturning注解的方法没机会执行。

  try{
@Before 方法执行
目标业务方法执行
@AfterReturning 方法执行
}catch(Exception e){
@AfterThrowing 如果有异常将执行
}finally{
@After 方法执行
}

(6)目标对象(Target Object)

被一个或多个切面所通知(在切入点增强)的对象,就是目标对象,Spring AOP底层是代理实现,目标对象其实也是被代理对象

(7)织入(Weave)

将切面应用到目标对象后代理对象创建的过程

Spring AOP

简单来说,Spring AOP底层调用了AspectJ AOP,而AspectJ AOP底层使用动态代理。有两种动态代理技术,一种是使用JDK动态代理,一种是使用CGLib动态代理。两种的区别在于目标方法是否有对应的接口,如果目标方法有对应的接口就使用JDK动态代理,如果目标方法没有接口就使用CGLib,使用CGLib需要导入第三方的包才能使用,如果使用不带注解的方式进行aop配置,可以在<aop:config>内配置“proxy-target-class”属性,默认情况下为false,如果设置为true,将使用CGLib代理,具体底层暂时水平不足无法深究,后续可能补充。

接下来的例子将使用JDK动态代理,因为目标方法有对应的接口。
Spring会帮忙动态创建对象并帮忙管理对象之间的依赖关系,因此使用Spring将大大简化AOP的使用过程,如果使用底层AspectJ AOP将加大代码量。使用AOP主要要准备确认好以下几点:

(1)确定普通业务组件

(2)确定切入点

(3)确定AOP业务组件,为普通业务组件织入增强处理

基于Spring AOP的简单应用

Spring AOP的简单应用,将以使用注解不使用注解两种方式进行简单使用。不管使用哪种方式,都需要导入aspectjweaver.jar,大牛博客介绍说要导入aopalliance.jar,暂时这个未做导入,也可以测试通过,后续了解。以下是Maven项目下pom.xml的配置:

    <dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.3</version>
</dependency>

case 1 使用注解的情况

(1)定义接口

 package Test;

 public interface HelloAOP {

     public void helloAop() throws InterruptedException;

 }

(2)定义接口的实现类

 package Test;

 import org.springframework.stereotype.Component;

 @Component("helloAOPImpl1")
public class HelloAOPImpl1 implements HelloAOP{ public void helloAop() throws InterruptedException {
//特地让其执行1s
Thread.sleep(1000);
System.out.println("Hello Aop From HelloAOPImpl1");
}
}

(3)定义横切关注点,拦截后打印两个时间

package AOP;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component; /**
* 带注解的AOP,打印时间
* @author clyang
*/
@Component
@Aspect
public class AOP1 {
/**
* 使用bean组件切入点
*/
@Before("bean(helloAOPImpl1)")
public void testBefore() {
System.out.println("当前时间为:"+System.currentTimeMillis());
} @After("bean(helloAOPImpl1)")
public void testAfter() {
System.out.println("当前时间为:"+System.currentTimeMillis());
}
}

(4)Sprng-aop1.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:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:jee="http://www.springframework.org /schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <!-- 使用注解的方式 -->
<!-- 配置组件扫描,使得@Component注解生效-->
<context:component-scan base-package="AOP"></context:component-scan>
<context:component-scan base-package="Test"></context:component-scan>
<!-- 注解驱动 -->
<mvc:annotation-driven></mvc:annotation-driven> <!-- 添加aop注解驱动,使得@Aspect注解生效 -->
<!-- 需要在头文件中添加“xmlns:aop”的命名申明,并在“xsi:schemaLocation”中指定aop配置的schema的地址 -->
<aop:aspectj-autoproxy />
</beans>

(5)写一个测试类进行测试

 package TestAOP;

 import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; import Test.HelloAOP; public class TestCase {
ApplicationContext ac1=null;//使用注解 @Before
public void before() {
String config1="config/spring-aop1.xml";
ac1=new ClassPathXmlApplicationContext(config1);
} /**
* 使用注解
* @throws InterruptedException
*/
@Test
public void test() throws InterruptedException {
//测试执行业务方法时,切面方法是否执行
HelloAOP helloAop=ac1.getBean("helloAOPImpl1",HelloAOP.class);
helloAop.helloAop();
} }

(6)运行结果为:

当前时间为:1553328677357
Hello Aop From HelloAOPImpl1
当前时间为:1553328678357

发现目标方法打印出结果的前后,分别调用@Before和@After的通知,执行了打印当前时间,并延迟1s打印执行后的时间。

(7)小花絮

当时在测试类里,本来想通过@Resource或者@Autowired配合@Qualifier注入helloAOP实现类实体对象,结果都返回为null,参考网上博客,可能的原因分析如下:

(1)如果使用main方法执行测试,@Resource注解以及具体执行方法,会封装在一个类里(假设类名为T)。然后main方法通过实例化这个类T,来调用它里面的执行方法,这种情况因为new关键字实例化了测试类T,测试类对象就不归Spring容器管理,导致测试类对象使用注解注入helloAOPImpl1失效。
(2)如果使用Junit测试,原理跟main方法类似。

case 2 不使用注解的情况

(1)定义接口的实现类

 package Test;

 public class HelloAOPImpl2 implements HelloAOP{

     public void helloAop() throws InterruptedException {
//特地让其执行1s
Thread.sleep(1000);
System.out.println("Hello Aop From HelloAOPImpl2");
}
}

(2)定义横切关注点,拦截后打印两个时间

 package AOP;

 import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Before; /**
* 不带注解的AOP 打印时间
* @author clyang
*
*/
public class AOP2 { public void testBefore() {
System.out.println("当前时间为:"+System.currentTimeMillis());
} public void testAfter() {
System.out.println("当前时间为:"+System.currentTimeMillis());
}
}

(3)Sprng-aop2.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:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:jee="http://www.springframework.org /schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <!-- 不使用注解的方式-->
<bean id="aop2" class="AOP.AOP2"></bean>
<bean id="helloAopImpl2" class="Test.HelloAOPImpl2"></bean> <!-- 1个切面配置 -->
<aop:config>
<aop:aspect id="test" ref="aop2">
<aop:pointcut id="time" expression="bean(helloAopImpl2)"/> <!-- 使用bean组件切入点 -->
<aop:before method="testBefore" pointcut-ref="time"/>
<aop:after method="testAfter" pointcut-ref="time"/>
</aop:aspect>
</aop:config> </beans>

(4)测试类修改进行测试

 package TestAOP;

 import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; import Test.HelloAOP; public class TestCase {
ApplicationContext ac1=null;//使用注解
ApplicationContext ac2=null;//不使用注解 @Before
public void before() {
String config1="config/spring-aop1.xml";
String config2="config/spring-aop2.xml";
ac1=new ClassPathXmlApplicationContext(config1);
ac2=new ClassPathXmlApplicationContext(config2);
} /**
* 使用注解
* @throws InterruptedException
*/
@Test
public void test() throws InterruptedException {
//测试执行业务方法时,切面方法是否执行
//helloAop.helloAop(); HelloAOP helloAop=ac1.getBean("helloAOPImpl1",HelloAOP.class);
helloAop.helloAop();
}
/**
* 不使用注解
* @throws InterruptedException
*/
@Test
public void test1() throws InterruptedException {
HelloAOP helloAop=ac2.getBean("helloAopImpl2",HelloAOP.class);
helloAop.helloAop();
} }

(5)单独执行test1方法进行测试,测试结果为:

当前时间为:1553329722057
Hello Aop From HelloAOPImpl2
当前时间为:1553329723058

Spring AOP使用的简单对比

(1)如果使用注解,使用过程很简单,配置文件配置好注解扫描和Aspect自动动态代理扫描,Spring容器启动后就可以扫描到业务类和AOP类,通过通知注解,完成对目标方法的拦截,实现横切。

(2)如果不使用注解,需要配置文件中管理bean,对业务类和AOP类进行手动管理,并在里面进行具体AOP配置。相对来说使用要稍微复杂一些。以下再参考大牛博文进行一点补充,如何在没有注解的情况下配置AOP。

使用注解配置的三种方式

(1)在<aop:aspect>标签内使用<aop:pointcut>声明切入点,该切入点一般只被该切面使用,expression是切入点方式,如上文有三种来写,这里写bean组件切入点。切入点使用id属性指定bean名字,在通知定义时使用pointcut-ref来引入切入点。当执行到切入点后会激活通知里对应的方法,如本例中的testBefore()方法和testAfter()方法。

        <aop:config>
<aop:aspect id="test" ref="aop2">
<aop:pointcut id="time" expression="bean(helloAopImpl2)"/> <!-- 使用bean组件切入点 -->
<aop:before method="testBefore" pointcut-ref="time"/>
<aop:after method="testAfter" pointcut-ref="time"/>
</aop:aspect>
</aop:config>

(2)在<aop:config>标签内使用<aop:pointcut>声明切入点,这个切入点可以被多个切面使用,同样对切入点进行命名,在通知定义时通过bean id来引入切入点。

        <aop:config>
<aop:pointcut id="time" expression="bean(helloAopImpl2)"/> <!-- 使用bean组件切入点 -->
<aop:aspect id="test" ref="aop2">
<aop:before method="testBefore" pointcut-ref="time"/>
<aop:after method="testAfter" pointcut-ref="time"/>
</aop:aspect>
</aop:config>

(3)匿名切入点Bean,在通知声明时通过pointcut属性指定切入点表达式,该切入点只被该通知使用

        <aop:config>
<aop:aspect id="test" ref="aop2">
<aop:before method="testBefore" pointcut="bean(helloAopImpl2)"/>
<aop:after method="testAfter" pointcut="bean(helloAopImpl2)"/>
</aop:aspect>
</aop:config>

本文中使用的是第一种方式。

总结

(1)AOP在需要横向扩展功能时非常有效,其底层使用了动态代理技术,动态代理技术底层使用过了反射的技术。

(2)AOP可以使用两种配置方式来使用,带注解和不带注解的方式,并且不带注解的方式还有多种配置AOP的写法。

(3)AOP可以使用在云笔记项目中进行业务层的性能测试。

参考博文:

(1)https://www.cnblogs.com/xrq730/p/4919025.html

(2)https://www.cnblogs.com/mxck/p/7027912.html

(3)https://www.cnblogs.com/youngchaolin/p/11594869.html

云笔记项目-AOP知识简单学习的更多相关文章

  1. 云笔记项目-Java反射知识学习

    在云笔记项目中,补充了部分反射的知识,反射这一部分基础知识非常重要,前面学习的框架Spring和MyBatis读取xml配置文件创建对象,以及JDBC加载驱动等都用了反射,但只知道有这个东西,具体不知 ...

  2. 云笔记项目-Spring事务学习_测试准备

    在做云笔记项目的过程中,顺便简单的学习了Spring的事务概念,业务以如果添加笔记,则增加用户星星数目作为例子,引入了事务的概念.类似注册送积分之类的,云笔记项目以增加笔记就送星星来说明事务.具体在添 ...

  3. 云笔记项目-补充JS面向对象编程基础知识

    简单介绍: 此部分知识为在做云笔记项目中补充,因为云笔记项目中涉及到前端js,里面写了很多js脚本,用到了创建js属性和方法,在js中直接声明的属性和方法最终都会变成window的对象,即其成为了全局 ...

  4. 云笔记项目-网页端debug功能学习

    在做云笔记项目的过程中,除了服务端在eclipse中debug调试代码外,有时候需要在浏览器端也需要进行debug调试,刘老师举了一个冒泡排序算法的dubug例子,进行了讲解. 首先上浏览器端测试代码 ...

  5. 云笔记项目-MyBatis返回自增类型&堆栈对象补充理解

    在云笔记项目中,讲到了MySql的自增,MyBatis查询到自增类型数据后可以设置返回到参数属性,其中学习了MySql的自增写法,堆栈对象等知识. MySql数据类型自增 建立一张Person表,其中 ...

  6. AOP的简单练习

    ---恢复内容开始--- 1.AOP的主要作用及概念简介 AOP最大的用处在于事务处理上,业务层在项目中主要负责以下的操作: ·调用数据层进行处理: ·进行事务的处理: ·关闭数据库的连接操作: 但在 ...

  7. 简单学习一下ibd数据文件解析

    来源:原创投稿 作者:花家舍 简介:数据库技术爱好者. GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 简单学习一下数据文件解析 这是尝试使用Golang语言简单解析My ...

  8. 开源项目Material Calendar View 学习记录 (一)

    开源项目Material Calendar View 学习记录 Github: https://github.com/prolificinteractive/material-calendarview ...

  9. .NetCore简单学习图谱

    一.学习途径 学习.netcore的最佳途径在哪里,无疑是微软官方.netCore指南.它覆盖十分全面,就目前网上经常看到的各种文章都能在微软处找到类似文章,堪称.netcore的百科全书.所以我利用 ...

随机推荐

  1. 各机器学习方法代码(OpenCV2)

    #include <iostream> #include <math.h> #include <string> #include "cv.h" ...

  2. CCF-模板生成系统-201509-3

    主要是string---STL的运用 趁机整理一下erase, find, substr, replace, insert #include <bits/stdc++.h> using n ...

  3. day06 内存地址 小数据池缓存机制

    1. 内存相关 示例一 v1=[11,22,33] v2=[11,22,33] #值相等 内存地址不等 v1=11 v2=11 #按理说内存地址应该不等,但是python为了优化使其内存地址相等 v1 ...

  4. spring boot 1.4 整合 mybatis druid

    http://www.jianshu.com/p/cef49ad91ba9spring boot 1.4 整合 mybatis druid

  5. wikipedia 维基百科 语料 获取 与 提取 处理 by python3.5

    英文维基百科 https://dumps.wikimedia.org/enwiki/ 中文维基百科 https://dumps.wikimedia.org/zhwiki/ 全部语言的列表 https: ...

  6. Linux platform平台总线、平台设备、平台驱动

    平台总线(platform_bus)的需求来源? 随着soc的升级,S3C2440->S3C6410->S5PV210->4412,以前的程序就得重新写一遍,做着大量的重复工作, 人 ...

  7. shutil 拷贝 / 移动 / 压缩 / 解压缩

    # shutil_demo.py 高级文件操作(拷贝 / 移动 / 压缩 / 解压缩) import shutil def shutil_demo(): # 拷贝文件 shutil.copy2('fi ...

  8. 初识TypeScript

    环境配置 1,全局安装node和npm 2,安装TypeScript包 npm install typescript -g tsc --version 编写第一个ts程序 1,初始化项目 新建文件夹d ...

  9. Let'sencrypt.sh 抛出异常: Response: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:726)>

    起因 今天网站的SSL证书过期了,打算重新申请,运行 Let'sencrypt.sh 的时候抛出了这么个异常. 一番搜索,发现居然找不到直接的答案.没有直接的答案就只能通过间接的答案来解决了. 希望我 ...

  10. 通过eclipse打开jdk native源码

    1.下载 eclipse http://www.eclipse.org/downloads/eclipse-packages/ 建议下载 Eclipse IDE for Eclipse Committ ...