AOP相关概念
【场景描述】
结合前面的编码实操,已经解决了最初需求,推而广之,我们将纵向的从上到下的主要业务逻辑成为主线关注,如上面对User的CRUD操作;将增加日志记录、安全检查等横向性的问题,可以喝主线业务分离分开的称为(参考代理模式篇)
横切性关注点
AOP编程也就是基于上述描述推演开来。
1) 横切性关注点
我们加在主线业务前后的这些内容称为“横切性关注点”。根据OOP编程的原则,提供给多个地方调用的内容需要将它封装为一个方法,方法也不可能单独存在,需要存在一个类中,有此引出了后续概念。
2) 通知(advice)
将横向业务内容也即横切性关注点的内容封装为一个方法,这个方法称为“通知”advice
,它可以放在主方法的前面(比如User里面的add方法是主方法而checkSecurity可以放在add方法前面后面等,均可)也可以放在后面。除此外还有异常通知、环绕通知和最终通知。
即被方法化的横切性关注点称为通知,它可以放在前后等多个地方。
3) 连接点(joinpoint)
就是那些被拦截到的点,spring这些点就是方法。也就是主线业务中的主要方法,每一
方法就是一个连接点,比如User类的CRUD四个方法,就是4个连接点。
4) 切入点(pointcut)
就是要对那些方法进行拦截,是对需要拦截的jonitpoint的定义。
5) 切面(aspect)
一个个封装了的方法称为advice,每一个方法均应该封装在一个类中;此外,在
这些advice上也需要定义将这些通知用于那些方法前面或者后面,这些定义也封装在这个类中,称为切入点,它们以后就作用于joinpoint将这些统一封装起来的类称为切面。公式表达:aspect=pointcut+advice
6) 织入(weave)
将aspect运用到代理的目标对象上并导致proxy创建的过程称为织入。
1.2 使用Spring进行AOP编程
1.2.1 用注解配置方式进行AOP开发
1) 首先启动对@AspectJ注解的支持以及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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <!-- 首先启动对@AspectJ注解的支持 --> <aop:aspectj-autoproxy/> <bean id="myAspect" class="com.ailk.test.MyAspect"/> <bean id="userServiceImpl" class="com.ailk.service.impl.UserServiceImpl"/> </beans> |
2) 进行Aspect内部通知和切入点的配置
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut;
@Aspect publicclass MyAspect { //声明一个切入点并配置,UserServiceImpl类中的delete方法进行拦截 @Pointcut("execution (* com.ailk.service.impl.UserServiceImpl.delete(..))") privatevoid anyMethod() {}
@Before("anyMethod()") publicvoid checkSecurity() { System.out.println(" 前置通知,安全检查---------"); } @AfterReturning("anyMethod()") publicvoid checkSecurityAfterReturning() { System.out.println(" 后置通知,安全检查---------"); } @After("anyMethod()") publicvoid checkSecurityAfter() { System.out.println(" 最终通知,安全检查---------"); } @AfterThrowing("anyMethod()") publicvoid checkSecurityAfterThrowing() { System.out.println(" 例外通知,安全检查---------"); } @Around("anyMethod()") public Object checkSecurityAround(ProceedingJoinPoint pjp) throws Throwable { System.out.println(" 环绕通知进入**************************"); Object obj = pjp.proceed(); System.out.println(" 环绕通知结束**************************"); return obj; } /* 【方法输入参数的精细化定义,按照输入参数匹配】 * 最上面的pointcut定义了对com.ailk.service.impl.UserServiceImpl.delete类包下的 * delete方法有效它可以接受任何参数,下面是更精细化的配置,要求输入 * 参数还要有long uuid,String name两个才行,可以当做是delete(long uuid,String name) * 这样的方法才起效。 * */ @Before("anyMethod() && args(uuid,name)") publicvoid checkSecurity(long uuid,String name) { System.out.println(" 前置通知,安全检查有参数---------"); } /* 【方法输入参数的精细化定义,通过切面获得方法的返回值】 * 最上面的pointcut定义了对com.ailk.service.impl.UserServiceImpl.delete类包下的 * delete方法有效它可以接受任何参数,下面是更精细化的配置,要求方法返回值的必须是String类型 * 的才起效且可以捕捉到方法调用时的返回值。 * */ @AfterReturning(pointcut="anyMethod()",returning="retValue") publicvoid checkSecurityAfterReturning(String retValue) { System.out.println(" 后置通知,安全检查---------"+retValue); } } |
import com.ailk.interfaces.UserService; import com.ailk.po.User;
publicclass UserServiceImpl implements UserService { publicvoid add(User user){ System.out.println("add用户成功"); } publicvoid delete(long uuid){ //int number = 10/0; System.out.println("delete用户成功"); } publicvoid update(long uuid){ System.out.println("update用户成功"); } publicvoid getUserInfo(long uuid){ System.out.println("getUserInfo用户成功"); } } |
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.ailk.interfaces.UserService; import com.ailk.po.User;
publicclass Test4 { publicstaticvoid main(String[] args) { User user = new User(); user.setName("张三"); user.setPassword("1234");
ApplicationContext ac = new ClassPathXmlApplicationContext("AOP.xml"); UserService service = (UserService) ac.getBean("userServiceImpl"); service.add(user); System.out.println(); service.delete(1); System.out.println(); service.update(1); service.getUserInfo(1); } } |
1.2.2 用XML配置方式进行AOP开发
【切面类,里面无任何注解】
import org.aspectj.lang.ProceedingJoinPoint;
publicclass MyAspectByXML { publicvoid checkSecurityBefore(){ System.out.println(" 前置通知MyAspectByXML,安全检查---------"); } publicvoid checkSecurityAfterReturning(){ System.out.println(" 后置通知MyAspectByXML,安全检查---------"); } publicvoid checkSecurityAfter(){ System.out.println(" 最终通知MyAspectByXML,安全检查---------"); } publicvoid checkSecurityAfterThrowing(){ System.out.println(" 例外通知MyAspectByXML,安全检查---------"); } public Object checkSecurityAround(ProceedingJoinPoint pjp) throws Throwable{ System.out.println(" 环绕通知MyAspectByXML进入**************************"); Object obj = pjp.proceed(); System.out.println(" 环绕通知MyAspectByXML结束**************************"); return obj; } } |
【XML配置文件配置切面,结合前面的MyAspectByXML类】
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<!-- 首先启动对@AspectJ注解的支持 --> <aop:aspectj-autoproxy/>
<!-- 注解方式的切面 --> <bean id="myAspect" class="com.ailk.test.MyAspect"/>
<!-- XML配置文件方式的切面 --> <bean id="myAspectByXML" class="com.ailk.test.MyAspectByXML"/> <aop:config> <aop:aspect id="AspectByXML" ref="myAspectByXML"> <aop:pointcut id="mycut" expression="execution (* com.ailk.service.impl.UserServiceImpl.add(..))"/> <aop:before pointcut-ref="mycut" method="checkSecurityBefore"/> <aop:after-returning pointcut-ref="mycut" method="checkSecurityAfterReturning"/> <aop:after-throwing pointcut-ref="mycut" method="checkSecurityAfterThrowing"/> <aop:after pointcut-ref="mycut" method="checkSecurityAfter"/> <aop:around pointcut-ref="mycut" method="checkSecurityAround"/> </aop:aspect> </aop:config> <bean id="userServiceImpl" class="com.ailk.service.impl.UserServiceImpl"/> </beans> |
【备注】
表达式如何写:
Ø execution (* com.ailk.service.impl.UserServiceImpl.add(..))
表示任意返回值com.ailk.service.impl.UserServiceImpl这个类里面的add方法,该方法输入参数不限;如果add方法修改为*,该类里面的任意方法。
Ø execution (java.lang.String com.ailk.service.impl.UserServiceImpl.add(..))
返回值是String的方法才起效,其它同上
Ø execution (*com.ailk.service.impl.UserServiceImpl.add(java.lang.String,..))
表示该add方法的第一个入参必须是String型,后面的其它参数不管,其它同上。
Ø execution (!void com.ailk.service.impl.UserServiceImpl.add(..))
返回值只要不是void的都处理。
Ø execution (* com.ailk.service.impl..*.add(..))
impl包下面的全部都起效