首先让我们先说一下切面需要我们关注哪些地方(组成部分),
1.切面点表达式[pointcut]:指定类的指定方法(就是为了定位指定类的指定方法)
2.方位:定义在指定方法的前,后,抛出异常,等等
3.执行横切业务逻辑:公共部分的代码,
注:那什么是公共部分的代码?如:日志记录,性能统计,安全控制,事务处理等方面
4.通知[advice] : 包涵方位,横切业务逻辑
5.连接点[JoinPoint] : 包涵切点表达式,方位
什么意思那??一张图告诉你,,,
接着我们要知道一个常识 ;
我们AOP可以实现的功能不用AOP也可以实现
那问题来了,我们不用AOP怎么实现这个日志的记录
那我们就举个Product的栗子:
比如写增加时(伪代码)
addProduct(){
log.info("开始执行addProduct方法")
添加产品的核心逻辑
log.error("执行出错:某某某")
}
那删除也是一样的
deleteProduct(){
log.info("开始执行deleteProduct方法")
添加产品的核心逻辑
log.error("执行出错:某某某")
}
之后的修改也一样,,,
那,问题来了,这样写方便吗???
是,现在写,感觉挺方便,但是有没有考虑到一个问题,
这是在写一个的增删改,那么我们在正常的项目里,有多少条增删改,,,
1百?一千?1一万???
那你就按1千算,1000×(3+2)=6000行代码
你这样就莫名其妙的写了6000行或者说N行代码,
你不会用AOP时,你又要必须写和用,你就得这样写
......
程序猿开发效率,代码冗余的问题,这都是很严重的问题
所以说,怎么解决现在这些非核心业务逻辑的代码,
那就用到切面...
那,一句话怎么总结这个切面,
在指定类的指定方法的前后进行特定的业务逻辑
为什么使用这个切面:
还是刚刚说的,为了将代码中的非核心代码提取出来,进行统一的处理,从而提高开发效率,让程序猿只需要关心核心业务逻辑代码
之后我们会说一说如何实现一个基本的前置通知与后置通知,让大家先了解了解
首先我们说说前置通知,
先来的简单的要求,要求:
在controller层的每个类的每个方法之前,打印出 '你好Java' 在说之前那我要强调的是,大家要知道,能用注解完成的,都可以用配置文件来完成,相反,用配置文件完成的也可以用注解来完成,
我就不用注解了,用注解的方式来完成
1、首先我们先建一个新的class文件,我的就叫 FHspect
2、直接输出 你好java
现在就写好了,就差配置它了,如果我不配置它,它就是一个普通的java类,如果我给它配置好,它就被称为切面类而不是普通的java类,这个类 叫 '切面类' ,这个方法就叫 '横切业务逻辑'
3、在spring-controller.cml里配置如下
我一一给大家解释,
①首先是
②而
③
④
⑤要知道
总结: 在这些 com.fh.shop.controller..*.*(..) 方法的前面执行 com.fh.shop.aspect.FHAspect 这个切面类中的 showInfo 的方法.
注:
这样就配置好了,那接下来就可以看效果了,,, ,,,
成功输出...
还是,再来说一个要求:
在controller层的每个类的每个方法之前/后,打印出 '你好Java_方法名()'
还是写一个普通的方法,
public void showInfo(JoinPoint jp){
System.out.println("你好JAVA");
}
那怎么怎么获取方法名
public void showInfo(JoinPoint jp){
String methodName = jp.getSignature.getName();
System.out.println("你好JAVA");
}
注: getSignature 代表的是方法的签名,而 getName 代表的是方法名
最终的样子是如下的
//之前
public void showInfo(JoinPoint jp){
String methodName = jp.getSignature.getName();
System.out.println("前置:你好JAVA"+methodName +"()");
}
//之后
public void doInfo(JoinPoint jp){
String methodName = jp.getSignature.getName();
System.out.println("后置:你好JAVA"+methodName +"()");
}
这只是写完了一个普通方法,还是和刚刚一样,要去配置
在刚刚的配置后加上
实际效果如下:
这就好了,,,你就可以看看效果了
首先我们肯定是先要有一个类
我的就叫LogAspect
然后我需要导入相关的jar包了,
好了我的jar包也导入了然后我们就要该配置文件了,这总要有log4j来做为支持
log4j是放到resources文件夹下即可
我们一般项目上线后将log4j设置为 INFO 级别或比 INFO 级别更高的级别, debug 级别的太多了,所以一般不用,当然在开发的时候,当然随便了...
我们先设置成DeBug级别试一下 然后我们就启动,并访问以下,看它与正常情况下有什么区别,
就如你看到的一样,会将你执行的出你所用到的 SQL 语句,但是!!!毕竟 Debug打出来的数据太庞大了,大部分又不用,设置成 INFO 级别又不给打印 sql 语句,那我们就需要在log4j里配置一下了
log4j.rootLogger=info,名
log4j.logger.路径=debug
实际如下:
这两行的意思分别说说,
首先是第一行,log4j.rootLogger=info,名:
这是说,其他包下的(非项目中的包下的)都设置成 INFO 级别,
然后是第二行,log4j.logger.路径=debug:
只要是我这个项目路径下的全部都设置成 debug 级别
记住!现在是debug,是方便你做修改,但是!!!项目上线后,切记切记要将两个都改为INFO级别,或INFO更高的级别!!!
然后我们就要对所有的日志进行统一的处理,找到我们刚刚建的 LogAspect
我们就要先声明一个 log 的日志类
private static final Logger LOG = LoggerFactory.getLogger(LogAspect.class);
注:要注意的是Logger 用的是 org.slf4j 资源包,因为 slf 公司对 log 的资源做了封装,知道就行了...
接下来写一个 doLog 的普通方法,额,等等..
我们先要思考一个东西, 要知道,我们最后的要求是在控制台打印出:
执行XX类.方法名() 花费了XXX时间
执行XX类.方法名()报错:错误为XXX
好了,知道了要求,我们就可以用环绕通知了 ProceedingJoinPoint 实际代码为:
然后我们首先获取类名:
然后是方法名:
然后打印出来
打印有两种方式:
LOG.info("执行"+canonicalName+"类名为:"+methodName)
或者
LOG.info("执行{}类{}方法()",canonicalName, methodName)
注:这是用大括号当做占位符,然后用后面的别名替换占位符,小括号还是小括号
这些还是不够的,这样就需要讲到另一个东西了,
如果只有上面的这些代码,你是不能跳转,最后你跳转的结构是404,还需要写一个东西,
Object result = pjp.proceed()
还要捕获一下,而这句话的意思是:执行真正的业务逻辑
什么叫执行真正的业务逻辑?你可以理解为,执行了就不会出现刚刚的404
但是,光这样你还是会被环绕通知给拦截掉,所以,还要返回 result
在最上面先声明一个全局变量,再在下面返回 result
完整代码如下:
当然,这个方法也要改,它是有返回值的,它的返回值是 Object 类型,要不然,你还是会被环绕通知的切面类里面的方法拦截的
当然了,代码写完了,配置文件还是要配置的...
在配置再来个
再来个
后面的被遮住的 id 还是与 pointcut-ref 保持一致
然后就写完了,你就可以访问页面来看看效果了...
之后我们还要加一些东西,花费的时间,
在执行方法之前加如下内容,
这意思是:记录当前时间
然后在执行完后,再写一个:
意思是:记录结束时间
最后,在之前的info里面再加个如下内容:
而这个end-start是说:用最后的结束时间,减去开始时记录的时间最后等于中间执行一共花费的时间
注:两个所记录的时间最小到毫秒值,所以,最后减出来的也是到毫秒值
实际代码如下:
那,出现异常了又改怎么办?
还是和刚刚的一样也是写一个
那我们对于异常的处理,都是往外抛
最后整体的代码如下:
最后我们执行出来的效果也有了:
错误的打印我也有
错误级别就是error级别,因为我们给的级别比较低,是 debug 级别,所以只要比debug级别高的都会显示,哪怕我们最后项目上线,我们把级别改成 INFO 级别的,但是它还是会在控制台打印出 INFO 级别和 error 级别
在这里那我就说一下我们最常用几个级别,最常用的不是所有,
error>warn>info>debug
就行了....