天天看点

【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

文章目录

  • 03 AOP面向切面编程
    • 3.1 AOP概述
    • 3.2 面向切面编程对有什么好处
    • 3.3 模拟AOP框架实现
      • 3.3.1 代码实现版本一
      • 3.3.2 代码实现版本二
      • 3.3.3 代码实现版本三
      • 3.3.4 代码实现版本四
      • 3.3.5 代码实现版本五
    • 3.4 Spring的AOP通知类型(了解)
    • 3.5 AOP编程术语(掌握)
      • (1)切面(Aspect)
      • (2)连接点(JoinPoint)
      • (3)切入点(Pointcut)
      • (4)目标对象(Target)
      • (5)通知(Advice)
    • 3.6AspectJ对AOP的实现(掌握)
      • 3.6.1AspectJ 简介
      • 3.6.2 AspectJ 的通知类型(理解)
      • 3.6.3AspectJ的切入点表达式(掌握)
      • 3.6.4AspectJ的开发环境(掌握)
        • (1)添加maven依赖
        • (2)引入AOP约束
      • 3.6.5AspectJ基于注解的AOP实现(掌握)
        • (1)@Before前置通知实现
        • (2) @Before前置通知-方法有JoinPoint参数
        • (3) @AfterReturning后置通知-注解有returning属性
        • (4) @Around环绕通知增强方法有ProceedingJoinPoint
        • (5) @After 最终通知
        • (6) @Pointcut 定义切入点别名
      • 3.6.6 SpringAOP与AspectJ的区别
      • 3.6.7AspectJ框架切换JDK动态代理和CGLib动态代理

03 AOP面向切面编程

3.1 AOP概述

  • AOP(Aspect Orient Programming),意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。
  • AOP 是 Spring 框架中的一个重要内容。
  • 面向切面编程是从动态角度考虑程序运行过程。
  • AOP 底层,就是采用动态代理模式实现的。
  • 采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理。
  • 利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

何为面向切面编程?

  • 面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。
  • 所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。
  • 若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。
  • 例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑—转账。

3.2 面向切面编程对有什么好处

1.减少重复;

2.专注业务;

注意:面向切面编程只是面向对象编程的一种补充。用 AOP 减少重复代码,专注业务实现。

【SSM】Spring系列——AOP面向切面编程03 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();  
    } 
}
           
【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

这个解决方案很好的解决了业务和切面的紧耦合。可以灵活的进行业务的切换,可以灵活的进行切面的切换。可以嵌套切面的处理。

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("王筝","《盗墓笔记》一点都不恐怖,很好看!");	
    }
}
           
【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

Spring的AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,

简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

总结:AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。

3.5 AOP编程术语(掌握)

(1)切面(Aspect)

  • 切面泛指交叉业务逻辑,或是公共的,通用的业务。
  • 上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。
  • 实际就是对主业务逻辑的一种增强。

(2)连接点(JoinPoint)

  • 连接点指可以被切面织入的具体方法。
  • 通常业务接口中的方法均为连接点。

(3)切入点(Pointcut)

  • 切入点指声明的一个或多个连接点的集合。
  • 通过切入点指定一组方法。
  • 被标记为 final 的方法是不能作为连接点与切入点的。
  • 因为最终的是不能被修改的,不能被增强的。

(4)目标对象(Target)

  • 目标对象指 将要被增强 的对象。
  • 即包含主业务逻辑的 类的对象。
  • 上例中 的BookServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。
  • 当然, 不被增强,也就无所谓目标不目标了。

(5)通知(Advice)

  • 通知表示切面的执行时间,Advice 也叫增强。
  • 上例中的 MyInvocationHandler 就可以理解为是一种通知。
  • 换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。
  • 通知类型不同,切入时间不同。
  • 切入点定义切入的位置,通知定义切入的时机。

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 的开源项目,官网介绍如下:

【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

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 定义了专门的表达式用于指定切入点。表达式的原型是:

【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

解释:

modifiers-pattern 访问权限类型

ret-type-pattern 返回值类型

declaring-type-pattern 包名类名

name-pattern(param-pattern) 方法名(参数类型和参数个数)

throws-pattern 抛出异常类型

?表示可选的部分

以上表达式共 4 个部分可简化如下:

execution(访问权限方法返回值 方法声明(参数)异常类型)

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

举例:

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:定义业务接口与实现类

【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

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:声明目标对象切面类对象

【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

Step4:注册 AspectJ 的自动代理

  • 在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理对象。
  • 这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型与切入点,将其织入,并生成代理。
【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程
  • < 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);
}
           
【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

(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()));   
    }
}
           
【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

(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); 
}
           
【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

可以改变目标方法的返回值(目标方法的返回值是引用类型)

@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); 
}
           
【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

(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();  
    }
}
           
【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

定义访问限制和修改返回值:

@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); 
}
           
【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

(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 
@Component
public 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); 
}
           

运行结果:

【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

(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(){} 
}
运行结果:
           
【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

3.6.6 SpringAOP与AspectJ的区别

【SSM】Spring系列——AOP面向切面编程03 AOP面向切面编程

3.6.7AspectJ框架切换JDK动态代理和CGLib动态代理

​ <aop:aspectj-autoproxy >< /aop:aspectj-autoproxy >

​ ===>默认是JDK动态代理,取时必须使用接口类型

​ <aop:aspectj-autoproxy proxy-target-class=“true”>< /aop:aspectj-autoproxy >

​ ==>设置为CGLib子类代理,可以使用接口和实现类接

​ 记住:使用接口来接,永远不出错.