Spring之AOP入门

原创
2020/03/11 22:54
阅读数 169

Spring AOP入门

1、AOP

1.1、概念

AOP为面向切面编程,采用动态代理实现。

1.2、优点

采用动态代理的方式,可以增强原有的目标类的方法,我们可以在目标方法执行前后分别做一些事情。

对于aop就可以在5种通知里做一些事情,比如说数据库连接的释放,日志的打印,事务的操作。

这种方式,使得不用修改原有程序,就可以增加功能,降低了耦合。

1.3、结构

2、AOP入门案例

2.1、AOP前

AOP采用动态代理实现,故很有必要在看AOP前先看动态代理的实现。动态代理分为两种,对于有接口,有实现类的可以采用jdk动态代理,对于没有接口,只有实现类的,需要采用cglib代理。

2.1.1、依赖包

<dependencies>
    <dependency>
        <artifactId>org.springframework</artifactId>
        <groupId>spring-core</groupId>
        <version>3.2.7.RELEASE</version>
    </dependency>
    <dependency>
        <artifactId>org.springframework</artifactId>
        <groupId>spring-context</groupId>
        <version>3.2.7.RELEASE</version>
    </dependency>
    <dependency>
        <artifactId>org.springframework</artifactId>
        <groupId>spring-beans</groupId>
        <version>3.2.7.RELEASE</version>
    </dependency>
    <dependency>
        <artifactId>org.springframework</artifactId>
        <groupId>spring-expression</groupId>
        <version>3.2.7.RELEASE</version>
    </dependency>
    <dependency>
        <artifactId>org.springframework</artifactId>
        <groupId>spring-aop</groupId>
        <version>3.2.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aopalliance</groupId>
        <artifactId>com.springsource.org.aopalliance</artifactId>
        <version>1.0</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.6.12</version>
    </dependency>
    <dependency>
        <artifactId>commons-logging</artifactId>
        <groupId>commons-logging</groupId>
        <version>1.1</version>
    </dependency>
</dependencies>

2.2、jdk动态代理(AOP手动方式)

jdk动态代理中,代理类需要实现目标类的所有接口。

2.2.1、目标类:接口+实现类

接口:

​public interface UserService {
    public void addUser();
    public void updateUser();
    public void deleteUser();
}​

实现类:

public class UserServiceImpl implements UserService {
    public void addUser() {
        System.out.println("addUser");
    }
    public void updateUser() {
        System.out.println("updateUser");
    }
    public void deleteUser() {
        System.out.println("deleteUser");
    }
}

2.2.2切面类:用于保存通知

public class MyAspect {
    public void before() {
        System.out.println("brfore");
    }
    public void after() {
        System.out.println("after");
    }
}

2.2.3、代理类:生成代理对象

public class MyBeanFactory {
    public static UserService createService() {
        // 1、目标类
        final UserService userService = new UserServiceImpl();
        // 2、切面类
        final MyAspect myAspect = new MyAspect();
        // 3、代理对象
        // 参数1、类加载器
                         当前类.class.getClassLoader()
                        目标类对象.getClass().getClassLoader() 
        // 参数2、目标类所实现的所有接口
                        目标类对象.getClass().getInterfaces() 
                        new Class[]{接口.class}
        // 参数3、InvocationHandler处理类,采用匿名内部类实现
        UserService proxyService = (UserService)Proxy.newProxyInstance(MyBeanFactory.class.getClassLoader(),
        userService.getClass().getInterfaces(), new InvocationHandler() {
            //参数1proxy、代理对象
            //参数2method、要执行方法的描述对象
            //参数3arg2、方法的实际参数
            public Object invoke(Object proxy, Method method, Object[] arg2) throws Throwable {
                myAspect.before();
                Object obj = method.invoke(userService, arg2);
                myAspect.after();
                return obj;
            }
        });
        return proxyService;
    }
}

2.2.4、测试类:

@Test
public void fun() {
    UserService userService = MyBeanFactory.createService();
    userService.addUser();
    userService.updateUser();
    userService.deleteUser();
}

2.3、cglib代理方式

cglib代理适用于没有接口只有实现类的情况。cglib是通过让代理类继承目标类,从而增强目标类的方法。

2.3.1、目标类等同于jdk动态代理的接口的实现类

2.3.2、切面类同上

2.3.3、测试类同上

2.3.4、代理类

public class MyBeanFactory {
    public static UserServiceImpl createService() {
    // 1、目标类
    final UserServiceImpl userService = new UserServiceImpl();
    // 2、切面类
    final MyAspect myAspect = new MyAspect();
    // 3、代理类
    Enhancer enhancer = new Enhancer();
    //设置父类
    enhancer.setSuperclass(userService.getClass());
    /* 设置回调函数 , MethodInterceptor接口 等效 jdk中 InvocationHandler接口
     * intercept() 等效 jdk invoke()
     */
    enhancer.setCallback(new MethodInterceptor() {
        //参数1、代理对象
        //参数2、要执行方法的描述对象(当前)
        //参数3、方法的实际参数
        //参数4、代理类方法的代理
        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
            myAspect.before();
            //通过反射的方式执行要目标类的方法
            Object obj = arg1.invoke(userService, arg2);
            //执行代理类的父类(目标类)
            arg3.invokeSuper(arg0, arg2);
            myAspect.after();
            return obj;
        }
    });
    //创建代理对象,向上转型
    UserServiceImpl proxyService = (UserServiceImpl) enhancer.create();
    return proxyService;
    }
}

2.4、AOP半自动代理

半自动代理采用spring提供的org.springframework.aop.framework.ProxyFactoryBean类(一个代理工厂)生成代理对象

2.4.1、目标类与jdk动态代理相同

2.4.2、切面类:环绕通知(此处与其他不同)

public class MyAspect implements MethodInterceptor 
    public Object invoke(MethodInvocation arg0) throws Throwable {
        System.out.println("before");
        Object obj = arg0.proceed();
        System.out.println("after");
        return obj;
    }
}

2.4.3、配置文件beans.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"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
                                                http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 目标类 -->
    <bean id="userService" class="beanfactory.UserServiceImpl"></bean>
    <!-- 切面类 -->
    <bean id="myAspect" class="beanfactory.MyAspect"></bean>
    <!-- 代理类 -->
    <!-- 使用spring提供的代理工厂 -->
    <bean id="proxyService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 目标类的接口 -->
        <property name="interfaces" value="beanfactory.UserService"></property>
        <!-- 目标类 -->
        <property name="target" ref="userService"></property>
        <!-- 只能放一个切面类 -->
        <!-- 切面类的名称 -->
        <property name="interceptorNames" value="myAspect"></property>
    </bean>
</beans>

2.4.4、测试类

@Test
public void fun() {
    String xmlPath = "beanfactory/beans.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
    // 获得代理对象
    UserService userService = (UserService) applicationContext.getBean("proxyService");
    userService.addUser();
    userService.updateUser();
    userService.deleteUser();
}

2.4.5、结果

before
addUser
after
before
updateUser
after
before
deleteUser
after

2.5、AOP全自动代理

全自动代理仅需要在beans.xml中配置一下就好,使用<aop:config></aop:config>。

spring会自动判断使用那种代理方式,有接口时采用jdk方式,没有接口时采用cglib方式。

2.5.1、目标类与jdk动态代理相同

2.5.2、测试类与半自动方式相同

2.5.3、切面类

AOP提供了5中通知类型,分别为,前置通知,返回通知,异常通知,最终通知,环绕通知,其中环绕通知最为强大。

除环绕通知,其他可以没有参数。

public class MyAspect {
    //前置通知,在目标类方法执行前执行
    public void before(JoinPoint joinPoint) {
        System.out.println("before");
    }
    //返回通知,在目标类方法执行后执行,可以拿到目标类方法的返回值,如果没有就是null
    public void afterreturning(JoinPoint joinPoint, Object ret) {
        System.out.println("afterreturning" + ret);
    }
    //异常通知,当目标类方法抛出异常时执行,可以拿到异常信息,类似于在catch块里面的方法
    public void throwable(JoinPoint joinPoint, Throwable e) {
        System.out.println("throwable" + " " + e.getMessage());
    }
    //在最后执行
    public void afterafter(JoinPoint joinPoint) {
        System.out.println("afterafter");
    }
    //在方法前后都会执行
    public Object invoke(ProceedingJoinPoint jontPoint) throws Throwable {
        System.out.println("环绕通知-------->前");
        Object obj = jontPoint.proceed();
        System.out.println("环绕通知---------->后");
        return obj;
    }
}

2.5.4、配置文件beans.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:aop="http://www.springframework.org/schema/aop"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
                                                http://www.springframework.org/schema/beans/spring-beans.xsd
                                                http://www.springframework.org/schema/aop
                                                http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 目标类 -->
    <bean id="userService" class="aop.UserServiceImpl"></bean>
    <!-- 切面类 -->
    <bean id="myAspect" class="aop.MyAspect"></bean>
    <!-- proxs-target-class="true":强制采用cglib方式代理 -->
    <aop:config proxy-target-class="true">
        <!-- 切入点 -->
        <aop:pointcut expression="execution(* aop.UserService.*(..))" id="myPointCut" />
        <!-- 切面 -->
        <aop:aspect ref="myAspect">
            <!-- 前置通知 -->
            <aop:before method="before" pointcut-ref="myPointCut" />
            <!-- 返回通知 -->
            <aop:after-returning method="afterreturning" returning="ret" pointcut-ref="myPointCut" />
            <!-- 环绕通知 -->
            <aop:around method="invoke" pointcut-ref="myPointCut" />
            <!-- 异常通知 -->
            <aop:after-throwing method="throwable" throwing="e" pointcut-ref="myPointCut" />
            <!-- 最终通知 -->
            <aop:after method="afterafter" pointcut-ref="myPointCut" />
        </aop:aspect>
    </aop:config>
</beans>

2.5.5、结果

因为环绕通知中有return语句,故执行顺序和配置有关。

2.5.5.1、配置环绕通知时

before
环绕通知-------->前
addUser
afterafter
环绕通知---------->后
afterreturning1
------------------------------------
before
环绕通知-------->前
deleteUser
afterafter
环绕通知---------->后
afterreturningnull
------------------------------------
before
环绕通知-------->前
updateUser
afterafter
throwable / by zero

2.5.5.2、不配置环绕通知

before
addUser
afterreturning1
afterafter
------------------------------------
before
deleteUser
afterreturningnull
afterafter
------------------------------------
before
updateUser
throwable / by zero
afterafter

2.5.5.3、其他

异常通知,只有在产生异常后才会执行,此时,返回通知会被异常挡住不在执行。

返回通知可以拿到目标类方法拿到的返回值,如果没有返回值,就是null。

环绕通知会使得最终通知提前。类似于,return执行前,finally块中的会先执行。

2.5.6、补充

全自动方式也可将切面做成类似于半自动的形式。

切面类:

前置通知:

public class MyAspectBefore implements MethodBeforeAdvice {
    public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { 
        System.out.println("before"); 
    }
}

环绕通知:

public class MyAspectMethod implements MethodInterceptor {
    public Object invoke(MethodInvocation arg0) throws Throwable {
        System.out.println("环绕前");
        Object obj = arg0.proceed();
        System.out.println("环绕后");
        return obj;
    }
}

配置方式:

<aop:config proxy-target-class="true">
    <!-- 切入点 -->
    <aop:pointcut expression="execution(* aop2.UserService.*(..))" id="myPointCut" />
    <!-- 特殊的切面,只有一个环绕通知 -->
    <aop:advisor advice-ref="myAspectMethod" pointcut-ref="myPointCut" />
    <aop:advisor advice-ref="myAspectBefore" pointcut-ref="myPointCut" />
</aop:config>

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部