文章目录
一、SpringAop
简单介绍
- 定义全称:Aop的全称是aspects-oriented-programming—面向切面编程-------->也就是Aop是面向对象编程的一个补充,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。将不同方法的同一位置抽象成一个切面对象,对该切面对象进行编程就是Aop.
- 优点
(1)降低模块之间的耦合度
(2)使系统容易扩展
(3)更好地实现代码复用
(4)非业务代码更加集中,不分散,便于统一管理
(5)业务代码更加简洁纯粹,没有其他代码的影响
(6)将复杂的需求分解出不同的方面,将散布在系统中的公共功能集中解决
二、使用步骤
创建maven工程,pom.xml依赖文件中导入相关依赖
<!--该依赖包含aspectjweaver依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.7</version>
</dependency>
<!--该aop依赖包被包含在springwebmvc依赖中-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.5</version>
</dependency>
接口编程,在实现类业务中添加一些日志信息
- 接口类以及实现类
public interface Cal {
public int add(int num1,int num2);
public int sub(int num1,int num2);
public int mul(int num1,int num2);
public int div(int num1,int num2);
}
public class CalImpl implements Cal{
@Override
public int add(int num1, int num2) {
System.out.println("add的方法参数是["+num1+","+num2+"]");
int result=num1+num2;
System.out.println("add方法的结果是" + result);
return result;
}
@Override
public int sub(int num1, int num2) {
System.out.println("sub的方法参数是["+num1+","+num2+"]");
int result=num1-num2;
System.out.println("sub方法的结果是" + result);
return result;
}
@Override
public int mul(int num1, int num2) {
System.out.println("mul的方法参数是["+num1+","+num2+"]");
int result=num1*num2;
System.out.println("mul方法的结果是" + result);
return result;
}
@Override
public int div(int num1, int num2) {
System.out.println("div的方法参数是["+num1+","+num2+"]");
int result=num1/num2;
System.out.println("div方法的结果是" + result);
return result;
}
}
- 测试类
public class Test1 {
public static void main(String[] args) {
Cal cal=new CalImpl();
cal.add(1,1);
System.out.println("=============");
cal.div(2,1);
System.out.println("=============");
cal.mul(1,2);
System.out.println("=============");
cal.sub(1,2);
}
}
总结:上述接口实现类中的日志信息和业务逻辑的耦合性很强,不利于系统的维护,所以使用Aop实现优化操作,因此此时需要使用Aop技术思想,也就是Jdk的动态代理模式进行实现。
解决方法:给业务代码找一个代理,打印日志信息的工作完全可以交给代理来完成,因此业务代码只需要关注自身的业务逻辑即可。
jdk的动态的代理实现Aop切面编程思想
- 接口类以及实现类
public interface Cal {
public int add(int num1,int num2);
public int sub(int num1,int num2);
public int mul(int num1,int num2);
public int div(int num1,int num2);
}
public class CalImpl implements Cal{
@Override
public int add(int num1, int num2) {
//System.out.println("add的方法参数是["+num1+","+num2+"]");
int result=num1+num2;
//System.out.println("add方法的结果是" + result);
return result;
}
@Override
public int sub(int num1, int num2) {
// System.out.println("sub的方法参数是["+num1+","+num2+"]");
int result=num1-num2;
//System.out.println("sub方法的结果是" + result);
return result;
}
@Override
public int mul(int num1, int num2) {
//System.out.println("mul的方法参数是["+num1+","+num2+"]");
int result=num1*num2;
//System.out.println("mul方法的结果是" + result);
return result;
}
@Override
public int div(int num1, int num2) {
//System.out.println("div的方法参数是["+num1+","+num2+"]");
int result=num1/num2;
//System.out.println("div方法的结果是" + result);
return result;
}
}
- 动态代理实现类
package com.entor.jdkproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
//生成动态代理类,其实这个类不是代理对象类,实现InvocationHandler是具有实现代理对象类的功能
public class MyInvocationJdkProxy implements InvocationHandler {
//接收委托对象(目标对象)
private Object object=null;
//返回代理对象
public Object bind(Object object){
this.object=object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
//其中的this指的是当前的MyInvocationJdkProxy这个类来生成动态代理类,返回代理对象,主要是把委托对象中具有的接口功能赋给代理对象同样拥有
}
//这个方法重写就是为了剥离业务逻辑代码和非业务逻辑代码之间的耦合度,也就是解决不用在每个业务逻辑方法中进行日志信息等其他非业务代码
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+"方法的参数是:"+ Arrays.toString(args));//代理对象实现日志信息的输出
Object result = method.invoke(this.object, args);//委托对象的方法执行业务逻辑
//调用的还是委托对象的方法
System.out.println(method.getName()+"的结果是:"+result);
return result;
}
}
- 测试类
package com.entor.jdkproxy;
public class Test2 {
public static void main(String[] args) {
Cal cal=new CalImpl();
MyInvocationJdkProxy myInvocationJdkProxy=new MyInvocationJdkProxy();
//把委托对象cal传入到生成代理类的bind方法中获取代理对象
Cal bind =(Cal) myInvocationJdkProxy.bind(cal);
bind.add(1,1);
bind.sub(2,1);
bind.mul(1,2);
bind.div(2,1);
}
}
总结:上述是利用jdk的动态代理实现的Aop的过程,比较复杂,不容易理解,因此spring框架对Aop进行了封装,使用Spring框架可以实现面向编程的思想来实现Aop。
解决方法:Spring框架中不在需要创建InvocationHandler,只需要创建一个切面对象,将所有的非业务代码在切面对象中完成即可,Spring框架底层会自动根据切面类和委托类(目标类)生成一个代理对象。
Spring框架封装的切面实现Aop切面编程思想
- 接口类以及接口实现类
public interface Cal {
public int add(int num1,int num2);
public int sub(int num1,int num2);
public int mul(int num1,int num2);
public int div(int num1,int num2);
}
package com.entor.jdkproxy;
import org.springframework.stereotype.Component;
@Component//添加注解,把委托实现类交给Ioc去管理,相当于xml中的bean创建
public class CalImpl implements Cal{
@Override
public int add(int num1, int num2) {
//System.out.println("add的方法参数是["+num1+","+num2+"]");
int result=num1+num2;
//System.out.println("add方法的结果是" + result);
return result;
}
@Override
public int sub(int num1, int num2) {
// System.out.println("sub的方法参数是["+num1+","+num2+"]");
int result=num1-num2;
//System.out.println("sub方法的结果是" + result);
return result;
}
@Override
public int mul(int num1, int num2) {
//System.out.println("mul的方法参数是["+num1+","+num2+"]");
int result=num1*num2;
//System.out.println("mul方法的结果是" + result);
return result;
}
@Override
public int div(int num1, int num2) {
//System.out.println("div的方法参数是["+num1+","+num2+"]");
int result=num1/num2;
//System.out.println("div方法的结果是" + result);
return result;
}
}
- spring框架下的自定义切面类
package com.entor.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//这是一个切面类
@Aspect
//加这个注解是使这个类成为一个切面对象
@Component
/**
* 加这个注解就是把该类在springIoc中创建Bean并管理Bean
* id值就是默认的类名小写----Cal proxy =(Cal) context.getBean("calImpl");
* 也可以在该注解中自定义bean的id属性名@Component(value = "test")或者@Component("test")
*/
public class LoggerAspect {
//@Before(value = "execution(public int com.entor.jdkproxy.CalImpl.*(..))")
@Before(value = "execution(* com.entor.jdkproxy.CalImpl.*(..))")
//JoinPoint是切入点,是把委托实现类的所有业务方法连接的总体,*(..)表示[*代表该类下的所有任意方法,括号里面的..表示方法里面的所有参数]
public void before(JoinPoint joinPoint) {
//获取方法名
String name = joinPoint.getSignature().getName();
//获取参数信息
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name + "方法的参数是:" + args);
}
@After(value = "execution(public int com.entor.jdkproxy.CalImpl.*(..))")
//@After(value = "execution(* com.entor.jdkproxy.CalImpl.*(..))")
public void after(JoinPoint joinPoint) {
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法执行完毕");
}
//@AfterReturning(value = "execution(public int com.entor.jdkproxy.CalImpl.*(..))", returning = "result")
@AfterReturning(value = "execution(* com.entor.jdkproxy.CalImpl.*(..))", returning = "result")
/**
* 由于注解@AfterReturning中的returning中的参数值得到的是接口实现类中对应方法的返回结果,
* 这个参数值的属性名必须是与对应的下面的afterReturn中的形参名称一样一一对应关系,这样才能拿到相应的值
* 只要是这两者一一对应即可,这个名称也不必与接口实现类业务方法的返回值名称变量一致
*/
public void afterReturn(JoinPoint joinPoint, Object result) {
//这里的result是通过returning = "result"映射得到的结果对应上获取到的值
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name + "方法的结果是:" + result);
}
/**
* 这个注解表示获取到委托类(目标类)【相当于代理对象在执行】在执行业务逻辑时,出现的异常时进行捕获
* @param joinPoint
* @param exception
*/
@AfterThrowing(value = "execution(public int com.entor.jdkproxy.CalImpl.*(..))",throwing = "exception")
public void afterThrow(JoinPoint joinPoint,Exception exception){
String name = joinPoint.getSignature().getName();
System.out.println(name + "方法的结果是:" + exception);
}
}
LoggerAspect切面类定义处添加的注解解释:
(1)@Aspect:表示该类是切面类
(2)@Component:表示将该类的对象注入到Ioc中去管理
具体方法处添加的注解:
(1)@Before:是前置通知,相当于beforeadvice,在委托方法之前执行;应用场景:如在保存之前进行权限校验,只有管理员身份才有权限保存
(2)@After:最终final通知,不管是否有异常,该通知都会执行
(3)@AfterReturning:后置通知,相当于AfterReturningAdvice,在目标方法之后执行;应用场景:如在删除一条记录时候,记录是谁什么时候删除的
(4)@AfterThrowing:异常抛出通知,相当于ThrowAdvice;只会在目标方法有异常的情况下,才会执行
- xml配置文件
<?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">
<!--自动扫描包,base-package="com.entor"表示改包下的所有类以及子包下的所有类扫描-->
<context:component-scan base-package="com.entor"/>
<!--是Aspect注解生效,为目标类自动生成代理对象-->
<aop:aspectj-autoproxy>
</aop:aspectj-autoproxy>
</beans>
xml配置文件中的注解解释:
(1)<context:component-scan base-package=“com.entor”/>:这个标签是将包名路径为com.entor的所有类进行扫描,如果该类同时添加了@Component注解在类上,则将该类扫描添加到Ioc容器中,即是Ioc来管理它的对象。
(2)aspectj-autoproxy:这个是让Spring框架结合切面类和委托(目标)类自动生成动态代理类。
- 测试类
package com.entor.aop;
import com.entor.jdkproxy.Cal;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
//加载配置文件
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
//获取代理对象
Cal proxy =(Cal) context.getBean("calImpl");
proxy.add(1,2);
proxy.sub(2,1);
proxy.mul(2,2);
proxy.div(2,1);
}
}
总结
- 切面:横切关注点被模块化的抽象对象
- 通知:切面对象完成的工作
- 目标:被通知的对象,即是被横切的对象
- 代理:切面、通知、目标混合之后的对象
- 连接点:通知要插入业务代码的具体位置
- 切点:Aop通过切点定位到连接点