在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。我们常说:“这件事情要从几个方面来看待”,往往意思是:需要从不同的角度来看待同一个事物。这里的“方面”,指的是事物的外在特性在不同观察角度下的体现。而在AOP中,Aspect的含义,可能更多的理解为“切面”比较合适。它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
五种通知类型
- 前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。
- 正常返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
- 异常返回通知[After throwing advice]:在连接点抛出异常后执行。
- 返回通知[After (finally) advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
- 环绕通知[Around advice]:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
XML配置文件实现AOP
引入Maven依赖库
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
切面类
public class RegisterAop {
public RegisterAop() {
}
// 无参数切点
public void exampleAfterHandler(){
System.out.println("进入注册切点....................");
}
// 一个参数的切点
public void oneParamBeforeHandler(String smsVerifyCode){
System.out.println("Come in Aop Procduce : "+smsVerifyCode);
}
// 多个参数的切点
public void registerPointcutBeforeHandler(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
String smsVerifyCode = (String) args[0];
HttpServletRequest request = (HttpServletRequest) args[1];
HttpSession session = request.getSession();
JSONObject smsVerifyCodeInfo = (JSONObject) session.getAttribute("phoneSmsVerifyCode");
if(smsVerifyCodeInfo==null){
request.setAttribute("smsVerifyCodeErrorInfo","#NotHasVerifyCode");
return;
}
System.out.println("进入注册切点........SmsVerifyCode : "+smsVerifyCode);
}
}
上下文配置文件配置AOP
AOP配置放到Spring的上下文配置文件里。
如果是SpringMVC项目就放到MVC上下文配置文件里,如果还放到Spring上下文配置文件中会出现触发不了AOP的情况。
配置文件设置切面需要加入AOP的xml命名空间和schema:
SpringAOP命名空间:
xmlns:aop="http://www.springframework.org/schema/aop"
SpringAOP的schema路径:
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
applicationContext.xml
<!-- 切面类 -->
<bean id="RegisterAopAspectClass" class="cn.Aop.RegisterAop"></bean>
<aop:config>
<!-- 不带参数的切点-->
<aop:aspect id="registerAopAspectConfig1" ref="RegisterAopAspectClass">
<aop:pointcut id="ControllerProc1Pointcut" expression="execution(* cn.Controller.IndexController.proc2(..)))"/>
<aop:after method="exampleAfterHandler" pointcut-ref="ControllerProc1Pointcut" ></aop:after>
</aop:aspect>
<!-- 带一个参数的切点-->
<!-- 文件配置和注解配置在参数连接时用的符号不一样(配置文件:and )/(注解配置:&& )-->
<aop:aspect id="registerAopAspectConfig2" ref="RegisterAopAspectClass">
<aop:pointcut id="ControllerProc2Pointcut22" expression="execution(* cn.Controller.IndexController.oneParamProc(String )) and args( smsVerifyCode))"/>
<aop:before method="oneParamBeforeHandler" pointcut-ref="ControllerProc2Pointcut22" arg-names="smsVerifyCode"></aop:before>
</aop:aspect>
<!-- 带三个参数的切点-->
<aop:aspect id="registerAopAspectConfig3" ref="RegisterAopAspectClass">
<aop:pointcut id="RegisterProcPointcut" expression="execution(* cn.Controller.IndexController.registerProc(..)) and args(..))"/>
<aop:before method="registerPointcutBeforeHandler" pointcut-ref="RegisterProcPointcut" ></aop:before>
</aop:aspect>
</aop:config>
测试用Controller
@Controller
public class IndexController {
@RequestMapping("/")
@ResponseBody
public String proc1(){
return "hello mvc";
}
@ResponseBody
@RequestMapping("/hello")
public String proc2(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
ServiceInteface serviceInteface =(ExampleService) applicationContext.getBean("aaa");
System.out.println(serviceInteface.testProc());
serviceInteface.testProc();
return "good eveing";
}
@ResponseBody
@RequestMapping(value = "/oneParam")
public String oneParamProc( String smsVerifyCode ){
JSONObject result = new JSONObject();
result.put("status", "Param : " + smsVerifyCode);
return result.toJSONString();
}
@ResponseBody
@RequestMapping(value = "/register")
public String registerProc( String smsVerifyCode , HttpServletRequest request){
Object smsVerifyCodeErrorInfoStr = request.getAttribute("smsVerifyCodeErrorInfo");
String smsVerifyCodeErrorInfo = (String)smsVerifyCodeErrorInfoStr;
JSONObject result = new JSONObject();
result.put("state", "success");
result.put("info", smsVerifyCodeErrorInfo);
return result.toJSONString();
}
}
测试结果
正确切入方法,测试成功。
注解配置AOP
注解同样需要 aspectjweaver依赖库
上下文配置文件
需要开启配置:
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
<!-- <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>-->
<!-- proxy-target-class属性值决定是基于接口的还是基于类的代理被创建 (JDK动态代理或者CGlib) -->
注解切面类实现
@Component
@Aspect
public class RegisterAop {
// 关注点
@Pointcut(value = "execution(* cn.Controller.IndexController.proc2(..)))")
public void proc2Pointcut(){}
// 关注点
// 文件配置和注解配置在参数连接时用的符号不一样(配置文件:and )/(注解配置:&& )
@Pointcut(value = "execution(* cn.Controller.IndexController.oneParamProc(String )) && args( smsVerifyCode))")
public void oneParamProcPointcut(String smsVerifyCode){}
// 关注点
@Pointcut(value = "execution(* cn.Controller.IndexController.registerProc(..)) && args(..))")
public void registerPointcut(){}
public RegisterAop() {
}
// 通知点-无参数
@After(value = "proc2Pointcut()")
public void exampleAfterHandler(){
System.out.println("进入注册切点....................");
}
// 通知点-一个参数
@Before(value = "oneParamProcPointcut(smsVerifyCode)")
public void oneParamBeforeHandler(String smsVerifyCode){
System.out.println("Come in Aop Procduce : "+smsVerifyCode);
}
// 通知点-多个参数
@Before(value = "registerPointcut()")
public void registerPointcutBeforeHandler(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
String smsVerifyCode = (String) args[0];
HttpServletRequest request = (HttpServletRequest) args[1];
HttpSession session = request.getSession();
JSONObject smsVerifyCodeInfo = (JSONObject) session.getAttribute("phoneSmsVerifyCode");
if(smsVerifyCodeInfo==null){
request.setAttribute("smsVerifyCodeErrorInfo","#NotHasVerifyCode");
System.out.println("进入注册切点........SmsVerifyCode : "+smsVerifyCode);
return;
}
System.out.println("进入注册切点........SmsVerifyCode : "+smsVerifyCode);
}
}
测试结果
与上面结果一致。