学习Spring的第5天
可以使用动态代理来将日志代码动态的写在核心方法执行的前后。
但是,我们发现,虽然动态代理很强大,但是写起来好难。
当然,这个动态代理技术最大的缺陷就是:
如果目标对象没有实现任何接口
动态代理最大的缺陷:
JDK默认的动态代理,如果目标对象没有实现任何接口,是无法为它创建代理对象的。
我们看一下我们自己定义的getProxy()返回的对象是什么类型的。
之所以代理对象可以调用被代理对象的加减乘除的方法,就是因为,代理对象也实现了被代理对象的所有接口。
代理对象和被代理对象唯一能产生的关联就是实现了同一个接口。
这个结果可以看出我们获取的proxy对象,它也实现了Calculator接口。
如果目标对象(被代理对象)没有实现任何接口,是无法为他创建代理对象的。
动态代理已经是面向切面编程了。
因为,动态代理就是将日志代码动态的切入到加减乘除方法的指定位置(method.invoke()这个运行的前后)。
Spring知道动态代理的写法,太难了,所以就弄了一个AOP。
其实 AOP的底层就是:动态代理
可以利用Spring一句代码都不写的去创建动态代理。
实现简单,而且没有强制要求,目标对象必须实现接口。
AOP简单总结:
AOP:
将某段代码:这里举例子就是日志的打印代码
动态的切入:没有把日志代码和逻辑代码(加减乘除)写在一起
到
指定的方法:加减乘除
的
指定位置:方法的开始,结束,出异常之后…
Spring简化了切面编程。
AOP的几个专业术语:(抽象)
按照之前的例子来讲解。
1、接口,有加减乘除四个方法。
2、在方法的开始前,执行后,或者出现异常的时候,都可以选择打印日志。
术语1:横切关注点。
无论是加,减,乘,除四个方法,都是在它的方法开始的时候,加了日志打印。这就是横切关注点。
所以有四个横切关注点。
这个就是横切关注点
术语2:通知方法。
我们把方法执行前的日志打印叫做:通知方法。
所以,通知方法也有四个。
其实这两个就是通知方法。
术语3:切面类
这些方法,我们统一写在了一个LogUtils里面了。
这个类LogUtils叫做切面类。
1、接口类:
2、创建代理类:
3、日志打印工具类:
4、测试类:
执行结果:
术语4:连接点
每一个方法的每一个位置都是一个连接点。
比如:
拿add()方法来举例子。
add()方法,执行开始之前,要执行LogUtils.logStart()。开始执行add()之前的这个位置就是一个连接点,执行之后又是一个连接点。
术语5:切入点
但是现在我希望:
四个方法,每个方法有四个位置,我不希望每个方法的每个日志都进行日志的打印。
比如:
add:只有在方法结束的时候,打印日志
sub:从来都不打印日志
mul:只有在方法返回的时候,打印日志
div:只有在方法异常的时候,打印日志
也就是这三个点会打印日志。
我们把这三个点叫做切入点。
我们真正需要执行日志记录的地方叫做切入点。
切入点和连接点的关系:
我们在众多连接点中,选出我们感兴趣的地方,这些地方就是切入点。
术语6:切入点表达式:
刚才说了。要在众多连接点里面选出感兴趣的切入点。那怎么选呢?
就是写一个切入点表达式来选。
连接点 VS 切入点 VS 切入点表达式
类似于
表中所有数据 VS 只要性别是女生的数据 VS 查询出女生数据的sql语句
使用AOP来将日志记录动态的加进去。
学习AOP的简单配置。
目标:
如何将LogUtils这个类(切面类)
中的这些logStart()和logEnd()等这些方法(通知方法)
动态的在加法,或者乘法,或者触发,或者减法运行的各个位置去切入。
现在不写动态代理了。
AOP的使用步骤。
1、导包。下面是基础的包
Spring支持面向切面编程的包是:
这是基本版的面向切面编程的包
还有加强版的面向切面编程:
导这三个加强版的jar包的原因就是,即使目标对象没有实现任何接口,也能创建动态代理。
2、写配置:
第一步:
将目标类和切面类加入到IOC容器中。
目标类:MyMathCaculator
切面类:LogUtils
将类加入到IOC容器中:
1、加注解。
2、写好ioc.xml配置文件
context 扫描基础包
<?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"
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-4.3.xsd">
<context:component-scan base-package="com.rtl" ></context:component-scan>
</beans>
第二步:
告诉Spring,哪个是切面类。
告诉Spring,哪个是切面类。就是在切面类上面在加上注解@Aspect
我们学习过程中,一直没有使用maven做。直接导入自己去网上找的jar包。发现学习AOP编程的时候无法找到@Aspect注解的包
所以我们选择重新new一个项目,这个是maven项目。
自己改成maven项目之后,就已经解决@Aspect注解加不上的问题了。
那就重新介绍一下项目的架构。
本项目是一个学习AOP编程的项目。使用maven技术。
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring_aop</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring.version>5.2.2.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
</project>
jdbc.username=root
jdbc.password=root
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/test?useSSL=false
jdbc.driverClass=com.mysql.jdbc.Driver
<?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"
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-4.3.xsd">
<context:component-scan base-package="com.rtl" ></context:component-scan>
</beans>
package com.rtl.impl;
import com.rtl.inter.Calculator;
import org.springframework.stereotype.Service;
@Service
public class MyMathCalculator implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j ;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j ;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j ;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j ;
return result;
}
}
package com.rtl.inter;
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
package com.rtl.utils;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect
@Component
public class LogUtils {
public static void logStart(Method method, Object... args){
System.out.println("【"+method.getName()+"】方法开始执行,使用的参数列表是:【"+ Arrays.asList(args)+"】");
}
public static void logEnd(Method method,Object result){
System.out.println("【"+method.getName()+"】方法执行完成,它的计算结果是:"+result);
}
}
package com.rtl.test;
import com.rtl.impl.MyMathCalculator;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
@Test
public void test01(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
}
}
写AOP编程
第一步:
导包(依赖形式)
第二步:
写配置。
1、目标类和切面类加入到IOC容器里面。
2、告诉Spring,我们哪个类是切面类。
也就是在LogUtils类头上,加上@Aspect注解
3、告诉Spring,切面类里面的每一个方法,都是何时运行的。
我们先把参数弄掉,简单一点开始。
之前的LogUtils:
现在的LogUtils:
没有方法的参数了
package com.rtl.utils;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect
@Component
public class LogUtils {
//想在执行目标方法(加减乘除)之前执行
public static void logStart(){
System.out.println("【xxx】方法开始执行,使用的参数列表是:【xxx】");
}
//想在目标方法(加减乘除)正常执行完成之后执行
public static void logReturn(){
System.out.println("【xxx】方法执行完成,它的计算结果是:xxx");
}
//想在目标方法(加减乘除)执行的时候,出现异常执行
public static void logException(){
System.out.println("【xxx】方法执行的时候出现异常了,异常信息已经通知测试小组了。");
}
//想在目标方法(加减乘除)结束的时候执行
public static void logEnd(){
System.out.println("【xxx】方法最终执行完毕");
}
}
3、告诉Spring,切面类里面的每一个方法,都是何时运行的。
我们先把参数弄掉,简单一点开始。
使用几个注解:
1、@Before 前置通知
org.aspectj.lang.annotation.Before;
在目标方法之前运行。
2、@After 后置通知
在目标方法运行结束之后
3、@AfterReturning 返回通知
在目标方法正常返回的时候
3、AfterThrowing 异常通知
在目标方法抛出异常时执行
4、@Around 环绕 环绕通知
它是最强大的通知,最后讲
写上注解之后,在括号里面加上切入点表达式
访问权限符 返回值类型 方法签名(copy reference)
比如这个logStart()方法,他就是在add()方法执行之前执行的。
所以:
@Before(“execution(public int com.rtl.impl.MyMathCalculator.add(int,int))”)
@Before("execution(public int com.rtl.impl.MyMathCalculator.add(int,int))")
//想在执行目标方法(加减乘除)之前执行
public static void logStart(){
System.out.println("【xxx】方法开始执行,使用的参数列表是:【xxx】");
}
这个就是在所有方法执行的时候,都选择执行。
我们先选择所有的方法。也就是用*,而不是具体方法
这些就是第三步,我们要告诉这些通知方法,在什么时候需要执行。
package com.rtl.utils;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect
@Component
public class LogUtils {
@Before("execution(public int com.rtl.impl.MyMathCalculator.*(int,int))")
//想在执行目标方法(加减乘除)之前执行
public static void logStart(){
System.out.println("【xxx】方法开始执行,使用的参数列表是:【xxx】");
}
@AfterReturning("execution(public int com.rtl.impl.MyMathCalculator.*(int,int))")
//想在目标方法(加减乘除)正常执行完成之后执行
public static void logReturn(){
System.out.println("【xxx】方法执行完成,它的计算结果是:xxx");
}
@AfterThrowing("execution(public int com.rtl.impl.MyMathCalculator.*(int,int))")
//想在目标方法(加减乘除)执行的时候,出现异常执行
public static void logException(){
System.out.println("【xxx】方法执行的时候出现异常了,异常信息已经通知测试小组了。");
}
@After(("execution(public int com.rtl.impl.MyMathCalculator.*(int,int))"))
//想在目标方法(加减乘除)结束的时候执行
public static void logEnd(){
System.out.println("【xxx】方法最终执行完毕");
}
}
配置里面的最后一个:
开启基于注解的AOP模式。
在配置文件ioc.xml里面去配置。
引入aop名称空间
66 20:37(在配置文件ioc.xml里面去配置。)