03 AOP 面向切面编程
3.1 AOP 概述
AOP(Aspect Orient Programming),意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。
AOP 是 Spring 框架中的一个重要内容。
面向切面编程是从动态角度考虑程序运行过程。
AOP 底层,就是采用动态代理模式实现的。
采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理。
利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
何为面向切面编程?
面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。
所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。
若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。
例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑---转账。
3.2 面向切面编程对有什么好处
1.减少重复;
2.专注业务;
注意:面向切面编程只是面向对象编程的一种补充。用 AOP 减少重复代码,专注业务实现。
3.3 模拟 AOP 框架实现
主要的目的是进行业务逻辑与切面的解耦合。完全分离业务逻辑和切面。
分为五个版本:
1)版本一:业务和切面紧耦合在一起。
2)版本二:子类代理实现切面功能增强。
3)版本三:使用静态代理分离业务。
4)版本四:使用静态代理分离业务和切面。
5)版本五:使用动态代理优化业务和切面的解耦合。
3.3.1 代码实现版本一
业务和切面紧耦合在一起。
public class BookServiceImpl { public void buy(){ try { //切面部分:事务处理 System.out.println("事务开启......"); //主业务实现 System.out.println("图书购买业务的实现............"); //切面部分:事务处理 System.out.println("事务提交......"); } catch (Exception e) { //切面部分:事务处理 System.out.println("事务回滚.........."); } } }
复制代码
3.3.2 代码实现版本二
子类代理实现切面功能增强
public class BookServiceImpl { public void buy(){ //实现主业务功能 System.out.println("图书购买业务的实现..........."); }}public class SubBookServiceImpl extends BookServiceImpl { //重写父类的业务方法,增强切面(事务)的功能 @Override public void buy() { try { //切面部分: System.out.println("事务开启................"); super.buy(); //切面部分: System.out.println("事务提交................"); } catch (Exception e) { //切面部分: System.out.println("事务回滚................"); } } }
复制代码
3.3.3 代码实现版本三
使用静态代理分离业务
public interface Service { public void buy(); }
public class BookServiceImpl implements Service { @Override public void buy() { //只要完成主业务功能就行 System.out.println("图书购买业务实现..............."); }}
public class ProductServiceImpl implements Service{ @Override public void buy() { System.out.println("商品购买业务实现.............."); }}
public class Agent implements Service { //上接口上灵活,目标对象灵活切换 public Service target; //使用构造方法传入目标对象 public Agent(Service target){ this.target = target; } @Override public void buy() { try { //切面功能实现 System.out.println("事务开启..........."); //业务功能实现 target.buy(); //切面功能实现 System.out.println("事务提交"); } catch (Exception e) { System.out.println("事务回滚............."); } }}
public class MyTest03 { @Test public void test03(){ Service service = new Agent(new ProductServiceImpl()); service.buy(); } }
复制代码
3.3.4 代码实现版本四
使用静态代理分离业务和切面
public interface AOP { default void before(){} default void after(){} default void exception(){} }public class TransAop implements AOP { @Override public void before() { System.out.println("事务开启............"); } @Override public void after() { System.out.println("事务提交..........."); } @Override public void exception() { System.out.println("事务回滚..........."); }}public class LogAop implements AOP { @Override public void before() { System.out.println("前置日志输出 ............."); } }
public interface Service { public void buy(); }public class BookServiceImpl implements Service { @Override public void buy() { //只要完成主业务功能就行 System.out.println("图书购买业务实现..............."); }}public class ProductServiceImpl implements Service{ @Override public void buy() { System.out.println("商品购买业务实现.............."); }}
public class Agent implements Service{ public Service target; //为了灵活的切换业务,上接口 public AOP aop; //为了灵活的切换切面,上接口 public Agent(Service target,AOP aop){ this.target = target; this.aop = aop; } @Override public void buy() { try { //切面功能 aop.before(); //哪个实现类来了,调用哪个实现类的功能 //业务功能 target.buy(); //切面功能 aop.after(); } catch (Exception e) { aop.exception(); } } }
public class MyTest04 { @Test public void test03(){ Service agent = new Agent(new ProductServiceImpl(),new LogAop()); agent.buy(); }}
复制代码
3.3.5 代码实现版本五
完全的解耦了业务与服务性质的业务(切面),切换功能和方面更灵活。
但是只能是 buy()一个功能,如果再代理的功能多了,就不行了,解决方案是动态代理模式。
public class ProxyFactory { //通过方法参数传入目标对象和切面对象 public static Object getAgent(Service target,AOP aop){ return Proxy.newProxyInstance( //类加载 target.getClass().getClassLoader(), //目标对象实现的所有接口 target.getClass().getInterfaces(), //代理功能实现 new InvocationHandler() { @Override public Object invoke( //生成的代理对象 Object proxy, //正在被调用的目标方法bug(),show() Method method, //目标方法的参数 Object[] args) throws Throwable { Object obj=null; try { aop.before(); //灵活的进行切面功能切换 obj = method.invoke(target,args); //灵活的进行业务功能切换 aop.after(); //灵活的进行切面功能切换 } catch (Exception e) { aop.exception(); //灵活的进行切面功能切换 } return obj;//目标方法的返回值 } }); //返回动态代理对象 }}public class MyTest05 { @Test public void test03(){ //得到代理对象 Service agent = (Service) ProxyFactory.getAgent(new ProductServiceImpl(),new TransAop()); Service agent1 = (Service) ProxyFactory.getAgent(agent,new LogAop()); agent1.buy(); } }
复制代码
这个解决方案很好的解决了业务和切面的紧耦合。可以灵活的进行业务的切换,可以灵活的进行切面的切换。可以嵌套切面的处理。
3.4 Spring 的 AOP 通知类型(了解)
Spring 支持 AOP 的编程,常用的有以下几种:
1)Before 通知:在目标方法被调用前调用,涉及接口 org.springframework.aop.MethodBeforeAdvice;2)After 通知:在目标方法被调用后调用,涉及接口为 org.springframework.aop.AfterReturningAdvice;3)Throws 通知:目标方法抛出异常时调用,涉及接口 org.springframework.aop.ThrowsAdvice;4)Around 通知:拦截对目标对象方法调用,涉及接口为 org.aopalliance.intercept.MethodInterceptor。
案例:
LogAdvice.java public class LogAdvice implements MethodBeforeAdvice { private static SimpleDateFormat sf = new SimpleDateFormat("yyyy年MM月dd日"); @Override public void before(Method m, Object[] args, Object arg2) throws Throwable { System.out.println("\n[系统日志]["+sf.format(new Date()) +"]"+m.getName() +"("+Arrays.toString(args) +")"); } }
复制代码
BookService .java
public interface BookService { public boolean buy(String userName,String bookName,double price); public void comment(String userName,String comments);}
复制代码
BookServiceImpl .java
public class BookServiceImpl implements BookService { /* 购买图书 */ @Override public boolean buy(String userName, String bookName, double price) { System.out.println("业务buy开始执行"); System.out.println(userName+"购买了图书"+bookName); System.out.println(userName+"增加积分"+(int)(price/10)); System.out.println("图书购买完毕,向物流下单...."); System.out.println("业务buy结束"); return true; } /* 发表评论 */ @Override public void comment(String userName, String comments) { System.out.println("业务comment开始执行"); System.out.println(userName+"发表图书评论"+comments); System.out.println("业务comment执行结束"); }}
复制代码
applicationContext.xml
<!-- 实现业务功能的实现类 --><bean id="bookServiceTarget"class="com.oracle.aop.biz.impl.BookServiceImpl"></bean><!-- 日志功能 --><bean id="logAdvice" class="com.oracle.aop.LogAdvice"></bean><bean id="bookService" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 将要绑定的业务接口 --> <property name="proxyInterfaces"> <value>com.oracle.aop.biz.BookService</value> </property> <!-- 实现日志功能的切面 --> <property name="interceptorNames"> <list> <value>logAdvice</value> </list> </property> <!-- 织入 --> <property name="target" ref="bookServiceTarget"></property></bean>
复制代码
TestAOP.java
public class TestAOP { @Test public void testAop(){ ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml"); BookService bookservice=(BookService)context.getBean("bookService"); bookservice.buy("高志水", "CMMi实务手册", 50); bookservice.comment("王筝","《盗墓笔记》一点都不恐怖,很好看!"); }}
复制代码
Spring 的 AOP 利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,
简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
AOP 代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
总结:AOP 的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。
3.5 AOP 编程术语(掌握)
(1)切面(Aspect)
(2)连接点(JoinPoint)
连接点指可以被切面织入的具体方法。
通常业务接口中的方法均为连接点。
(3)切入点(Pointcut)
(4)目标对象(Target)
(5)通知(Advice)
3.6AspectJ 对 AOP 的实现(掌握)
对于 AOP 这种编程思想,很多框架都进行了实现。
Spring 就是其中之一,可以完成面向切面编程。
然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便, 而且还支持注解式开发。
所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。
在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。
3.6.1AspectJ 简介
AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。
官网地址:http://www.eclipse.org/aspectj/
AspetJ 是 Eclipse 的开源项目,官网介绍如下:
a seamless aspect-oriented extension to the Javatm programming language(一种基于 Java 平台的面向切面编程的语言)
Java platform compatible(兼容 Java 平台,可以无缝扩展)
easy to learn and use(易学易用)
3.6.2 AspectJ 的通知类型(理解)
AspectJ 中常用的通知有四种类型:
(1)前置通知 @Before
(2)后置通知 @AfterReturning
(3)环绕通知 @Around
(4)最终通知 @After
(5)定义切入点 @Pointcut(了解)
3.6.3AspectJ 的切入点表达式(掌握)
AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
解释:
modifiers-pattern 访问权限类型
ret-type-pattern 返回值类型
declaring-type-pattern 包名类名
name-pattern(param-pattern) 方法名(参数类型和参数个数)
throws-pattern 抛出异常类型
?表示可选的部分
以上表达式共 4 个部分可简化如下:
execution(访问权限方法返回值 方法声明(参数)异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
举例:
execution(public (..))
指定切入点为:任意公共方法。
execution(* set*(..))
指定切入点为:任何一个以“set”开始的方法。
execution( com.xyz.service.impl..(..))
指定切入点为:定义在 service 包里的任意类的任意方法。
execution(com.xyz.service...(..)) * com.xyz.service.power2.aa..(..)
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
execution(* ..service..(..)) a.b.service..(..) a.b.c.d.service..*(..)
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
execution(* .service..*(..))
指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点
execution(* .ISomeService.(..))
指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点
execution(* ..ISomeService.(..))
指定所有包下的 ISomeSerivce 接口中所有方法为切入点
execution(* com.xyz.service.IAccountService.*(..))
指定切入点为:IAccountService 接口中的任意方法。
execution(* com.xyz.service.IAccountService+.*(..))
指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
execution(* joke(String,int)))
指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。
execution(* joke(String,*)))
指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如 joke(String s1,String s2)和 joke(String s1,double d2)都是,但 joke(String s1,double d2,String
s3)不是。
execution(* joke(String,..)))
指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3) 都是。
execution(* joke(Object))
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。
execution(* joke(Object+)))
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。
3.6.4AspectJ 的开发环境(掌握)
(1)添加 maven 依赖
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope></dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version></dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version></dependency>
复制代码
(2)引入 AOP 约束
在 AspectJ 实现 AOP 时,要引入 AOP 的约束。配置文件中使用的 AOP 约束中的标签, 均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的。
AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。
3.6.5AspectJ 基于注解的 AOP 实现(掌握)
AspectJ 提供了以注解方式对于 AOP 的实现。
(1)@Before 前置通知实现
Step1:定义业务接口与实现类
Step2:定义切面类
类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。
@Aspect //交给AspectJ框架去识别切面类,来进行切面方法的调用public class MyAspect { /*前置通知中的切面方法的规范 1)访问权限是public 2)没有返回值void 3)切面方法的名称自定义 4)切面方法可以没有参数,如果有也是固定的类型JoinPoint 5)使用@Before注解表明是前切功能 6)@Before的参数: value:*指定切入点表达式 public String doSome(String name, int age) */ @Before(value = "execution( * com.bjpowernode.s01.*.*(..))") public void myBefore(){ System.out.println("前置功能输出..............."); } 切入点表达式其它形式: @Aspect public class MyAspect { @Before(value = "execution(public void com.bjpowernode.s01.SomeServiceImpl.doSome(String,int))") @Before(value = "execution(* com.bjpowernode.s01.SomeServiceImpl.*(String,int))") @Before(value = "execution(* *...s01.SomeServiceImpl.*(..))") @Before(value = "execution(* *.*(..))") public void myAspect() { System.out.println("我是前置日志处理........."); } }
复制代码
Step3:声明目标对象切面类对象
Step4:注册 AspectJ 的自动代理
< aop:aspectj-autoproxy/ >的底层是由 AnnotationAwareAspectJ AutoProxyCreator 实现的。
从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。
其工作原理是,aop:aspectj-autoproxy/通过扫描找到 @Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
Step5:测试类中使用目标对象的 id
@Test public void test01(){ ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml"); SomeService someService = (SomeService) ac.getBean("someService"); System.out.println(someService.getClass()); String s = someService.doSome("张三",22); System.out.println(s);}
复制代码
(2) @Before 前置通知-方法有 JoinPoint 参数
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。
该类型的对象本身就是切入点表达式。
通过 JoinPoint 类型参数,可获取切入点表达式、方法签名、目标对象等。
不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。
@Aspect //交给AspectJ框架去识别切面类,来进行切面方法的调用public class MyAspect { @Before(value = "execution( * com.bjpowernode.s01.*.*(..))") public void myBefore(JoinPoint joinPoint){ System.out.println("前置功能输出..............."); System.out.println("目标方法的签名:"+joinPoint.getSignature()); System.out.println("目标方法的所有参数:"+ Arrays.toString(joinPoint.getArgs())); }}
复制代码
(3) @AfterReturning 后置通知-注解有 returning 属性
在目标方法执行之后执行。所以可以获取到目标方法的返回值。
该注解的 returning 属性就是用于指定接收方法返回值的变量名的。
被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。
该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
接口方法:
public interface SomeService { String doSome(String name, int age); Student change();}
复制代码
实现方法:
@Component public class SomeServiceImpl implements SomeService { @Override public String doSome(String name, int age) { System.**out**.println(name+"doSome方法被调用 (主业务方法)"); return "abcd"; } @Override public Student change() { return new Student("张三"); } }
复制代码
定义切面:
@Aspect //交给AspectJ框架扫描识别切面类@Component public class MyAspect { /* 后置通知切面方法的规范 1)访问权限是 public 2)切面方法没有返回值 void 3)方法自定义 4)切面方法可以没有参数,如果有参数则是目标方法的返回值,也可以包含参数JoinPoint,它必须是第一个参数 5)使用@AfterReturning注解 6)参数value:指定切入点表达式 returning:指定目标方法返回值的形参名称,此名称必须与切面方法的参数名称一致 */ @AfterReturning(value = "execution(* com.bjpowernode.s02.SomeServiceImpl.*(..))",returning = "obj") public void myAfterReturning(Object obj){ System.out.println("后置通知.........."); //改变目标方法的返回值 if(obj != null){ if(obj instanceof String){ String s = ((String) obj).toUpperCase(); //转为大写 System.out.println("在切面方法中的输出:"+s); } if(obj instanceof Student){ ((Student) obj).setName("李四"); System.out.println("在切面方法中的输出"+(Student)obj); } } } }
复制代码
测试类:
@Test public void test01(){ ApplicationContext ac = new ClassPathXmlApplicationContext("s02/applicationContext.xml"); SomeServiceImpl someService = (SomeServiceImpl) ac.getBean("someServiceImpl"); System.out.println(someService.getClass()); String s = someService.doSome("张三",22); System.out.println("在测试类中输出目标方法的返回值---"+s); }
复制代码
可以改变目标方法的返回值(目标方法的返回值是引用类型)
@Test public void test03(){ ApplicationContext ac = new ClassPathXmlApplicationContext("s02/applicationContext.xml"); SomeServiceImpl someService = (SomeServiceImpl) ac.getBean("someServiceImpl"); System.out.println(someService.getClass()); Student stu = someService.change(); System.out.println("在测试类中输出目标方法的返回值---"+stu); }
复制代码
(4) @Around 环绕通知增强方法有 ProceedingJoinPoint
在目标方法执行之前之后执行。
被注解为环绕增强的方法要有返回值,Object 类型。
并且方法可以包含一个 ProceedingJoinPoint 类型的参数。
接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。
若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。
该增强方法实际是拦截了目标方法的执行。
接口方法:
public interface SomeService { String doSome(String name, int age); }
复制代码
接口方法的实现:
@Component public class SomeServiceImpl implements SomeService { @Override public String doSome(String name, int age) { System.out.println(name+"doSome方法被调用 (主业务方法)"); return "abcd"; }}
复制代码
定义切面:
@Aspect @Component public class MyAspect { //环绕通知方法的规范 /* 1)访问权限是 public 2)切面方法有返回值,此返回值就是目标方法的返回值. 3)切面方法的名称自定义 4)切面方法有参数,参数就是目标方法.它是ProceedingJoinPoint的类型 5)必须要回避异常Throwable 6)使用@Around注解 7)参数:value:指定切入点表达式 */ //普通的环绕通知实现 @Around(value = "execution(* com.bjpowernode.s03.SomeServiceImpl.*(..))") public Object myAround(ProceedingJoinPoint pjp)throws Throwable{ //前切功能增强 System.**out**.println("环绕通知中前切功能 ............."); //调用目标方法 Object obj = pjp.proceed(); //method.invoke(); //后切功能增强 System.**out**.println("环绕通知中后切功能 ............."); return obj.toString().toUpperCase(); }}
复制代码
定义访问限制和修改返回值:
@Around(value = "execution(* com.bjpowernode.s03.SomeServiceImpl.*(..))") public Object myAround(ProceedingJoinPoint pjp)throws Throwable{ //取出目标方法的参数,进行判断,来决定是否调用目标方法以及增强功能 Object []args = pjp.getArgs(); if(args != null && args.length >1){ String name = (String) args[0]; if("张三".equals(name)){ System.out.println("前置通知实现........"); Object obj = pjp.proceed(); System.out.println("后置通知实现........"); return obj.toString().toUpperCase(); } } System.out.println("目标方法拒绝访问 !"); return null; }
测试类:@Test public void test01(){ ApplicationContext ac = new ClassPathXmlApplicationContext("s03/applicationContext.xml"); SomeServiceImpl someService = (SomeServiceImpl) ac.getBean("someServiceImpl"); System.out.println(someService.getClass()); String s = someService.doSome("张三1",22); System.out.println("在测试类中输出目标方法的返回值---"+s); }
复制代码
(5) @After 最终通知
无论目标方法是否抛出异常,该增强均会被执行。
接口方法:
public interface SomeService { String doSome(String name, int age);}
复制代码
方法实现:
@Component public class SomeServiceImpl implements SomeService { @Override public String doSome(String name, int age) { System.out.println(name+"doSome方法被调用 (主业务方法)"); System.out.println(1/0); return "abcd"; }}
复制代码
定义切面:
@Aspect @Componentpublic class MyAspect { /* 最终方法的规范 1)访问权限是public 2)切面方法没有返回值void 3)方法名称自定义 4)方法可以没有参数,也可以有,则JoinPoint. 5)使用@After注解 6)参数:value:指定切入点表达式 */ @After(value = "execution(* com.bjpowernode.s04.SomeServiceImpl.*(..))") public void myAfter(){ System.out.println("最终通知被执行............."); }}测试类:@Test public void test01(){ ApplicationContext ac = new ClassPathXmlApplicationContext("s04/applicationContext.xml"); SomeServiceImpl someService = (SomeServiceImpl) ac.getBean("someServiceImpl"); System.out.println(someService.getClass()); String s = someService.doSome("张三",22); System.out.println("在测试类中输出目标方法的返回值---"+s); }
复制代码
运行结果:
(6) @Pointcut 定义切入点别名
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。
AspectJ 提供了 @Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将 @Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。
代表的就是 @Pointcut 定义的切入点。这个使用 @Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。
@Aspect @Component public class MyAspect { /*最终方法的规范 1)访问权限是public 2)切面方法没有返回值void 3)方法名称自定义 4)方法可以没有参数,也可以有,则JoinPoint. 5)使用@After注解 6)参数:value:指定切入点表达式 */ @After(value = "mycut()") public void myAfter(){ System.**out**.println("最终通知被执行............."); } @Before(value = "mycut()") public void myBefore(){ System.**out**.println("前置通知被执行............."); } @AfterReturning(value = "mycut()",returning = "obj") public void myAfterReturning(Object obj){ System.**out**.println("后置通知被执行............."); } //给切入点表达式起别名 @Pointcut(value = "execution(* com.bjpowernode.s04.SomeServiceImpl.*(..))") public void mycut(){} }运行结果:
复制代码
3.6.6 SpringAOP 与 AspectJ 的区别
3.6.7AspectJ 框架切换 JDK 动态代理和 CGLib 动态代理
<aop:aspectj-autoproxy >< /aop:aspectj-autoproxy > ===>默认是 JDK 动态代理,取时必须使用接口类型
<aop:aspectj-autoproxy proxy-target-class="true">< /aop:aspectj-autoproxy > ==>设置为 CGLib 子类代理,可以使用接口和实现类接
记住:使用接口来接,永远不出错.
评论