spring(六).Spring中的AOP

                                        Spring中的AOP

目录

一.AOP思想

二.AOP应用

1.简单的例子

(1).基于配置

(2).基于注解

2.要点总结

(1).切面组件 Aspect

(2).通知 Advice

(3).切点 Point

3.案例实战

(1).在Spring容器中配置aop

(2).编写切面组件

(3).人为构造异常

(4).测试

三.参考资料


一.AOP思想

在学习Spring的AOP之前,我们先看看另外两个概念,面向过程编程和面向对象编程。

面向过程编程(Procedure Oriented Programming):简称OPP,是一种以过程为中心的编程思想,围绕当前需要的功能和目标具体化的,流程化的去实现。很直接,想要干什么就直接去干。

编程角度:编程者把要做的功能当成一个目标(其他什么都不考虑),一步一步去实现,去完成,去达到。

例如:Pascal,C等语言就是这样的编程思想。

面向对象编程(Object Oriented Programming):简称OOP,是一种以对象为中心的编程思想。要完成一个功能,一般需要一个对象或多个对象的特性和行为去完成。是在面向过程的基础上进行了抽象。

编程角度:编程者认为世界中的任何事物都是一个个体,一个单体,一个对象,都有特点(属性)和行为(方法)。

例如:C++、C#、Java等语言就是这样的编程思想。

面向切面切面编程(Aspect Oriented Programming):简称AOP,是一种以横切为中心的编程思想。面向对象编程是把任何东西都看成一个整体,一个对象的。往往这个整体之间又需要一些通用的,附加的东西,那我们就需要把这个整体切开,把需要的通用的东西放进去,这样去实现一个功能。

可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

二.AOP应用

下面说到的全部实例,都是基于一个搭好springMVC项目完成的:这个项目名称是spring_demo1,是一个前后端分离的实现了简单用户登录的web项目,详情可点击:spring_demo1

1.简单的例子

(1).基于配置

需求:我们要在每次调用Controller层前,打印一句话,“2018-11-04 19:44:30 调用了controller”,里面的绿色字体时间是调用时的当前系统时间。

a.在com.cdd包下新建一个包aspect,在com.cdd.aspect包编写切面组件PrintLog,代码入下:

package com.cdd.aspect;

import java.text.SimpleDateFormat;
import java.util.Date;

public class PrintLog {
	public void callTime(){
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:MM:ss");
		String str_date = sdf.format(date);
		System.out.println(str_date + "调用controller了");
	}
}

b.配置切面组件

在spring容器配置文件applicationContext.xml中配置切面组件,先把PrintLog类注册为bean,再在aop配置中将该bena配置成切面组,调用在进入com.cdd.controller包下组件之前(切点),先调用切面组件的callTime方法 ,配置部分如下:

c.测试。

在浏览器地址栏输入:http://localhost:8080/spring_demo1/login.html 回车,再响应页面输入用户名:cdd,密码:1234,点击登录后(当然其他能调controller层的方式都可以,比如单元测试,postman发请求等),查看控制台,如下图:

(2).基于注解

需要:在调用业务层service完成后,控制台打印“2018-11-04 19:44:30 sercice层调用完了”,这句话。

a.开启aop注解支持

在spring容器配置文件applicationContext.xml中配置,开启aop注解支持。配置部分如下:

b.编写切面组件PrintLog1,代码如下:

package com.cdd.aspect;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component  //将该组件注册到spring容器中
@Aspect     //将该组件配置为切面组件
public class PrintLog1 {
	
	@After("within(com.cdd.serviceImpl..*)")  //配置通知和切点
	public void callTime1(){
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String str_date = sdf.format(date);
		System.out.println(str_date+" sercice层调用完了");
	}
}

c.测试

在浏览器地址栏输入:http://localhost:8080/spring_demo1/user/login.do?name=cdd&pwd=1234,点击回车(或者其他方法,只要能调到service层就行),查看控制台如下,就说这个切面组件配置成功了

注意:

如果在上面的例子中,控制台报: java.lang.NoClassDefFoundError: org/aspectj/lang/JoinPoint,如下图:

原因是:虽然 我们直接将Spring 依赖的所有的jar 包 导入进来 但是 我们因为使用了 表达式语言 我们需要引入一个 第三方的jar 包,com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar,那么想lib文件夹下引入该包,并且通过Build Path添加到项目中,重新启动tomcat后,测试就正常了。

2.要点总结

(1).切面组件 Aspect

功能:干什么

要切入什么功能,比如说打印一份调用日志或发生异常时,将异常信息保存到一个文件中,这往往是某个类的某个方法。

例如,刚才写的PrintLog中的callTime方法、PrintLog类中的callTime1()方法

(2).通知 Advice

时间:啥时候干

通知就是这个切面组件切入的时机,通俗点说,就是什么时候出现这个功能,发生这件事。重点是时机。

AOP有5大通知,AspectJ 支持 5 种类型的通知注解: 
@Before: 前置通知, 在方法执行之前执行 
@After: 后置通知, 在方法执行之后执行 
@AfterRunning: 最终通知, 在方法返回结果之后执行 
@AfterThrowing: 异常通知, 在方法抛出异常之后 
@Around: 环绕通知, 围绕着方法执行
其中,环绕通知 = 前置通知 + 后置通知

例如,在一个catch块中:

try{

      前置通知@Before

      //目标方法处理

      后置通知@AfterReturning

}catch(e){

       异常通知@AfterThrowing

}finally{

       最终通知@After

}

(3).切点 Point

地点:在哪儿干

就是往哪些组件上切入这样的功能。

在配置切点时,有两种表达式:分别是类型限定表达式、方法限定表达式

a.类型限定表达式

--类型限定表达式(对组件作用):within( 类型 )

例:

             within(org.service.UserService)   //单个组件UserService

             within(org.service.*)            //org.service包下的组件(有一个点)

             within(org.service..*)           //org.service 包及其子包下的所有组件(有两个点)

b.方法限定表达式

--方法限定表达式:execution(  修饰符 返回类型 方法名(参数)  )

表达式中:

          修饰符(可有可无),返回类型(没返回的话是void、有返回的话,返回什么类型就是类型、都适应的话用 * )。

         方法名(例:find* () 即,以find开头的方法)。

         参数(没有就不写,有什么类型就写什么类型, ,,两个点表示0个或多个参数)。

例:

              execution( * find*(..) )       //对以find开头的方法作用

              execution( * org.service.UserService.regist(..) )   //对指定包下的指定方法作用

              execution( * org.service..*.*() )  //对org.service包下及其子包下的所有放作用,相当于within(org.service.*)

补充:

         上述表达式可以使用! 、&& 、|| 运算符连接,

         例如:execution( * find ecution( *org.service..*.*() )

3.案例实战

需求:在登录时,如果发生异常,把这个异常信息保存到本地中,文件路径为,D:\\project\\test\\exception_log.txt文件中。

(1).在Spring容器中配置aop

在Spring容器配置文件applicationContext.xml中开启aop支持(如果已经配置了,可以忽略本步操作),如下图:

(2).编写切面组件

在com.cdd.aspect包下编写切面组件,SvaeException类,代码如下:

package com.cdd.aspect;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class SaveException {
	
	@AfterThrowing(throwing="ex",pointcut="within(com.cdd.controller.*)")
	public void exceptionMessage(Exception ex){
		//控制台打印下这个异常信息
		System.out.println(ex);
		try {
			FileWriter fw = new FileWriter("D:\\project\\test\\exception_log.txt");
			PrintWriter pw = new PrintWriter(fw);
			ex.printStackTrace(pw);
			pw.flush();
			pw.close();
			fw.close();
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("保存异常信息发送异常");
		}
	}
}

(3).人为构造异常

在UserServiceImpl组件的login()方法里构造一个数组下标越界异常(要记得测试完成后去掉这个异常),代码如下:

package com.cdd.serviceImpl;

import java.security.NoSuchAlgorithmException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.cdd.dao.UserDao;
import com.cdd.entity.DataResult;
import com.cdd.entity.User;
import com.cdd.service.UserService;
import com.cdd.util.FirstUtil;

@Service
public class UserServiceImpl implements UserService {

	@Autowired
	private UserDao userDao;
	
	public DataResult Login(String name, String pwd) {
		System.out.println("正在service层");
		DataResult dataResult = new DataResult();
		//给密码进行md5加密
		try {
			pwd = FirstUtil.md5(pwd);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		
		}
		Integer isExist = userDao.checkLogin(name, pwd);
		if(isExist==null || isExist==0){
			dataResult.setStatus(-1);
			dataResult.setMsg("用户名或密码错误");
		}else{
			dataResult.setStatus(0);
			dataResult.setMsg("登录成功");
		}
		
		//构造一个数组下标越界异常
		int[] a = {1};
		System.out.println(a[2]);
		
		return dataResult;
	}
}

(4).测试

在浏览器地址栏输入:http://localhost:8080/spring_demo1/user/login.do?name=cdd&pwd=1234,点击回车,(其他方式也行,只要能从Controller层调用这个登录功能就行)。

a.查看控制台:

b.查看异常信息是否保存到exception_log.txt文件中了

到这里说明我们的认为完成了。

三.参考资料

aop思想:https://www.cnblogs.com/hongwz/p/5764917.html

aop通知:https://blog.csdn.net/sinat_28978689/article/details/62215513

发布了45 篇原创文章 · 获赞 28 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/weixin_41968788/article/details/83718133
今日推荐