什么是AOP
AOP(Aspect Oriented Programming)意为面向切面编程,我们所熟悉的是面向对象编程(OOP),将程序中所有参与模块都抽象成对象,然后通过对象之间的相互调用关系来完成需求。
AOP 是对 OOP 的一个补充,是在另外一个维度上抽象出对象,具体是指程序运行时动态地将非业务代码切入到业务代码中,从而实现代码的解耦合,将非业务代码抽象成一个对象,对该对象进行编程这就是面向切面编程思想。
jdk动态代理实现AOP
创建一个计算器接口 Cal:
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 {
public int add(int num1, int num2) {
int result = num1+num2;
return result;
}
public int sub(int num1, int num2) {
int result = num1-num2;
return result;
}
public int mul(int num1, int num2) {
int result = num1*num2;
return result;
}
public int div(int num1, int num2) {
int result = num1/num2;
return result;
}
}
这是一个很简单的实现简单计算的实现类,现在我们有一个需求,需要在方法计算之前打印日志记录方法信息,方法结束后再记录一次日志打印返回值等信息。
这是一个很简单的需求,我们只需要在方法前后记录日志即可,例如:
@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;
}
但是对于每一个方法,都需要这样去写一下,如果方法很多,就会很麻烦,所以我们用动态代理来实现。
增加一个动态代理类:
public class MyInvocationHandler implements InvocationHandler {
//委托对象
private Object obj = null;
@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.obj, args);
System.out.println(method.getName()+"的结果是:"+result);
return result;
}
//返回代理对象
public Object bind(Object obj){
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
}
}
如上代码,实现了InvocationHandler 接口,并实现了其invoke方法。简单解释一下。
bind方法中,首先将我们让类中的obj指向执行方法的原对象,因为后续代理类中的方法也是需要原对象去执行的,之后调用java中的Proxy类的近代方法去动态的生成一个代理对象。方法参数传入原对象的类加载器去加载代理类和类的所有接口去获取原对象的所有功能。
invoke,代理执行原对象的方法时,其实执行的就是这个invoke方法。我们只需要在invoke方法中去实现我们想要在方法前后所作的事,就可以了。
看下方法调用:
public static void main(String[] args) {
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
CalImpl cal = new CalImpl();
Cal cal2 = (Cal)myInvocationHandler.bind(new CalImpl());
cal2.add(3,4);
}
;
如上,我们先调用bind方法去得到jdk生成的代理类,之后调用代理类的方法,就可以实现我们想要的功能了。注意的事,这个生成的代理类其实是CalImpl的父类,所以如果CalImpl已经有父类的,是不能使用jdk动态代理的。
spring实现AOP
还是上面的原对象,下面用spring来实现AOP。
在spring中,默认就是用jdk的动态代理来实现AOP的,只是spring已经作了一些封装,我们不需要自己去实现代理类了,只需要直接使用即可。先加入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
创建spring的xml文件。加入以下配置。
<!-- 自动扫描 -->
<context:component-scan base-package="com.sensen.springaop"></context:component-scan>
<!-- 是Aspect注解生效,为目标类自动生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
之后给CalImpl加上注解@Component,交给spring管理。然后创建切面类LoggerAspect:
@Before("execution(public int com.sensen.springaop.demo1.CalImpl.*(..))")
public void before(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name+"的参数是:"+args);
}
@After("execution(public int com.sensen.springaop.demo1.CalImpl.*(..))")
public void after(JoinPoint joinPoint){
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法结束");
}
@AfterReturning(value="execution(public int com.sensen.springaop.demo1.CalImpl.*(..))",returning="result")
public void afterReturn(JoinPoint joinPoint,Object result){
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法的结果是"+result);
}
@AfterThrowing(value="execution(public int com.sensen.springaop.demo1.CalImpl.*(..))",throwing="ex")
public void afterThrowing(JoinPoint joinPoint,Exception ex){
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法抛出异常:"+ex);
}
如上,分别在方法执行前,执行后,返回返回值后,抛出异常后,打印了所需日志。测试一下:
//加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//获取代理对象
Cal proxy = (Cal) applicationContext.getBean("calImpl");
proxy.add(10,3);
proxy.sub(10,3);
proxy.mul(10,3);
proxy.div(10,3);
完成我们所需功能。
spring实现AOP默认的也是采取jdk动态代理,注意的是一般都是在对象实现接口而不是继承父类的时候,采取jdk动态代理,因为java是单继承的,而jdk生成的代理类又是原对象的父类。
当原对象是继承父类的时候,spring可以采用cglib代理方式来实现aop,只需要加上如下配置:
<!--设置代理模式为CGlib-->
<aop:config proxy-target-class="true"></aop:config>