就是在aspect里面使用pointcut来定义需要拦截的joinpoint,而后在weaving的过程中,为target对象附加上在aspect里面定义的advice的行为
先来看看spring实现代理的两种方式:jdk动态代理和cglib代理(必要做出解释的地方 代码里面都有说明了)
测试类SpringTestProxy.java
package com.undergrowth.test; import static org.junit.Assert.*; import java.util.ArrayList; import java.util.List; import org.aspectj.weaver.NewConstructorTypeMunger; import org.junit.Test; import com.undergrowth.utils.ProxyObjectInstance; /* * 本类只是测试Spring Proxy的两种实现方式 * 1.jdk dynamic proxy(jdk自带的动态代理) * 2.cglib proxy(第三方jar包的cglib代理) * * */ public class SpringTestProxy { @Test public void test() { ProxyObjectInstance proxyObjectInstance=new ProxyObjectInstance(); List arrayList=(List) proxyObjectInstance.createJdkProxyObject(new ArrayList()); System.out.println(arrayList.add(3)); arrayList=(List) proxyObjectInstance.createCglibObject(new ArrayList()); System.out.println(arrayList.add(3)); } }
控制台输出:
我是前置通知 我是后置通知 true 我是cglib的前置通知 我是cglib的后置通知 true
ProxyObjectInstance.java
package com.undergrowth.utils; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /* * 本类是使用jdk的动态代理和cglib的代理来为目标对象创建代理对象 */ public class ProxyObjectInstance implements InvocationHandler,MethodInterceptor{ private Object target; //使用jdk的动态代理 使用的是目标对象的类加载器和接口来创建代理对象 //并且为代理对象添加一个事件处理器InvocationHandler 当调用目标对象上的方法的时候 则调用事件处理器的方法即(invoke方法) public Object createJdkProxyObject(Object targetObject) { this.target=targetObject; Class targetClass=this.target.getClass(); return Proxy.newProxyInstance(targetClass.getClassLoader(),targetClass.getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //为了测试方便 则是输出测试语句 System.out.println("我是前置通知"); //调用目标对象的方法 Object returnValue=method.invoke(target, args); System.out.println("我是后置通知"); return returnValue; } //使用cglib创建代理对象 使用的是目标对象作为代理对象的父类 //代理对象重写目标对象的非final方法 并设置事件处理器MethodInterceptor //当调用目标对象上的方法的时候 则调用事件处理器的方法即(intercept方法) public Object createCglibObject(Object targetObject) { this.target=targetObject; Enhancer enhancer=new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object arg0, Method arg1, Object[] args, MethodProxy method) throws Throwable { //为了测试方便 则是输出测试语句 System.out.println("我是cglib的前置通知"); //调用目标对象的方法 Object returnValue=method.invoke(target, args); System.out.println("我是cglib的后置通知"); return returnValue; } }
可以看到 spring的两种代理方式 结果是一致的 当目标对象有接口时,为其使用jdk的动态代理 当目标对象没有接口时,则使用cglib代理来实现
接下来就是看看spring的aop编程 两种方式 注解和xml文件方式
先来看看注解的方式
测试代码:
SpringTestAspectJ.java
package com.undergrowth.test; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.undergrowth.aspect.test.bean.TargetObject; /* * 本类是使用aspectj来进行测试spring的aop编程 *1.先打开配置文件中的spring的aspectj的解析器 *2.使用@Aspect进行标示切面 * *如果是使用xml的方式的话 *则是使用xml配置的方式将pojo对象注册为一个aspect对象 * * * ** 对于 * Weaving(织入)---linking aspects with other application types or objects * to create an advised object. * 就是利用应用程序类型或者对象并且连接切面来创建通知对象的过程 * 例如spring的事务管理中,当使用@Transactional对类进行注解后,spring的容器就会使用aop的方式为我们目标类的方法 * 打开事务、关闭事务 此时为目标类打开事务、关闭事务的关注点即使我们所熟知的前置通知和后置通知 * * * 对于整个spring aop编程 个人理解 * 就是在aspect里面使用pointcut来定义需要拦截的joinpoint,而后在weaving的过程中, * 为target对象附加上在aspect里面定义的advice的行为 * */ public class SpringTestAspectJ { private static AbstractApplicationContext aac; @BeforeClass public static void beforeClass() { aac=new ClassPathXmlApplicationContext("applicationContext.xml"); } @Test public void test() { TargetObject targetObject= (TargetObject) aac.getBean("targetObject"); targetObject.say("spring aop annotation 测试"); } }
配置文件applicationContext.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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd"> <!-- enable Spring support for configuring Spring AOP based on @AspectJ aspects, and autoproxying beans based on whether or not they are advised by those aspects. 虽然看的不大明白 呵呵 我的理解是注册一系列的aspectj的注解解析器 有了他之后 你在java代码里面写的注解才会得到翻译 起到作用 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <bean id="aspectDefinition" class="com.undergrowth.aspect.AspectDefinition"></bean> <!-- 使用基于xml配置的方式 将纯java对象com.undergrowth.aspect.AspectXml注册为一个aspect 这种方式和使用注解的方式是一致的 --> <!-- <bean id="aspectXml" class="com.undergrowth.aspect.AspectXml"></bean> <aop:config> <aop:aspect ref="aspectXml"> <aop:pointcut expression="execution(* com.undergrowth.aspect.test.bean..*.*(..))" id="pointcut"/> <aop:before method="beforeAdvice" pointcut-ref="pointcut"/> <aop:after-returning method="afterReturn" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> --> <bean id="targetObject" class="com.undergrowth.aspect.test.bean.TargetObject"></bean> </beans>
切面类文件 AspectDefinition.java
package com.undergrowth.aspect; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; //切面就是许多关注点组成一个模块并且横跨多个java类 @Aspect public class AspectDefinition { //定义切入点--即要拦截哪些方法的过滤器 @Pointcut("execution(* com.undergrowth.aspect.test.bean..*.*(..))") private void interceptorDefini(){} //前置通知 @Before("interceptorDefini()") public void beforeAdvice() { System.out.println("前置通知"); } //后置通知 即在目标对象的方法返回之后执行 @AfterReturning("interceptorDefini()") public void afterReturn() { System.out.println("后置通知"); } }
目标对象 TargetObject.java
package com.undergrowth.aspect.test.bean; /* * 代理对象 */ public class TargetObject { public void say(String sayWhat) { System.out.println("你想说的是:"+sayWhat); } }
运行测试方法
控制台输出:
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. 前置通知 你想说的是:spring aop annotation 测试 后置通知
上面即是使用注解的方式进行实现spring sop
修改配置文件 applicationContext.java 如下
<?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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd"> <!-- enable Spring support for configuring Spring AOP based on @AspectJ aspects, and autoproxying beans based on whether or not they are advised by those aspects. 虽然看的不大明白 呵呵 我的理解是注册一系列的aspectj的注解解析器 有了他之后 你在java代码里面写的注解才会得到翻译 起到作用 --> <!-- <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <bean id="aspectDefinition" class="com.undergrowth.aspect.AspectDefinition"></bean> --> <!-- 使用基于xml配置的方式 将纯java对象com.undergrowth.aspect.AspectXml注册为一个aspect 这种方式和使用注解的方式是一致的 --> <bean id="aspectXml" class="com.undergrowth.aspect.AspectXml"></bean> <aop:config> <aop:aspect ref="aspectXml"> <aop:pointcut expression="execution(* com.undergrowth.aspect.test.bean..*.*(..))" id="pointcut"/> <aop:before method="beforeAdvice" pointcut-ref="pointcut"/> <aop:after-returning method="afterReturn" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> <bean id="targetObject" class="com.undergrowth.aspect.test.bean.TargetObject"></bean> </beans>
纯java类 AspectXml.java
package com.undergrowth.aspect; /* * 本类是使用xml的方式 将此类声明为一个aspect */ public class AspectXml { public void beforeAdvice() { System.out.println("使用xml方式的前置通知"); } public void afterReturn() { System.out.println("使用xml方式的后置通知"); } }
运行测试方法 控制台输出:
log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. 使用xml方式的前置通知 你想说的是:spring aop annotation 测试 使用xml方式的后置通知
个人感觉 用起来 事务管理的那一块实在是太方便了 简化了很多代码 当然 我的水平有限 可能有更多的功能 我还没发觉
上面的测试用例都写得比较简单 详细的在spring官方文档都有说明 记录学习的脚步 继续努力
附加: pointcut表达式的一些解释
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
All parts except the returning type pattern (ret-type-pattern in the snippet above), name pattern, and parameters pattern
are optional. The returning type pattern determines what the return type of the method must be in order for a join point to be matched. Most frequently you will use *
as
the returning type pattern, which matches any return type. A fully-qualified type name will match only when the method returns the given type. The name pattern matches the method name. You can use the *
wildcard
as all or part of a name pattern. The parameters pattern is slightly more complex: ()
matches
a method that takes no parameters, whereas (..)
matches
any number of parameters (zero or more). The pattern (*)
matches
a method taking one parameter of any type, (*,String)
matches
a method taking two parameters, the first can be of any type, the second must be a String. Consult the Language
Semantics section of the AspectJ Programming Guide for more information.
the execution of any method defined in the service package or a sub-package: execution(* com.xyz.service..*.*(..))