Java

Spring泛览二(AOP)

2019-02-22  本文已影响0人  强某某

AOP

面向切面编程,通过预编译和运行期动态代理,实现程序功能,是函数式编程的一个衍生范型

AOP实现方式


  1. 动态代理Proxy:接口+实现类
  2. cglib字节码: 实现类 (通过创建目标类的子类来实现功能)

JDK动态代理

该方式必须有接口和实现类,原因是对应回调传参的参数是接口类型

/**
 * 切面类:增加代码 与 切入点 结合
 */
public class MyAspect2 {

    public void before(){
        System.out.println("开启事务...");
    }

    public void after(){
        System.out.println("提交事务...");
    }
}
/**
* 接口
*/
public interface IUserService {

    public void add();

    public void add(User user);

    //切面编程
    public void addUser();
    public void updateUser();
    public void deleteUser();
    public int deleteUser(int id);
}
/**
* 接口实现类
*/
@Service("myUserService")
public class UserServiceImpl implements IUserService {

    @Autowired //spring会自动注入userDao赋值
    private IUserDao userDao;

    @Override
    public void add(User user) {
        System.out.println("service 添加用户:" + user);
        //调用dao
        userDao.add(user);
    }

    @Override
    public void addUser() {
        System.out.println("添加用户。。。。");
    }

    @Override
    public void updateUser() {
        System.out.println("更新用户。。。。");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户。。。。");
    }
    @Override
    public int deleteUser(int id) {
        System.out.println("通过id删除用户");
        return 1;
    }
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public void add() {
        System.out.println("创建用户...." + name);
    }
    public UserServiceImpl() {
        //System.out.println("UserServiceImpl()调用了");
    }
}
/**
* 工厂类
*/
public class MyBeanFactory {

    /**
     * JDK实现代理
     * @return
     */
    public static IUserService createUserService(){
        //1.创建目标对象target
        final IUserService userService = new UserServiceImpl();

        //2.声明切面类对象
        final MyAspect2 aspect = new MyAspect2();

        //3.把切面类2个方法 应用 目标类
        //3.1 创建JDK代理,拦截方法
        /*newProxyInstance(
                ClassLoader loader, 类加载器,写当前类(记住即可)
                Class<?>[] interfaces, 接口,接口的方法会被拦截
                InvocationHandler h) 处理
                */
        IUserService seriviceProxy = (IUserService) Proxy.newProxyInstance(
                MyBeanFactory.class.getClassLoader(),
                //因为此处必须传接口,所以原生jdk动态代理必有有接口
                userService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //开启事务
                        aspect.before();
                        //方法返回值是 业务方法的返回值
                        Object retObj = method.invoke(userService,args);
                        //System.out.println("拦截返回值:" + retObj);
                        //提交事务
                        aspect.after();
                        return retObj;
                    }
                }
        );
        return seriviceProxy;
    }
}

/**
* 测试方法
*/
@Test
    public void test1() throws Exception {

        //实现AOP编程,使用cglib代理来实现

        StudentService ss = MyBeanFactory.createStudentService();

        ss.delete();
       /* ss.update();
        ss.add();*/

    }

cglib字节码实现AOP

只需要实现类(当然有接口和实现类也可)即可,当然切面对象也不能少即(MyAspect2.java)

public class StudentService {
    public void delete(){
        System.out.println("删除学生");

    }
    public void add(){
        System.out.println("add学生");
    }
    public void update(){
        System.out.println("update学生");
    }
}
public class MyBeanFactory {
    /**
     * cglib实现代理
     * @return
     */
    public static StudentService createStudentService(){
        //1.创建目标对象target
        final StudentService studentService = new StudentService();

        //2.声明切面类对象
        final MyAspect2 aspect = new MyAspect2();

        //3.创建增强对象
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(studentService.getClass());
        //设置回调【拦截】
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                /**
                 * proxy:
                 * proxy代理对象是StudentService的子类
                 */
                aspect.before();
                //方式一:放行方法
                //Object retObj = method.invoke(studentService,args);

                //方式二:等同于方式一,通过代理方法调用父类,好处是解耦了,因为没有引用外部变量
                Object retObj = methodProxy.invokeSuper(proxy,args);//解耦
                System.out.println("拦截.....");
                aspect.after();
                return retObj;
            }
        });

        //创建代理对象
        StudentService serviceProxy = (StudentService) enhancer.create();
        return serviceProxy;
    }
}

Spring的AOP自动和半自动

不论自动还是半自动,目的都是不写生成代理对象的相关方法,例如避免书写上面的MyBeanFactory.java文件

Spring项目中半自动AOP

需要额外添加AOP联盟(规范)、spring-aop(实现)两个jar包


  1. com.springsource.org.aopalliance-1.0.0.jar AOP联盟
  2. spring-aop-3.2.0.RELEASE.jar AOP实现

简单案例

<!--bean.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<!--xmlns xml namespace:xml命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">
    <!--  配置UserService-->
    <bean id="userService" class="com.zengqiang.service.UserServiceImpl"></bean>

    <!--  配置切面类对象-->
    <bean id="myAspect" class="com.zengqiang.aspect.MyAspect"></bean>

    <!-- 配置代理对象
        默认情况下Spring的AOP生成的代理是JDK的Proxy实现的
    -->
    <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 接口 :如果只是一个接口,就写Value,如果是多个接口就写List-->
        <property name="interfaces"  value="com.zengqiang.service.IUserService">
        </property>
        <!-- 目标对象 -->
        <!-- 注意此处和上下不同。用的是ref,而不是value,value是特殊写法,记下即可 -->
        <property name="target" ref="userService"/>
        <!-- 切面类-->
        <property name="interceptorNames" value="myAspect"></property>
        <!-- 配置使用cglib生成-->
        <property name="optimize" value="true"></property>
    </bean>
</beans>
/**
 * 切面类:增加代码 与 切入点 结合
    务必实现MethodInterceptor
 */
public class MyAspect implements MethodInterceptor{

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        //拦截方法
        System.out.println("开启事务...");
        //放行
        Object retObj = mi.proceed();
        System.out.println("拦截.....");
        System.out.println("提交事务...");
        return retObj;
    }
}

Spring项目中全自动AOP

需要com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar(织入)
需要添加的索引


  1. xmlns:aop ="http://www.springframework.org/schema/aop"
  2. http://www.springframework.org/schema/aop
  3. http://www.springframework.org/schema/aop/spring-aop.xsd

<!--bean.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<!--xmlns xml namespace:xml命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p ="http://www.springframework.org/schema/p"
       xmlns:context ="http://www.springframework.org/schema/context"
       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/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">
    <!--  配置UserService-->
    <bean id="userService" class="com.zengqiang.service.UserServiceImpl"></bean>
    <bean id="studentService" class="com.zengqiang.service.StudentService"></bean>
    <!--  配置切面类对象-->
    <bean id="myAspect" class="com.zengqiang.aspect.MyAspect"></bean>
    <!-- 全自动AOP配置
     1.在bean中配置aop约束
     2.配置aop:conifg内容,把切入点和通知结合

     proxy-target-class:使用cglib实现代理
     expression 表达式:*任意
                execution(*         com.zengqiang.service.*.   *       (..))
                          返回值    包名            类名 方法名  参数
    
    proxy-target-class="true":代表开启cglib
     -->
    <aop:config  proxy-target-class="true">
        <!-- 切入点:
             expression:表达式
             每个service的方法前面都开启事务和结束事务

             AOP:用于事务配置&日志记录
         -->
        <aop:pointcut id="myPointcut" expression="execution(* com.zengqing.service.*.*(..))"/>
        <!-- 通知(就是切面类对象) 关联 切入点-->
        <aop:advisor advice-ref="myAspect" pointcut-ref="myPointcut"></aop:advisor>
    </aop:config>
</beans>

AspectJ

基本案例

/**
* 切面类
*/
public class MyAspect3{
    public void myBefore(){
        System.out.println("前置通知...");
    }
    public void myAfterReturning(){
        System.out.println("后置通知...");
    }
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知...");
        System.out.println(pjp.getSignature().getName());//切入点就方法名
        System.out.println("开启事务...");
        //放行
        Object retObj = pjp.proceed();
        System.out.println("提交事务...");
        return retObj;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!--xmlns xml namespace:xml命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p ="http://www.springframework.org/schema/p"
       xmlns:context ="http://www.springframework.org/schema/context"
       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/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">


    <!--  配置UserService-->
    <bean id="userService" class="com.zengqiang.service.UserServiceImpl"></bean>

    <!-- 配置切面对象-->
    <bean id="myAspect" class="com.zengqiang.aspect.MyAspect"></bean>

    <!-- 配置 aop -->
    <aop:config>
        <!-- aop:指定切面-->
        <aop:aspect ref="myAspect">
            <!--定义一个切入点-->
            <aop:pointcut id="myPointcut" expression="execution(* com.zengqiang.service.UserServiceImpl.*(..))"/>
            <!-- 配置前置通知...-->
            <aop:before method="myBefore" pointcut-ref="myPointcut" />
            <!-- 配置后置通知...-->
            <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="retValue"/>
            <!--配置环绕通知-->
            <aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
            <!-- 配置异常通知
            throwing="e" 值,是方法的参数名
            -->
            <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="e"/>
            <!--配置最终通知:不管有没有异常,最终通知都会执行-->
            <aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

execution()

语法:execution(修饰符  返回值  包.类.方法名(参数) throws异常)
修饰符,一般省略
    public      公共方法
    *           任意
返回值,不能省略
    void            返回没有值
    String      返回值字符串
    *           任意
包,[省略]
    com.zengqiang.crm           固定包
    com.zengqiang.crm.*.service crm包下面子包任意 (例如:com.zengqiang.crm.staff.service)
    com.zengqiang.crm..         crm包下面的所有子包(含自己)
    com.zengqiang.crm.*.service..   crm包下面任意子包,固定目录service,service目录任意包
类,[省略]
    UserServiceImpl         指定类
    *Impl                   以Impl结尾
    User*                   以User开头
    *                       任意
方法名,不能省略
    addUser                 固定方法
    add*                        以add开头
    *Do                     以Do结尾
    *                       任意
(参数)
    ()                      无参
    (int)                       一个整型
    (int ,int)                  两个
    (..)                        参数任意
throws ,可省略,一般不写。

案例1:
execution(* com.zengqiang.crm.*.service..*.*(..))

案例2:或
<aop:pointcut expression="execution(* com.zengqiang.crm.service.*.*(..)) || 
                          execution(* com.zengqiang.*Do.*(..))" id="myPointCut"/>

AspectJ基于xml的案例

package com.zengqiang.aspect;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;


public class MyAspect {
    public void myPointcut(){}

    public void myBefore(JoinPoint jp){
        System.out.println("1.前置通知..." + jp.getSignature().getName());//连接点方法名
    }

   
    public void myAfterReturning(JoinPoint jp,Object retValue){
        System.out.println("3.后置通知..." +  jp.getSignature().getName());
        System.out.println("返回值:" + retValue);
    }

    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        //ProceedingJoinPoint和JoinPoint区别之一就是ProceedingJoinPoint有放行方法
        System.out.println("2.环绕通知...开启事务..." + pjp.getSignature().getName());
        //放行
        Object retObj = pjp.proceed();
        System.out.println("4.环绕通知....提交事务...");
        return retObj;
    }


    public void myAfterThrowing(JoinPoint jp,Throwable e){
        System.out.println("异常通知..." + jp.getSignature().getName() + "===" + e.getMessage() );
    }

    public void myAfter(JoinPoint jp){
        System.out.println("最终通知..." + jp.getSignature().getName());
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!--xmlns xml namespace:xml命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p ="http://www.springframework.org/schema/p"
       xmlns:context ="http://www.springframework.org/schema/context"
       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/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">


    <!--  配置UserService-->
    <bean id="userService" class="com.zengqiang.service.UserServiceImpl"></bean>

    <!-- 配置切面对象-->
    <bean id="myAspect" class="com.zengqiang.aspect.MyAspect"></bean>

    <!-- 配置 aop -->
    <aop:config>
        <!-- aop:指定切面-->
        <aop:aspect ref="myAspect">
            <!--定义一个切入点-->
            <aop:pointcut id="myPointcut" expression="execution(* com.zengqiang.service.UserServiceImpl.*(..))"/>

            <!-- 其实上面公共的切入点也可以不声明,直接再配置每个通知的时候在每次填写,但是复用性有问题 -->
            <!-- 配置前置通知...-->
            <aop:before method="myBefore" pointcut-ref="myPointcut" />

            <!-- 配置后置通知...-->
            <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="retValue"/>

            <!--配置环绕通知-->
            <aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>

            <!-- 配置异常通知
            throwing="e" 值,是方法的参数名
            -->
            <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="e"/>

            <!--配置最终通知:不管有没有异常,最终通知都会执行-->
            <aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

AspectJ基于注解的案例

//@Aspect对应 bean.xml中的<aop:config><aop:aspect ref="myAspect"></aop:aspect></aop:config>
@Component
@Aspect //告诉配置文件这是切入点类
public class MyAspect {
    //声明一个公共的切入点
   //等效于 <aop:pointcut id="myPointcut" expression="execution(* com.zengqiang.service.UserServiceImpl.*(..))"/>
    @Pointcut("execution(* com.zengqiang.service.UserServiceImpl.*(..))")
    public void myPointcut(){}

    @Before("myPointcut()")
    public void myBefore(JoinPoint jp){
        System.out.println("1.前置通知..." + jp.getSignature().getName());//连接点方法名
        //例如此时test中调用的是userService.deleteUser();方法,则上面jp.getSignature().getName()输出
        //就是deleteUser
    }

    /**
     * 后置通知获取service方法执行后的返回值
     * Object retValue:service方法执行的返回值,如果写了返回值,需要在xml中配置returning
     * @param jp
     */
    // <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="retValue"/>
    @AfterReturning(pointcut = "myPointcut()",returning = "retValue")
    public void myAfterReturning(JoinPoint jp,Object retValue){
        System.out.println("3.后置通知..." +  jp.getSignature().getName());
        System.out.println("返回值:" + retValue);
    }

    @Around("myPointcut()")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        //ProceedingJoinPoint和JoinPoint区别之一就是ProceedingJoinPoint有放行方法
        System.out.println("2.环绕通知...开启事务..." + pjp.getSignature().getName());
        //放行
        Object retObj = pjp.proceed();
        System.out.println("4.环绕通知....提交事务...");
        return retObj;
    }


    @AfterThrowing(pointcut = "myPointcut()",throwing = "e")
    public void myAfterThrowing(JoinPoint jp,Throwable e){
        System.out.println("异常通知..." + jp.getSignature().getName() + "===" + e.getMessage() );
    }

    @After("myPointcut()")
    public void myAfter(JoinPoint jp){
        System.out.println("最终通知..." + jp.getSignature().getName());
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!--xmlns xml namespace:xml命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p ="http://www.springframework.org/schema/p"
       xmlns:context ="http://www.springframework.org/schema/context"
       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/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">


    <!-- 配置扫描注解的位置 -->
    <context:component-scan base-package="com.zengqiang"/>

    <!-- 配置aop注解生效-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!--aop配置-->
    <aop:config>
        <aop:aspect ref="myAspect"></aop:aspect>
    </aop:config>
</beans>

当AspectJ-xml和注解同时存在的执行顺序

大部分情况下,这个顺序对业务没影响,主要注意事务提交和资源释放的顺序。该顺序针对上面AspectJ基于注解的案例中的MyAspect.JAVA


  1. service方法有返回值 + 无异常 + XML

1.前置通知...deleteUser
2.环绕通知...开启事务...deleteUser
通过id删除用户
3.后置通知...deleteUser
返回值:1
4.环绕通知....提交事务...
最终通知...deleteUser


  1. service方法有返回值 + 无异常 + 注解

2.环绕通知...开启事务...deleteUser
1.前置通知...deleteUser
通过id删除用户
4.环绕通知....提交事务...
最终通知...deleteUser
3.后置通知...deleteUser
返回值:1


  1. service方法没有返回值 + 无异常 + XML

1.前置通知...deleteUser
2.环绕通知...开启事务...deleteUser
删除用户。。。。
3.后置通知...deleteUser
返回值:null
4.环绕通知....提交事务...
最终通知...deleteUser


  1. service方法没有返回值 + 无异常 + 注解

2.环绕通知...开启事务...deleteUser
1.前置通知...deleteUser
删除用户。。。。
4.环绕通知....提交事务...
最终通知...deleteUser
3.后置通知...deleteUser
返回值:null



AOP的事务配置

基于xml的形式

 <!-- 配置事务管理器,对照下面的图片使用-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--配置dataSource-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
图片1.png 图片2.png

补充:isolation:代表隔离级别,propagation代表传播行为,图上是默认值,其实可以省略不写

基于注解的形式

图片3.png 图片4.png
上一篇下一篇

猜你喜欢

热点阅读