Spring学习之AOP
一、操作术语
连接点(Joinpoint):类里面可以被增强的方法,这些方法称为连接点。
切入点(Pointcut):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
通知/增强(Advice):所谓通知是指拦截到Joinpoint后要做的事情就是通知,通知分为前置通知、后置通知、异常通知、最终通知和环绕通知(切面要完成功能)。
切面(Aspect):切入点和通知的结合,即把增强应用到具体的方法上面,这个过程称为切面。
目标对象(Target):代理的目标对象(要增强的类)。
织入(Weaving):把增强(Advice)应用到目标(Target)的过程。
代理(Proxy):一个类被aop织入增强后,就产生一个代理结果类
二、实现策略
1、JDK动态代理
使用动态代理可以为一个或多个接口在运行期动态生成实现对象,生成的对象中实现接口的方法时可以添加增强代码,从而实现AOP。缺点是只能针对接口进行代理,另外由于动态代理是通过反射实现的,有时可能要考虑反射调用的开销。
2、字节码生成(CGLib 动态代理)
动态字节码生成技术是指在运行时动态生成指定类的一个子类对象,并覆盖其中特定方法,覆盖方法时可以添加增强代码,从而实现AOP。其常用工具是cglib。
3、定制的类加载器
当需要对类的所有对象都添加增强,动态代理和字节码生成本质上都需要动态构造代理对象,即最终被增强的对象是由AOP框架生成,不是开发者new出来的。解决的办法就是实现自定义的类加载器,在一个类被加载时对其进行增强。JBoss就是采用这种方式实现AOP功能。
4、代码生成
利用工具在已有代码基础上生成新的代码,其中可以添加任何横切代码来实现AOP。
5、语言扩展
可以对构造方法和属性的赋值操作进行增强,AspectJ是采用这种方式实现AOP的一个常见Java语言扩展。
三、编程式增强(无配置文件)
1、定义接口和实现类
package com.sgl.aop; public interface Person { void eat(String name); }
package com.sgl.aop; public class Student implements Person{ @Override public void eat(String name) { System.out.println("student eat "+name); } }
2、编写前置增强和后置增强
package com.sgl.aop; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.MethodBeforeAdvice; public class BeforeAndAfterAdvice implements MethodBeforeAdvice,AfterReturningAdvice{ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("after"); } @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("before"); } }
3、环绕增强
package com.sgl.aop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class AroundAdvice implements MethodInterceptor{ @Override public Object invoke(MethodInvocation invocation) throws Throwable { before(); Object result = invocation.proceed(); after(); return result; } public void before(){ System.out.println("before"); } public void after(){ System.out.println("after"); } }
4、单元测试
package com.sgl.aop; import org.junit.Test; import org.springframework.aop.framework.ProxyFactory; public class TestAop { @Test public void aopTest(){ //创建代理工厂 ProxyFactory factory = new ProxyFactory(); //添加代理目标对象 factory.setTarget(new Student()); //添加通知 factory.addAdvice(new BeforeAndAfterAdvice()); // factory.addAdvice(new AroundAdvice()); //获取代理对象 Person person = (Person) factory.getProxy(); person.eat("chicken"); } }
四、声明式增强(配置文件)
<?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-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <context:component-scan base-package="com.sgl.aop.xml1" /> <bean id="proxyObject" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="targetName" value="student" /> <property name="interceptorNames" value="beforeAndAfterAdvice" /> <property name="proxyInterfaces" value="com.sgl.aop.xml1.Person" /> </bean> </beans>
在代理目标和增强类添加@Component注解,创建bean
单元测试:
package com.sgl.aop.xml1; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestAop { @Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Person person = context.getBean("proxyObject",Person.class); person.eat("chicken"); } }
五、Spring的AspectJ自动代理
1、基于xml配置文件
package com.sgl.aop.xml; /** * 被代理的目标类 */ public class Math{ //加 public int add(int n1,int n2){ int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); return result; } //减 public int sub(int n1,int n2){ int result=n1-n2; System.out.println(n1+"-"+n2+"="+result); return result; } //乘 public int mut(int n1,int n2){ int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); return result; } //除 public int div(int n1,int n2){ int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); return result; } }
package com.sgl.aop.xml; public class Advices{ public void before(){ System.out.println("----------前置通知----------"); } public void after(){ System.out.println("----------最终通知----------"); } }
<?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-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <!-- 被代理对象 --> <bean id="math" class="com.sgl.aop.xml.Math" /> <!-- 通知 --> <bean id="advice" class="com.sgl.aop.xml.Advices" /> <!-- aop 配置 --> <aop:config> <!--切面 --> <aop:aspect ref="advice"> <!-- 切点 --> <aop:pointcut expression="execution(* com.sgl.aop.xml.*.*(..))" id="pointcut"/> <!--连接通知方法与切点 --> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
package com.sgl.aop.xml; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml"); Math math = context.getBean("math",Math.class); int n1 = 100, n2 = 5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2); } }
2、基于注解
package com.sgl.aop.annotion; import org.springframework.stereotype.Component; /** * 被代理的目标类 */ @Component public class Math{ //加 public int add(int n1,int n2){ int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); return result; } //减 public int sub(int n1,int n2){ int result=n1-n2; System.out.println(n1+"-"+n2+"="+result); return result; } //乘 public int mut(int n1,int n2){ int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); return result; } //除 public int div(int n1,int n2){ int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); return result; } }
package com.sgl.aop.annotion; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Component @Aspect public class Advices{ @Before(value="execution(* com.sgl.aop.annotion.*.add*(..))") public void before(JoinPoint joinPoint){ System.out.println("----------前置通知----------"); System.out.println(joinPoint.getSignature().getName()); } @After(value="execution(* com.sgl.aop.annotion.*.*(..))") public void after(){ System.out.println("----------最终通知----------"); } }
<?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-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <context:component-scan base-package="com.sgl.aop.annotion" /> <!-- 开启aop注解扫描 --> <aop:aspectj-autoproxy proxy-target-class="true" /> </beans>
package com.sgl.aop.annotion; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml"); Math math = context.getBean("math",Math.class); int n1 = 100, n2 = 5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2); } }
3、定义pointcut,复用切点
package com.sgl.aop.annotion1; import org.aspectj.lang.JoinPoint; 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; import org.springframework.stereotype.Component; @Component @Aspect public class Advices{ //切点 @Pointcut(value="execution(* com.sgl.aop.annotion1.*.a*(..))") public void pointcut(){ System.out.println("nihao"); }; @Before(value="pointcut()") public void before(JoinPoint joinPoint){ System.out.println("----------前置通知----------"); System.out.println(joinPoint.getSignature().getName()); } @After(value="pointcut()") public void after(){ System.out.println("----------最终通知----------"); } @Around(value="execution(* com.sgl.aop.annotion1.*.s*(..))") public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("----------环绕前置----------"); Object result = joinPoint.proceed(); System.out.println("----------环绕后置----------"); return result; } //返回结果通知 @AfterReturning(pointcut="execution(* com.sgl.aop.annotion1.*.m*(..))",returning="result") public void afterReturning(JoinPoint jp,Object result){ System.out.println(jp.getSignature().getName()); System.out.println("结果是:"+result); System.out.println("----------返回结果----------"); } //异常后通知 @AfterThrowing(pointcut="execution(* com.sgl.aop.annotion1.*.d*(..))",throwing="exp") public void afterThrowing(JoinPoint jp,Exception exp){ System.out.println(jp.getSignature().getName()); System.out.println("异常消息:"+exp.getMessage()); System.out.println("----------异常通知----------"); } }
package com.sgl.aop.annotion1; import org.springframework.stereotype.Component; /** * 被代理的目标类 */ @Component public class Math{ //加 public int add(int n1,int n2){ int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); return result; } //减 public int sub(int n1,int n2){ int result=n1-n2; System.out.println(n1+"-"+n2+"="+result); return result; } //乘 public int mut(int n1,int n2){ int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); return result; } //除 public int div(int n1,int n2){ int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); return result; } }
<?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-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <context:component-scan base-package="com.sgl.aop.annotion1" /> <!-- 开启aop注解扫描 --> <aop:aspectj-autoproxy proxy-target-class="true" /><!-- true表示代理目标类,默认为false,代理接口 --> </beans>
package com.sgl.aop.annotion1; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext3.xml"); Math math = context.getBean("math",Math.class); int n1 = 100, n2 = 5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2); } }
4、自定义配置文件类来代替applicationContext.xml配置文件
package com.sgl.aop.noxml; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * 相当于spring配置文件的类 * @author suguoliang@daoran.tv * @date 2018年4月13日 */ @Configuration/*用于表示当前类为容器的配置类,类似<beans/>*/ @ComponentScan(basePackages={"com.sgl.aop.noxml"})/*扫描的范围,相当于xml的配置<context:component-scan/>*/ @EnableAspectJAutoProxy(proxyTargetClass=true)/*自动代理,相当于<aop:aspectj-autoproxy proxy-target-class="true" />*/ public class ApplicationCfg { /** * 在配置中声明一个bean,相当于<bean id=getUser class="com.sgl.aop.noxml.User"/> * @return */ @Bean public User getUser(){ return new User(); } }
package com.sgl.aop.noxml; public class User { public void show(){ System.out.println("一个用户对象"); } }
package com.sgl.aop.noxml; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationCfg.class); Math math = context.getBean("math",Math.class); int n1 = 100, n2 = 5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2); } }