1.基本概念
日志,安全和事务管理都是AOP可以应用的地方。
应用AOP之后,日志的代码,安全的代码,事务的代码不会散落在各个模块,由Spring的AOP统一管理。如果通用的行为不满足,比如日志的级别不同,也可以自己按照需求再写一次即可。
Spring中的AOP是通过动态代理来实现的,类是继承于接口的会调用JDK的动态代理,否则使用CGLIB。完成代理的动作是在运行期动态完成的。
Spring AOP,AspectJ和JBoss是AOP的三个主要的框架。Spring只支持对方法进行拦截,而AspectJ和JBoss还支持对属性和构造函数的拦截。如果希望拦截到属性的变化,可以在Spring中引入AspectJ。
2.切点
切点和通知是切面的最基本的元素。
分布于应用中多处的功能被称为横切关注点,例如上图中的内容服务、计费服务等。
切点参数的含义:
execution(* com.spring.service.AService.*(..))
第一个*表示任意的返回类型,com.spring.service.AService指定了一个接口,第二*表示包含接口的任意方法,..表示任意的参数类型。指定了接口AService,则对实现了接口的任意子类都包含在内。
execution(* com.spring.service..*.*(..))
和上面的进行比较,第一个*表示任意的返回类型,第一个..表示com.spring.service及其子包都将被拦截,*.*代表任意类的任意方法,第二个..表示任意的参数类型
execution(* com.spring.service.AService.*(..)) && within(com.spring.service.impl.*)
表示同时要是com.spring.service.impl包下面的AService接口的实现类。表达式间的操作符包括&&,||,!或者and,or,not。
execution(!void com.spring.service.AService.*(java.lang.String,..)) ,第一个参数为String,后面可为任意个数,任意类型的参数。返回类型不为void。
如果需要知道一个方法执行花费了多长时间,环绕通知是很合适来完成的。环绕通知还很适合于做权限控制。
为切点增加参数信息execution(* com.spring.service.AService.*(java.lang.String)) and args(thoughts),将AService实现类方法中的String类型的thoughts作为参数传递给拦截器,可以获取方法的输入参数。
还可以将切点写为execution(* com.spring.service.AService.*(..)),再在具体的方法上加上输入参数,如:
@Before("anyMethod() && args(name)) //与方法的参数 名称name要一致
public void accessCheck(String name),作用相当于取交集,先满足切点条件,再要满足输入参数是唯一的,且为String类型,类似add(String name,Integer id)的方法将不会被拦截。
上面是获取方法的输入参数,下面的是获取方法的返回结果的实例:
@Before(pointcut = "anyMethod() ", returning = "result") //与方法的参数 名称result要一致
public void accessCheck(String result)
3.通知的概念
通知是指在执行横切关注点中的方法时,在执行动作的前,后等要执行的一些动作,如记录日志,调用事务等。
对应于配置文件中的标签如下:
4.基于XML的AOP实现
aop:aspect ref ="audience",表示切点要依赖于id为audience的bean,各种通知也是在id为audience的bean中定义的。
获取输入的参数:
5.基于注解的AOP实现
使用注解时@Pointcut需要依附于一个方法,方法本身不重要,可以是空实现。
基于注解及自动扫描实现IoC和AOP。
配置文件contextByAspect.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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd "> <context:annotation-config /> <!-- 自动扫描(自动注入) --> <context:component-scan base-package="com.spring..*"/> <aop:aspectj-autoproxy/> </beans>
定义接口类:
package com.spring.service; /** * 接口A */ public interface AService { public void fooA(String _msg); public void barA(); }
定义实现类:
package com.spring.service.impl; import org.springframework.stereotype.Component; import com.spring.service.AService; /** *接口A的实现类 */ @Component("aService") public class AServiceImplByAspect implements AService { public AServiceImplByAspect(){ System.out.println("a begin!"); } public void barA() { System.out.println("AServiceImpl.barA()"); } public void fooA(String _msg) { System.out.println("AServiceImpl.fooA(msg:"+_msg+")"); } }
定义切面package com.spring.aopimport org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 切面
*
*/
@Aspect
@Component
public class TestAspectByAspect {
@Pointcut(value = "execution(* com.spring.service.AService.*(..))")
public void before(){
}
public void doAfter(JoinPoint jp) {
System.out.println("log Ending method: "
+ jp.getTarget().getClass().getName() + "."
+ jp.getSignature().getName());
}
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long time = System.currentTimeMillis();
Object retVal = pjp.proceed();
time = System.currentTimeMillis() - time;
System.out.println("process time: " + time + " ms");// if(拥有权限){
// Object retVal = pjp.proceed();
//}else{
//
//}return retVal;
} @Before(value = "before()") public void doBefore(JoinPoint jp) { System.out.println("log Begining method: " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName()); } public void doThrowing(JoinPoint jp, Throwable ex) { System.out.println("method " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName() + " throw exception"); System.out.println(ex.getMessage()); } private void sendEx(String ex) { //TODO 发送短信或邮件提醒 } }
定义单元测试类:
package com.spring.test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Component; import org.springframework.test.AbstractDependencyInjectionSpringContextTests; import com.spring.service.AService; import com.spring.service.impl.BServiceImplByAspect; public class AOPTestByAspect extends AbstractDependencyInjectionSpringContextTests { @Autowired(required = false) private AService aService; protected String[] getConfigLocations() { String[] configs = new String[] { "/contextByAspect.xml"}; return configs; } /** * 测试正常调用 */ public void testCall() { System.out.println("SpringTest JUnit test"); aService.fooA("JUnit test fooA"); aService.barA(); } public void setAService(AService service) { aService = service; } }
Spring的AOP是基于代理的,如果所需求的功能超出了Spring的代理功能,就可考虑AspectJ的使用。
6.使用AOP动态的为类添加方法
前面的都是在拦截的方法前后做一些事情,下面的配置可以动态地为接口Perfomer的实现类增加一些方法。
或者进行如下的配置: