SPRING-了解4-AOP1

主要理念:不用继承,而是用Java Reflect类中的Proxy来实现对方法的增强

面向切面编程   oop的延续,对业务进行隔离,降低耦合度,目标是不改变原来的代码在主干中增加新功能

比如已经写好登录功能,现在需要加入权限,那么应该额外写权限模块,之后配置即可

AOP底层原理:

    底层使用动态代理,以下两者差别在于一个接口实现类代理,一个子类实现代理

    (1)有接口情况:使用JDK动态代理

           比如有UserDao 接口  以及UserDaoImpl实现类

              JDK动态代理就是UserDao 接口实现类代理对象,不通过new的方法,以增强类的方法

     (2)无接口情况:使用CGLIB动态代理

           目标是不改变以下类的add实现方法

public class User{
public void add(){
}
}

原始方法:可以创建User类的子类

新方法:创建User类的子类代理对象,不用new方法

实例

  使用JDK动态代理

     使用Proxy里面的方法创建代理对象

    查阅文档:Java 8 中文版 - 在线API中文手册 - 码工具

java.lang.reflect --》Class Proxy

 其中有方法

newProxyInstance(ClassLoader loader, <?>[] interfaces, InvocationHandler h)

 newProxyInstance(ClassLoader loader, <?>[] interfaces, InvocationHandler h)

注意其返回指定接口代理了的实例,静态的方法,可以调用

第一个参数:类加载器  ClassLoader loader

第二个参数:数组,增强方法所在的类,这个类实现的接口,可为多个

第三个参数:实现这个接口InvocationHandler ,创建代理对象  写增强的方法

底层原理代码实现

1)创建接口与实现类

public interface UserDao {
    public int add(int a,int b);
    public String update(String id);
}
public class UserDaoImpl implements UserDao{
    @Override
    public int add(int a, int b) {
        System.out.println("add方法执行了");
        return a+b;
    }

    @Override
    public String update(String id) {
        System.out.println("update方法执行了");
        return  id;

    }
}

1)创建JDKProxy类,名字任意

package com.i7i8i9.spring5;

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

public class JDKProxy {
    public static void main(String[] args) {
        Class[] interfaces={UserDao.class};//为了给Proxy.newProxyInstance提供第二个参数
        //创建接口实现类代理对象
        UserDaoImpl userDao=new UserDaoImpl(); //这个是为了Proxy.newProxyInstance第三个参数设计的类构造方法
        UserDao dao=(UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao));
        int sum=dao.add(3,5);
        System.out.println("求和"+sum);
    }
}
//创建代理对象
class UserDaoProxy implements InvocationHandler {
    //创建谁的代理对象,就要把谁传进来,多种方式
    //有参构造  Object obj可以用UserDaoImpl,但是范围会缩小
    private Object obj;
    public UserDaoProxy(Object obj){
      this.obj=obj;

    }

    //增强的逻辑
    @Override
    //invoke方法意思是对象一创建,方法就会被调用
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法之前
        System.out.println("方法之前执行"+method.getName()+"传递的参数"+ Arrays.toString(args));
        //被增强方法执行
        Object res=method.invoke(obj,args);
        //方法之后
        System.out.println("方法之后,"+obj);
        return res;
    }
}

main中  UserDao dao=(UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao)); 相当于 new 对象

这里面分为两个类,第一个是JDKProxy,命名任意,里面包含main方法

主要是围绕Proxy.newProxyInstance三个参数进行

针对第二个参数定义了一个集合:Class[] interfaces={UserDao.class};

针对第三个参数创建了一个类 class UserDaoProxy implements InvocationHandler

2)class UserDaoProxy

   在这个类里面第一个要求是:创建谁的代理对象,就要把谁传进来,多种方式实现

这里使用了构造方法传值,为了增强通用性,使用了Object

  private Object obj;
    public UserDaoProxy(Object obj){
      this.obj=obj;

    }

第二部分写了增强逻辑

也就是在被增强方法执行前后加入了新逻辑

注意被增强方法执行处其实可以加一个判断

根据method.getName()进行判断

 //增强的逻辑
    @Override
    //invoke方法意思是对象一创建,方法就会被调用
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法之前
        System.out.println("方法之前执行"+method.getName()+"传递的参数"+ Arrays.toString(args));
        //被增强方法执行
        Object res=method.invoke(obj,args);
        //方法之后
        System.out.println("方法之后,"+obj);
        return res;
    }

3)执行过程

先看执行结果

   

      a)第一步 准备阶段:   传入三个参数

    重点是    UserDao dao=(UserDao) Proxy.newProxyInstance(三个参数)

      因为Proxy类中创建了UserDaoImpl userDao并把它传给了第三个参数,即

new UserDaoProxy(userDao) 

  第三个参数返回了

因为是object,所以在UserDao dao=(UserDao)对其进行强转

b)第二步: 调用方法

int sum=dao.add(3,5);

这个触发了被增强方法执行,并返回了Object 对象 com.i7i8i9.spring5.UserDaoImpl@31cefde0

​
方法之前执行add传递的参数[3, 5]
add方法执行了
方法之后,com.i7i8i9.spring5.UserDaoImpl@31cefde0
求和8

​
//被增强方法执行
Object res=method.invoke(obj,args);

invoke执行完毕之后,再执行:

System.out.println("求和"+sum);

另外如果main中执行的是userdaoImpl另外一个方法,那么也会被增强,也就是说这个实现类中所有的方法都被增强了,当如不想增强,可以再invoke实现类中用if+method.getName获取当前是什么方法进入执行操作

AOP术语

1.连接点:

     从上面可以看出,假设一个实现类中有4个方法, 那么这四个方法都被增强了,他们就是连接点

2.切入点:实际被真正增强的方法

3.通知(增强):实际增强的逻辑部分,就是上文中的权限判断

   通知有多种类型

1)前置通知

2)后置通知

3)环绕通知:前后都执行

4)异常通知

5)最终通知:类似try catch 中的finally,无论有没有异常都会执行

切面:是个动作,把通知应用到切入点的过程

AOP操作(准备)

1.Spring框架一般是基于AspectJ实现AOP操作

   AspectJ不是Spring组成部分,是一个单独框架,不依赖Spring

一般把AspectJ和Spring结合起来做AOP操作

  两种实现方式

   1)基于xml配置文件

   2)基于注解方式实现

2.引入AOP操作相关依赖

  1) spring-framework中的spring-aspects-5.3.9.jar

还有其他三个

2) sf.cglib.jar

      https://mvnrepository.com/artifact/net.sourceforge.cglib/com.springsource.net.sf.cglib/2.2.0

点击files下载,出不来的话换一个网址

Download com.springsource.net.sf.cglib-2.2.0.jar : com.springsource.net « c « Jar File Download

3)aopalliance-1.0

第一个网址比较容易查,但有时候不是最新的

aopalliance-1.0.jar下载及Maven、Gradle引入代码,pom文件及包内class -时代Java

或者

https://mvnrepository.com/artifact/aopalliance/aopalliance/1.0

4)Aspectj

https://mvnrepository.com/artifact/org.aspectj/aspectjweaver/1.9.9.1

切入点表达式

作用:知道对哪个类的哪个方法进行增强

语法结构:execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))

举例1: 对 com.i7i8i9.dao.BookDao里面的add方法进行增强

          execution(* com.i7i8i9.dao.BookDao.add(..))注意*代表public private都可以   两个点代表方法参数

举例2: 对 com.i7i8i9.dao.BookDao里面的所有方法进行增强

          execution(* com.i7i8i9.dao.BookDao.*(..))注意第二个*代表所有方法

举例3: 对 com.i7i8i9.dao包里面的所有类的所有方法进行增强

          execution(* com.i7i8i9.dao.*.*(..))注意第二个*代表所有类

编写代码

1.建立类:User

2.创建增强类:UserProxy

3.通知配置5步

  1)开启注解扫描

      比如使用src下xml

 类似bean,引入conte和aop命名空间:各插入一行xmlns   增加两个http(修改其中3处名字)

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.i7i8i9.spring5.aopanno"></context:component-scan>

  2)使用注解创建被增强和增强类的对象创建

@Component
public class User {
    public void add(){
        System.out.println("add.......");
    }
}
@Component
public class UserProxy {
    public void before(){
        System.out.println("before----");
    }
}

3)在增强类上增加注解 @Aspect,表示要生成代理对象

@Component
@Aspect
public class UserProxy {
    public void before(){
        System.out.println("before----");
    }
}

4)在Spring配置文件中开启生成代理对象,意思就是开启这个之后就去找@Aspect,找到就生成代理对象

<?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: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.i7i8i9.spring5.aopanno"></context:component-scan>
    <!--开启Aspect生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

5)配置通知

  在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式

通知类型注解有5种Before After  AfterReturning    AfterReturning    Around

package com.i7i8i9.spring5.aopanno;

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

@Component
@Aspect
public class UserProxy {
//@Before是Aspect注解
    @Before(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
    public void before(){
        System.out.println("before----");
    }
    //方法执行之后,也是finally无论有没有异常都会执行
    @After(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
    public void after(){
        System.out.println("after----");
    }
   //在返回值之后就执行 有异常就不执行
    @AfterReturning(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
    public void afterReturn(){
        System.out.println("afterReturn----");
    }


    @AfterThrowing(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
    public void afterThrow(){
        System.out.println("异常通知----");
    }


    @Around(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("环绕之前----");
        proceedingJoinPoint.proceed();//用于区分执行前后,有异常抛出Throwable
        System.out.println("环绕之后----");//有异常就不执行
    }
}

测试

public class TestUserAop {

    @Test
    public void userAopTest(){
        ApplicationContext context=new ClassPathXmlApplicationContext("bean1.xml");
        User user=context.getBean("user", User.class);
        user.add();
        
    }
}

没有异常结果

环绕之前----
before----
add.......
afterReturn----
after----
环绕之后----

   制造一个异常

在User中增加:int i=10/0;

package com.i7i8i9.spring5.aopanno;

import org.springframework.stereotype.Component;

@Component
public class User {
    public void add(){
        int i=10/0;
        System.out.println("add.......");
    }
}

测试结果

环绕之前----
before----
异常通知----
after----

java.lang.ArithmeticException: / by zero

相同切入点提取

新建一个方法加@Pointcut

其他方法value=该方法

@Pointcut(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")

public void pointDemo(){ }



//@Before是Aspect注解 @Before(value = "pointDemo()")
public void before(){
        System.out.println("before----");
    }

增强优先级

多个类都对一个方法增强,可以在增强类上加@Order(数字类型值),越小优先级越高

似乎没有太大效果

@Order(13)
public class PersonProxy {
    @Before(value = "execution(* com.i7i8i9.spring5.aopanno.User.add(..))")
    public void afterReturn(){
        System.out.println("另外一个增强----");
    }
}



@Order(2)
public class UserProxy {

AspectJ 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: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
">
<!--创建对象-->
    <bean id="book" class="com.i7i8i9.spring5.aopxml.Book"></bean>
    <bean id="bookProxy" class="com.i7i8i9.spring5.aopxml.BookProxy"></bean>
<!--    切入点配置-->
    <aop:config>
<!--        切入点 p1名字随便起-->
        <aop:pointcut id="p1" expression="execution(* com.i7i8i9.spring5.aopxml.Book.buy(..))"/>
<!--        配置切面-->
    <aop:aspect ref="bookProxy">
<!--        配置增强在具体方法上-->
        <aop:before method="before" pointcut-ref="p1" />
    </aop:aspect>
    </aop:config>
</beans>

全注解方式

即不用xml

创建一个配置类

 

@Configuration
@ComponentScan(basePackages = {"com.i7i8i9"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值