Spring AOP面向切面编程、动态代理的理解

动态代理技术

原始编写java代码方式

.java -> javac ->.class -> 类加载

**代理的意义:**通过生成代理,来避免访问真实的目标类,实现无入侵式的代码扩展,在方法前后做一些事情,并且不修改源码;
**静态代理:**需要程序员自己创建,在静态代理时要知道代理的是哪个类或接口;
**动态代理:**通过反射机制完成,动态代理把代理类中对目标类的逻辑处理抽象成函数式内部类InvocationHandler,其中的invoke方法进行逻辑处理;代理通过InvocationHandler实现对目标类的工作,不需要像静态代理那样,对每个方法分别的处理;动态代理的调用处理程序必须事先InvocationHandler接口,及使用Proxy类中的newProxyInstance方法动态的创建代理类

Proxy生成代理类的底层实现
接口

public interface UserService {

    void a(int x,int y);
    void b();
    void c();
}

被代理的目标类

public class UserServiceTarget implements UserService {
    public void a(int x,int y) {
        System.out.println("方法a");
    }

    public void b() {
        System.out.println("方法b");
    }

    public void c() {
        System.out.println("方法c");
    }
}
public class TestDynamicProxy {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //1. 生成实现接口的代理类的字节码
        // 参数1 String 表示代理类的名字;参数2 Class[] 表示代理类要实现的接口
        final byte[] proxyBytes = ProxyGenerator.generateProxyClass("UserServiceProxy", new Class[]{UserService.class});

        //2. 执行类加载,将生成的代理类字节码加载为类对象
        //生成加载器
        ClassLoader cloader=new ClassLoader(){
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                return defineClass(name,proxyBytes,0,proxyBytes.length);
            }
        };

        //类加载后生成的代理类
        Class c=cloader.loadClass("UserServiceProxy");

        //3. 创建代理类实例对象,不能直接用new 创建

        //获取代理类的构造方法
        Constructor constructor = c.getConstructor(InvocationHandler.class);

        //目标类
        final UserServiceTarget target = new UserServiceTarget();


        //创建代理类实例
        UserService  proxy=(UserService) constructor.newInstance(new InvocationHandler() {
            //指定代理类所干的事情,即测量目标类方法运行时间
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long startTime = System.nanoTime();

                //执行目标类的方法
                method.invoke(target, args);

                long endTime = System.nanoTime();
                System.out.println("方法运行时间:" + (endTime - startTime));
                return null;
            }
        });

    proxy.a(10,20);
    }
}

直接使用Proxy来生成代理类

public class TestDyanmicProxy2 {
    public static void main(String[] args) {
        //直接创建代理类的实例

        //1. 获取类加载器
        ClassLoader cl = UserService.class.getClassLoader();
        //2. 规定代理类要实现的接口
        Class[] interfaces = {UserService.class};
        //3. 给定一个InvocationHandler对象,包含要执行的重复逻辑
        UserServiceTarget target = new UserServiceTarget();

        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long startTime = System.nanoTime();

                //执行目标类的方法
                method.invoke(target, args);

                long endTime = System.nanoTime();
                System.out.println("方法运行时间:" + (endTime - startTime));
                return null;
            }
        };

        //直接生成代理类对象
        UserService u =(UserService) Proxy.newProxyInstance(cl, interfaces, h);
        System.out.println(u.getClass());//class com.sun.proxy.$Proxy0
        u.b();
        u.c();
    }
}

AOP (aspect oriented programming 面向切面编程)

aspect 切面,oriented 面向,programming 编程

切面 aspect = 通知 adivce + 切点 pointcut

通知:是一个方法,其中包含了重复的逻辑(计时,事务)
切点:是一种匹配条件,与切点条件符合的目标方法,才会通知方法;

使用步骤

  1. 添加依赖
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.22.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.13</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
    </dependencies>
  1. 编写切面

@Aspect
public class 切面类{
	@Around("切点表达式") //切点表达式用来匹配目标和通知


	public Object 通知方法(ProceedingJoinpoint pjp){
		//重复逻辑 计时,事务控制,权限控制
		//调用目标  result是目标方法返回的结果
		Object result=pjp.proceed();
		retrun result;
	}
}
//表示此类是一个切面类
@Aspect
@Component
public class TimeAspect {

        //表示匹配service包下的UserserviceTarget类中的所有方法
//    @Around("within(service.UserServiceTarget)")

    //表示匹配service包下的UserServiceTarget中的修饰符为public 无返回值的a方法,参数任意个数、任意值
    @Around("execution(public void service.UserServiceTarget.a(..))")


    //组成部分1:通知方法
    //ProceedingJoinPoint 用来调用目标方法
    public Object time(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();

        //调用目标方法
        Object proceed = pjp.proceed();

        long end = System.nanoTime();
        System.out.println("方法运行时间:"+(end-start));
        return proceed;
    }
}
  1. 把切面类和目标类都交给Spring容器管理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
        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"
>
    <!--启用切面编程相关注解 如:@Aspect,@Around 还提供自动产生代理类的功能-->
    <aop:aspectj-autoproxy/>

    <context:component-scan base-package="service,aspect"/>

</beans>
  1. 使用切面
    从容器getBean根据接口获取,容器返回的是代理对象,代理对象中进行切点的匹配,匹配到了进行通知,通知内部调用目标;
public class TestProxy {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        //实际上调用的是代理类
        UserService bean = context.getBean(UserService.class);
        System.out.println(bean.getClass());//class com.sun.proxy.$Proxy8
        bean.a(10,20);
    }
}

切点表达式

  1. within 匹配类中的所有方法(粒度粗)
    语法:
within(包名.类名)

within(service.*ServiceTarget)

  1. execution 表达式 可以精确到类中的每个方法(粒度细)
    语法:
execution(访问修饰符	返回值	包名.类名.方法名字(参数信息))
  • 返回值的位置写 * 表示返回值类型是任意的;
  • 类名写 * 表示任意的类;
  • *写在参数位置只能表示一个参数;
  • ..写在参数位置,匹配任意个数和任意类型的参数
  1. @annotation注解方式
@annotation(包名.注解名)
    //表示方法上有@Time注解的就通知
    @Around("@annotation(aspect.Time)")

通知类型

  • @Around 环绕通知 (功能最强大)
  • @Before 前置通知:在目标方法调用之前通知
  • @After 后置通知
  • @AfterReturning 正常返回通知
  • @AfterThrowing 异常返回通知

所有的修饰都是针对目标方法;

面向切面的应用

  1. spring中的事务管理
    代理对象中与@Transactional进行匹配,如果方法上有这个注解,就会调用事务通知,继续调用目标方法TransactionIntercepter跟事务相关的通知代码;

  2. 统一的权限控制

  3. 缓存控制

  4. 统一的日志记录

spring中如果目标类没有实现接口

如果目标对象实现了接口:底层spring会调用jdk的动态代理技术来生成代理;要求代理对象和目标对象实现共同的接口;
如果不使用接口,spring通过CGLIB代理技术来生成代理对象,并不要求目标对象和代理对象实现共同接口,底层代理类继承自目标类;

spring中的单元测试

  1. 添加单元测试的依赖
        <!--单元测试的依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.22.RELEASE</version>
            <scope>test</scope>
        </dependency>

如果有多个类需要测试,为了减少多册注解,建议添加父类,只需注解父类,即可以测试子类;

@RunWith(SpringJUnit4ClassRunner.class) //让单元测试支持test
//指定spring配置文件的位置,让测试类运行时根据此文件创建spring容器
@ContextConfiguration("classpath:spring.xml")
public class AbstractTest {
}
public class Test1 extends AbstractTest{

    @Test
    public void test(){
        System.out.println("123");
    }
}

猜你喜欢

转载自blog.csdn.net/mashaokang1314/article/details/87520476
今日推荐