AOP是一种思想,一种编写方式。编写一段代码在合适的时机找到切入点然后执行。不直接修改原来的代码,而是在源代码执行前后执行一段额外的代码。
AOP联盟,一群热爱技术的人觉得AOP思想不错,于是打算促进和标准化AOP,定制了一套标准,推动AOP和JAVA发展。
那么AOP联盟的标准是什么呢?
spring-aop jar包中有个目录org.aopalliance,其中存在几个接口,即是AOP联盟提供的AOP标准API。
上述中出现的关键词有Advice(顶级通知类/拦截器),MethodInvocation(方法连接点)、MethodInterceptor(方法拦截器),SpringAOP在此基础上又增加了几个类,丰富了AOP定义及使用概念,包括
Advisor:包含通知(拦截器),Spring内部使用的AOP顶级接口,还需要包含一个aop适用判断的过滤器,考虑到通用性,过滤规则由其子类接口定义,例如IntroductionAdvisor和PointcutAdvisor,过滤器用于判断bean是否需要被代理。
PointCut:切点,属于过滤器的一种实现,匹配过滤哪些类哪些方法需要别切面处理,包含一个MethodMatcher和一个ClassFilter,使用PointcutAdvisor定义时需要。
ClassFilter:属于过滤器的一种实现。过滤筛选合适的类,有些类不需要被处理。
MethodMatcher:方法匹配器,定义方法匹配规则,属于过滤器的一种实现,哪些方法需要使用AOP。
SpirngAop实现的大致思路:
这一步对于SpringAop使用者很关键,决定了我们如何自定义配置Advisor,即SpringAOP和Aspectj,对于Aspectj是一种静态代理,下面会讲到,而SpringAOP是动态代理,但Aspectj的一套定义AOP的API非常好,直观易用。所以Spring引入了Aspectj,但只使用部分注解用来定义配置AOP,在获取Advisor阶段用来生成Advisor,与后面的代理生成和代理增强执行无关!
这两种方式有什么不同呢?
1.直接配置Advisor配置
实现Advisor接口,定义拦截器和拦截规则(切点)
2.间接配置Advisor
使用Aspectj jar包提供的注解定义AOP配置,由spring解析配置生成Advisor
最少需要定义三个类,一个Advisor的实现类,一个advice实现类(拦截器),一个aop适配过滤器(这里使用的Advisor为派生的PointcutAdvisor ,需要定义Pointcut切点),可以增加一个注解用于AOP埋点,需要给bean哪个方法进行切面,则在方法上加上该注解。
Advisor:MyAdvisor,返回一个Advice,
Advice:MyInterceptAdvice,拦截器,invoke方法中可以添加切面逻辑代码
PointCut: MyPointCut,切点,匹配过滤出需要切面的类及方法,查找方法头注解了MyAnnotation的方法。
埋点注解:MyAnnotation
MyAdvisor:
@Component
public class MyAdvisor implements PointcutAdvisor {
@Override
public Advice getAdvice() {
return new MyInterceptAdvice();
}
@Override
public boolean isPerInstance() {
return false;
}
@Override
public Pointcut getPointcut() {
return new MyPointCut();
}
}
MyInterceptAdvice
public class MyInterceptAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("go go go MyAdvisor process!!!");
return invocation.proceed();
}
}
MyPointCut
public class MyPointCut implements Pointcut {
@Override
public ClassFilter getClassFilter() {
return new MyClassFilter();
}
@Override
public MethodMatcher getMethodMatcher() {
return new MyMethodMatcher();
}
private class MyMethodMatcher implements MethodMatcher {
@Override
public boolean matches(Method method, Class<?> targetClass) {
Annotation[] annoArray = method.getDeclaredAnnotations();
if (annoArray == null || annoArray.length == 0) {
return false;
}
for (Annotation annotation : annoArray) {
if (annotation.annotationType() == MyAnnotation.class) {
return true;
}
}
return false;
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return false;
}
}
private class MyClassFilter implements ClassFilter {
@Override
public boolean matches(Class<?> clazz) {
return AnnotationUtils.isCandidateClass(clazz, MyAnnotation.class);
}
}
}
需要切面的Service类
@Component
public class TestService {
@MyAnnotation
public void test() {
System.out.println("test!");
}
}
再定义一个测试类
@Component
public class RunTest {
@Autowired
private TestService testService;
@PostConstruct
public void test() {
testService.test();
}
}
AOP之代理
AOP是通过代理方式实现的,由代理对象持有原对象,在执行原对象目标方法的前后执行增强的方法。
代理对象需要是原对象接口的实现或原对象的子类。
代理方式分为静态代理和动态代理,区别在于代理对象生成方式不同。
静态代理:编译器增强,编写代理class,使用代理类替换原有类进行调用。
动态代理:运行期增强,内存中动态生成代理类,使用反射动态调用原对象方法。
AOP的实现有:AspectJ、JDK动态代理、CGLIB动态代理、JBossAOP、JMangler(后面两种仅限于传说,没接触过)。
AspectJ属于静态代理,使用特定编译器在编译器生成代理对象并于源码融在一起,即修改了class文件。
JDK动态代理必须基于接口,即生成的代理对象是对原对象接口的实现,相当于替换了实现类。
CGLIB动态代理基于继承,即生成的代理对象是原对象的子类。
先看一个demo:
public class HelloWord {
public void sayHello(){
System.out.println("hello world !");
}
public static void main(String args[]){
HelloWord helloWord =new HelloWord();
helloWord.sayHello();
}
}
编写AspectJ类,注意关键字为aspect(MyAspectJDemo.aj,其中aj为AspectJ的后缀),含义与class相同,即定义一个AspectJ的类(需要用特定的编译器,了解一下就行)
aspect MyAspectJDemo {
/**
* 定义切点,日志记录切点
*/
pointcut recordLog():call(* HelloWord.sayHello(..));
/**
* 定义切点,权限验证(实际开发中日志和权限一般会放在不同的切面中,这里仅为方便演示)
*/
pointcut authCheck():call(* HelloWord.sayHello(..));
/**
* 定义前置通知!
*/
before():authCheck(){
System.out.println("sayHello方法执行前验证权限");
}
/**
* 定义后置通知
*/
after():recordLog(){
System.out.println("sayHello方法执行后记录日志");
}
}
pointcut定义切点,后面跟着函数名称,最后写匹配表达式,此时函数一般使用call()或者execution()进行匹配,案例中使用call():
pointcut 函数名 : 匹配表达式
pointcut recordLog():call(* HelloWord.sayHello(..));
案例:recordLog()是函数名称,自定义的,* 表示任意返回值,接着就是需要拦截的目标函数,sayHello(…)的…,表示任意参数类型。这里理解即可,整行代码的意思是使用pointcut定义一个名为recordLog的切点函数,其需要拦截的(切入)的目标方法是HelloWord类下的sayHello方法,参数不限。
advice定义通知语法,有一下五种类型:
[返回值类型] 通知函数名称(参数) [returning/throwing 表达式]:连接点函数(切点函数){
函数体
}
案例中的around环绕通知,可以通过proceed()方法控制目标函数是否执行
/**
* 环绕通知 可通过proceed()控制目标函数是否执行
* Object around(参数):连接点函数{
* 函数体
* Object result=proceed();//执行目标函数
* return result;
* }
*/
Object around():aroundAdvice(){
System.out.println("sayAround 执行前执行");
Object result=proceed();//执行目标函数
System.out.println("sayAround 执行后执行");
return result;
}
现在应该对切入点(pointcut)和通知(advice)有一些了解了,而切面则是定义切点和通知的组合,比如上面案例中使用aspect关键字定义的MyAspectJDemo,把切面应用到目标函数的过程称为织入(weaving);案例中定义的helloworld类中除了有sayhello()方法,还有main()方法,这些方法都可以被称为目标函数, 这些目标函数就被称为连接点,切入点的定义就是从这些连接点中过滤出来的:
AspectJ的织入方式及其原理
对于织入这个概念,可以理解为就是aspect(切面)应用到目标函数(类)的过程,这个过程一般分为静态织入和动态织入:
动态织入的方式是在运行时动态将要增强的代码织入目标类中,通过动态代理的方式实现;比如java的JDK动态代理(Proxy,底层通过反射实现)或者CGLIB动态代理(底层通过继承实现),Spring AOP 就是基于运行时增强的代理技术,AspectJ采用的是静态织入的方式,也就是在编译期织入,在这个期间使用aspectJ的编译器把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
Spring AOP 和AspectJ的目的是一致的,都是统一处理横切业务,但是与AspectJ不同的是,Spring AOP 更注重的是与Spring IOC容器的结合,因此在AOP的功能完善方面,AspectJ的具有更大的优势,同时,spring Aop注意到了AspectJ在AOP的实现方式上依赖于特殊编译器,因此Spring很机智的避免了,采用动态代理技术的实现原理构建Spring AOP 的动态织入,这是与AspectJ的最根本的区别,在AspectJ引用了@Aspect注解之后,spring也引进了,但是,spring只是引用了AspectJ的注解,仍然没有使用特殊的编译器,底层依然是动态代理技术的实现。
定义接口类
//接口类
public interface UserDao {
int addUser();
void updateUser();
void deleteUser();
void findUser();
}
定义目标类@Repository
public class UserDaoImp implements UserDao {
@Override
public int addUser() {
System.out.println("add user ......");
return 6666;
}
@Override
public void updateUser() {
System.out.println("update user ......");
}
@Override
public void deleteUser() {
System.out.println("delete user ......");
}
@Override
public void findUser() {
System.out.println("find user ......");
}
}
编写spring aop的aspect类:
@Aspect//定义切面类
public class MyAspect {
// 也可以这样定义切入点表达式
// /**
// * 使用Pointcut定义切点
// */
// @Pointcut("execution(* com.czy.UserDao.addUser(..))")
// private void myPointcut(){}
//
// /**
// * 应用切入点函数
// */
// @After(value="myPointcut()")
// public void afterDemo(){
// System.out.println("最终通知....");
// }
//前置通知
@Before(value = "execution(* com.czy.UserDao.*(..))")
public void before(){
System.out.println("前置通知。。");
}
//后置通知
@AfterReturning(value = "execution(* com.czy.UserDao.*(..))",returning = "returnVal")
public void after(Object returnVal){
System.out.println("后置通知。。"+returnVal);
}
/**
* 环绕通知
* @param joinPoint 用于执行切点的类
* @return
* @throws Throwable
*/
@Around(value = "execution(* com.czy.UserDao.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕同之前。。");
Object proceed = joinPoint.proceed();
System.out.println("环绕同之后。。");
return proceed;
}
/**
* 抛出通知
* @param e
*/
@AfterThrowing(value="execution(* com.czy.UserDao.*(..))",throwing = "e")
public void afterThrowable(Throwable e){
System.out.println("出现异常:msg="+e.getMessage());
}
/**
* 无论什么情况下都会执行的方法
*/
@After(value="execution(* com.czy.UserDao.*(..))")
public void after(){
System.out.println("最终通知....");
}
}
编写配置文件配置springioc容器管理
<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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 启动aspectJ注解-->
<aop:aspectj-autoproxy />
<!-- 定义目标对象-->
<bean id="userDao" class="com.czy.UserDaoImp"></bean>
<!-- 定义切面类-->
<bean id="MyAspect" class="com.czy.MyAspect"></bean>
</beans>
编写测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:SpringConfig.xml")
public class Test {
@Autowired
UserDao userDao;
@org.junit.Test
public void aspectJTest(){
userDao.addUser();
}
}