一. AOP
AOP,是非常深奥的,学识太少,所以只能是简单AOP开发。 这里不涉及太深的东西。
AOP,即 面向切面编程,Aspect Oriented Programming 。以前面向对象是纵向编程,AOP相当于是横向编程。 与IOC 一起,构成了Spring 的核心思想。
常常用来进行,日志记录,性能统计,安全控制,事务处理,异常处理等。
图片内容引用于: https://www.cnblogs.com/hongwz/p/5764917.html
下面,通过一个具体的实例来说明一下。
一个简单的例子: 在userDao 层实现时, 添加操作前的日志和操作后的日志。
二. AOP 之前的作法
二.一 UserDaoImpl 类中
@Repository(value="userDaoImpl") public class UserDaoImpl implements UserDao { @Override public int add() { System.out.println("添加用户的方法 userDao"); return 1; } }
这是一个简单的add() 添加方法。 需要在add() 方法里面记录操作前的时间和操作后的时间。 以前的作法是。
二.二 添加一个Logger 日志接口和实现类
package com.yjl.log; /** @author:yuejl @date: 2019年4月30日 下午3:38:09 @Description 类的相关描述 */ public interface Logger { public void before(); public void after(); }
实现类为:
package com.yjl.log.impl; import java.util.Date; import org.springframework.stereotype.Controller; import com.yjl.log.Logger; /** @author:yuejl @date: 2019年4月30日 下午3:39:22 @Description 类的相关描述 */ @Controller //进行注解 public class LoggerImpl implements Logger{ public void before(){ System.out.println("---------添加之前----------"+new Date().toLocaleString()); } public void after(){ System.out.println("---------添加之后---------"+new Date().toLocaleString()); } }
二.三 UserDaoImpl 新改变
需要添加Logger 接口的引用,并调用其中的before() 和after() 方法。
@Repository(value="userDaoImpl") public class UserDaoImpl implements UserDao { @Autowired private Logger logger; @Override public int add() { logger.before(); System.out.println("添加用户的方法 userDao"); logger.after(); return 1; } }
这样,在执行add() 方法的时候,就会执行 logger 中的before() 和after() 方法。
二.四 缺点
这样做,可以达到效果,但是却有问题。 一个类中有多个方法,而且每个项目中会存在多个类, 会造成很多类,很多方法中都要这么写,非常麻烦,更致命的是, 日志记录的方法 并不是每个类中方法所特有的东西, User 类中add() 方法,就是插入User 表数据, delete() 方法 就是删除User 表数据,与日志是没有关联的。
三. AOP 开发
三.一 引入jar 包
需要引入Spring 的aop jar包
其中,还需要引入aop 的关联jar 包
三.二 xml 约束
在xml 头部引入相应的约束。
xmlns:aop="http://www.springframework.org/schema/aop"
和
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
三.三 bean 配置
<!-- 采用xml的方式。 仍然用前面几章的例子 --> <!--关于loger的bean--> <bean id="logger" class="com.yjl.log.impl.LoggerImpl"></bean> <!-- 关于dao的bean --> <bean id="userDao" class="com.yjl.dao.impl.UserDaoImpl"></bean> <!-- 关于service的dao --> <bean id="userService" class="com.yjl.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean> <!-- 关于action的dao --> <bean id="userAction" class="com.yjl.web.action.UserAction" scope="prototype"> <property name="userService" ref="userService"></property> </bean>
三.四 AOP 配置 (重点)
<!-- 设置在dao中切入logger --> <aop:config> <!-- 配置切入点 。expression 会单独解释一下--> <aop:pointcut expression="execution(* com.yjl.dao.impl.UserDaoImpl.add(..))" id="pointCut1"/> <!-- 增强引用于的是哪个类 --> <aop:aspect ref="logger"> <!--method 为logger Bean中的方法, pointcut-ref 为上面配置的切入点--> <aop:before method="before" pointcut-ref="pointCut1"/> <!-- 最终增强 --> <aop:after method="after" pointcut-ref="pointCut1"/> </aop:aspect> </aop:config>
三.五 LoggerImpl 实现类
public class LoggerImpl implements Logger{ public void before(){ System.out.println("---------前置增强----------"+new Date().toLocaleString()); } @Override public void after() { System.out.println("---------最终增强---------"+new Date().toLocaleString()); } }
三.六 UserDaoImpl 实现类
@Override public int add() { System.out.println("添加用户的方法 userDao"); return 1; }
三.七 启动服务器,运行
会发现,在执行add() 方法时,会自动调用logger接口的before() 和after() 方法。 这就是AOP 的魅力。
四. AOP的术语解释及expression 表达式
四.一 术语解释
在AOP 中有一些常用的概念,如 切点,连接点,切面等。
很难懂,老蝴蝶给翻译翻译。
Joinpoint 连接点, 就是可以被增强的点。 如在UserDaoImpl 中 实现了常见的 add(), delete(), update(), get(), getAll() 等方法, 这些方法都可以像add() 一样被增强,被日志记录, 则这些方法都是 连接点。
Pointcut 切入点, 在实际开发中,只需要记录 add() ,delete(), update() 方法的日志即可,不需要记录 get() 和getAll() 的日志, 而且程序中也是要这么处理的, 那么 add,delete,update 就是切入点,即实际被切入的点。 而get() ,getAll() 不是。
Advice 增强, 由切入点 找到了这个方法,如add() 方法, 想要对这个方法进行什么处理操作呢, 如执行这个方法之前,做什么操作,(Before 前置通知), 执行这个方法后面做什么操作,(After-return 后置通知), 在目标方法执行前和执行后执行 (around 环绕通知), 出现异常的时候,做什么操作 (after-throwing异常通知), 最后执行完这个方法后,无论是否出现异常,都会做什么操作(after, 最终通知) . advice 就是切面要完成的功能,可以有多个。
四.二 expression 表达式
expression="execution(* com.yjl.dao.impl.UserDaoImpl.add(…))
[方法访问修饰符] 方法返回值 包名.类名.方法名(方法的参数)
可以拆成六部分.
1.execution () 表达式主体
2.* 返回值类型
3.包名 哪个包下
4.类名 如果是任意包下的任意类为 *
5.方法名 如add,delete 任意方法为 *
6.参数名 () 如果是任意的参数, 那么就是两个点 . .
expression 表达式,可以准确的找到是哪一个方法,包括重载的方法。
例子:
- UserDaoImpl 类的 add 方法
expression="execution(* com.yjl.dao.impl.UserDaoImpl.add(..))
- UserDaoImpl 类的所有方法
expression="execution(* com.yjl.dao.impl.UserDaoImpl.*(..))
- dao.impl 包下的所有类add 方法
expression="execution(* com.yjl.dao.impl.*.add(..))
- dao.impl 包下的所有类的所有方法
expression="execution(* com.yjl.dao.impl.*. *(..))
- com.yjl 包下的所有的包下的所有类的所有方法
expression="execution(* com.yjl.*.*.*(..))
- 所有包下的所有方法
expression="execution(* *.*.*(..))
- 所有公共的方法
expression="execution(public * *.*.*(..))
一般expression 表达式书写时,范围都尽量小,不能太大,一般为dao包下的所有类的所有方法。 即只记录与数据库有关的操作 (操作日志的时候,安全的时候不记录dao包。)
五. 所有通知集合
五.一 Logger 接口
package com.yjl.log; import org.aspectj.lang.ProceedingJoinPoint; /** @author:yuejl @date: 2019年4月30日 下午3:38:09 @Description 类的相关描述 */ public interface Logger { public void before(); public void afterReturn(); public int around(); public int aroundT(ProceedingJoinPoint p); public void throwExecption(); public void after(); }
五.二 LoggerImpl 实现
package com.yjl.log.impl; import java.util.Date; import org.aspectj.lang.ProceedingJoinPoint; import com.yjl.log.Logger; /** @author:yuejl @date: 2019年4月30日 下午3:39:22 @Description 类的相关描述 */ public class LoggerImpl implements Logger{ public void before(){ System.out.println("---------前置增强----------"+new Date().toLocaleString()); } public void afterReturn(){ System.out.println("---------后置增强---------"+new Date().toLocaleString()); } public int around() { // 在方法内部执行,所以返回值要与增强的方法的返回值一样。 System.out.println("---------我是一个普通环绕增强---------"); return 0; } @Override public void throwExecption() { System.out.println("---------我是一个异常增强---------"); } @Override public int aroundT(ProceedingJoinPoint p) { System.out.println("环之前"); int result=-1; try { result=(int) p.proceed(); //为dao中add() 方法的返回结果。 } catch (Throwable e) { e.printStackTrace(); } System.out.println("环之后"); // return 0 时, service 中得到的结果为0 return result; } @Override public void after() { System.out.println("---------最终增强---------"+new Date().toLocaleString()); } }
五.三 UserDaoImpl 中的add() 方法
@Override public int add() { System.out.println("添加用户的方法 userDao"); return 1; }
五.四 xml 配置
<!-- 设置在dao中切入logger --> <aop:config> <!--不要忘记添加 aopalliance 和aspectjweaver--> <!-- 配置切入点 . 可以有多种形式,分别说出是什么意思。--> <aop:pointcut expression="execution(* com.yjl.dao.impl.UserDaoImpl.add(..))" id="pointCut1"/> <!-- 把增强用到方法上 --> <aop:aspect ref="logger"> <aop:before method="before" pointcut-ref="pointCut1"/> <!-- 最终增强 --> <aop:after method="after" pointcut-ref="pointCut1"/> <!-- 后置增强 --> <aop:after-returning method="afterReturn" pointcut-ref="pointCut1"/> <aop:around method="around" pointcut-ref="pointCut1"/> <aop:around method="aroundT" pointcut-ref="pointCut1"/> <!-- 异常增强 --> <aop:after-throwing method="throwExecption" pointcut-ref="pointCut1"/> </aop:aspect> </aop:config>
五.五 重启服务器
没有异常增强和 环增强
将其去掉, around 只剩下一个aroundT
<!--<aop:around method="around" pointcut-ref="pointCut1"/>-->
出现了环绕增强,没有出现异常增强。 异常增强,只有在方法出现异常时,才出现。
将add() 方法改成:
@Override public int add() { System.out.println("添加用户的方法 userDao"); int i=10/0; return 1; }
可以看出,先执行 前置,然后是环绕,如果有异常,就执行 异常通知,然后是后置,最后是最终通知。
六 注解开发
六.一 开启AOP 注解的自动代码
<!-- aop注解的方式 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
六.二 在LoggerImpl类中添加注解@Aspect
@Component @Scope("prototype") @Aspect public class LoggerImpl implements Logger{
是org.aspectj.lang.annotation.Aspect 包下的注解。
六.三 添加在方法上配置注解
- 可以直接在后面跟 expression 语句。
@Before("execution(* com.yjl.dao.impl.UserDaoImpl.add(..))") public void before(){ System.out.println("---------前置增强----------"+new Date().toLocaleString()); } @AfterReturning("execution(* com.yjl.dao.impl.UserDaoImpl.add(..))") public void afterReturn(){ System.out.println("---------后置增强---------"+new Date().toLocaleString()); } //@Around("execution(* com.yjl.dao.impl.UserDaoImpl.add(..))") public int around() { // 在方法内部执行,所以返回值要与增强的方法的返回值一样。 System.out.println("---------我是一个普通环绕增强---------"); return 0; } @AfterThrowing("execution(* com.yjl.dao.impl.UserDaoImpl.add(..))") @Override public void throwExecption() { System.out.println("---------我是一个异常增强---------"); } @Around("execution(* com.yjl.dao.impl.UserDaoImpl.add(..))") @Override public int aroundT(ProceedingJoinPoint p) { System.out.println("环之前"); int result=-1; try { result=(int) p.proceed(); } catch (Throwable e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } System.out.println("环之后"); // return 0 时, service 中得到的结果为0 return result; } @After("execution(* com.yjl.dao.impl.UserDaoImpl.add(..))") @Override public void after() { System.out.println("---------最终增强---------"+new Date().toLocaleString()); }
- 如果配置的语句都一样的话, 可以这样写
@Pointcut(value="execution(* com.yjl.dao.impl.UserDaoImpl.add(..))") public void pointCut(){ } @Before("LoggerImpl.pointCut()") public void before(){ System.out.println("---------前置增强----------"+new Date().toLocaleString()); } @AfterReturning("LoggerImpl.pointCut()") public void afterReturn(){ System.out.println("---------后置增强---------"+new Date().toLocaleString()); }
运行之后是:
谢谢!!!