切面编程(二)

一、动态织入切面代码

  AOP的核心在于“业务代码”与“切面代码”的分离,这样设计的好处是

  (1)切面代码写一次即可

  (2)开发者只需关注业务代码的实现,无需重复编写功能重复的切面代码

  (3)运行时,执行业务代码时候动态织入切面代码

  如何实现分离,在本节和下节(切面编程(三))中将详细介绍几种方式

二、代理工厂实现AOP

  spring+jdk动态代理实现AOP详细样例实现

   步骤1.编写切面代码,并将切面加入IOC容器

package com.jyk.spring.aop;

import org.springframework.stereotype.Component;

@Component //加入IOC容器
public class UserAop {
    
    public void begin()
    {
        System.out.println("开启事务");
    }
    
    public void commit()
    {
        System.out.println("结束事务");
    }
}

  步骤2.编写业务接口和实现类,并将实现类加入IOC容器

package com.jyk.spring.aop;

public interface UserInterface {

    public void add();
    
    public void delete();
}
package com.jyk.spring.aop;

import javax.annotation.Resource;
import org.springframework.stereotype.Component;

@Component
public class UserImpl implements UserInterface{

    @Resource
    private UserAop ua;
    
    @Override
    public void add() {
        System.out.println("核心业务1");
    }

    @Override
    public void delete() {
        System.out.println("核心业务2");
    }
}

  步骤3.编写代理工厂

package com.jyk.spring.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.springframework.stereotype.Component;

import com.jyk.spring.proxy.ProxyFactory;

public class UserProxyFactory {

    static Object target;
    static UserAop aop;
    
    //生成代理对象的方法
    public static Object getProxyInstance(Object mTarget,UserAop mUserAop)
    {
        
        target = mTarget;
        aop = mUserAop;
        
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
                        aop.begin();
                        Object returnObject = method.invoke(target, args);
                        aop.commit();
                        return returnObject;
                    }
                });
    }
}

  步骤4.在spring配置文件中开启bean扫描,将配置加入IOC的容器的bean加入到容器中管理,同时将代理对象配置成bean。

     <!-- 开启注解扫描 -->            
        <context:component-scan base-package="com.jyk.spring.aop"></context:component-scan>
                
        <!-- 调用工厂方法,返回UserDao代理后的对象 -->
        <bean id="userImplProxy" class="com.jyk.spring.aop.UserProxyFactory" factory-method="getProxyInstance">
            <constructor-arg index="0" ref="userImpl"></constructor-arg>
            <constructor-arg index="1" ref="userAop"></constructor-arg>
        </bean>

  步骤5.启动测试,截图可见动态代理方式有效的对业务方法实现了拦截

三、注解实现AOP

    spring提供了丰富的注解实现aop的功能,通过配置注解,即可实现切面及切入点,一定程度上节省了开发的工作量,提高了效率。

  步骤1.引入aop相关jar包,核心jar包主要有spring-aop-x.x.x.RELEASE.jar,aopalliance.jar,aspectjweaver.jar和aspectjrt.jar,也可直接引入spring的核心jar包。

  步骤2.在spring的配置文件中引入aop名称空间,并对业务实现类所在的包开启切面扫描注解。   

<?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:aop="http://www.springframework.org/schema/aop"
    xmlns:p="http://www.springframework.org/schema/p"
    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/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        ">
                
        <!-- 开启aop注解方式 -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
        
        <!-- 开启注解扫描 -->            
        <context:component-scan base-package="com.jyk.spring.aop1"></context:component-scan>

</beans>

  步骤3.相关注解解释及使用

  @Aspect 指定一个类为切面类
  @Pointcut("execution(* com.jyk.spring.aop1.*.*(..))") 指定切入点表达式

  @Before("pointCut_()") 前置通知: 目标方法之前执行
  @After("pointCut_()") 后置通知:目标方法之后执行(始终执行)
  @AfterReturning("pointCut_()") 返回后通知: 执行方法结束前执行(异常不执行)
  @AfterThrowing("pointCut_()") 异常通知: 出现异常时候执行
  @Around("pointCut_()") 环绕通知: 环绕目标方法执行

  步骤4.编写切面类并编写切入点 package com.jyk.spring.aop1;

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 UserAop {
    
    //指定切入点表达式,即拦截哪些方法
  //com.jyk.spring.aop1.*.*(..))即表示拦截该包下所有类的所有方法
@Pointcut("execution(* com.jyk.spring.aop1.*.*(..))") public void testPT() { } //前置通知,执行目标方法前执行 @Before("testPT()") public void begin() { System.out.println("开启事务"); } //后置通知,执行目标方法后执行 @After("testPT()") public void commit() { System.out.println("结束事务"); } //返回后通知,目标方法调用结束后执行,出现异常不执行 @AfterReturning("testPT()") public void afterRunning() { System.out.println("目标方法调用结束后执行"); } //异常通知,目标方法调用发生异常时执行 @AfterThrowing("testPT()") public void throwException() { System.out.println("执行目标方法出现了异常"); } //环绕通知,环绕目标方法执行 @Around("testPT()") public void arround(ProceedingJoinPoint pj)throws Throwable { System.out.println("环绕前"); //执行目标方法 pj.proceed(); System.out.println("环绕后"); } }

  步骤5.编写业务接口和具体实现,并将实现类加入到spring的IOC容器

package com.jyk.spring.aop1;

public interface UserInterface {

    public void add();
    
    public void delete();
}
package com.jyk.spring.aop1;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;

@Component
public class UserImpl implements UserInterface{
    
    @Override
    public void add() {
        System.out.println("核心业务1");
    }

    @Override
    public void delete() {
        System.out.println("核心业务2");
    }
}

  步骤6.验证切入点执行顺序,和执行结果

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    
    //目标对象有实现接口,Spring会默认使用JDK代理
    @Test
    public void testJDK()
    {
        UserInterface ui = (UserInterface) ac.getBean("userImpl");
        System.out.println(ui.getClass());
        ui.add();
        ui.delete();
    }

  由执行结果可知,Around前拦截先于Before前拦截,Around后拦截先于After后拦截,AfterReturning先于Around后拦截,慢于After后拦截。

  其中,AfterThrowing只有在目标对象的目标方法出现异常时候才执行。

  注:目标对象有实现接口,Spring会默认使用JDK代理,目标对象没有实现接口,Spring会默认使用cglib代理。

posted @ 2018-07-29 16:02  纪煜楷  阅读(218)  评论(0编辑  收藏  举报