Aop (手写AOP)^_^(springAop的实现策略)

讲到java企业级开发框架,就不可避免的讲到 IOC,AOP,MCV 

面试时被问到AOP,讲的很乱,这里整理笔记,包括AOP,spring-AOP的部分知识,错误的地方请小伙伴指出来.

问题:谈谈你对AOP的理解?:

AOP概念(Aspect-Oriented Programming):

即面向切面编程,与OOP(Object - Oriented Programming,面向对象编程)相辅相成,AOP的基本单元为Aspect(切面),Struts2 的拦截器设计就是基于AOP的思想。

AOP 思想的由来:

  • 大型系统中的通用的服务型的代码会穿插在各个业务类,方法中,随着系统规模的增大,会造成大量的代码重复,且与核心代码没有太多的关系。
  • 系统中的业务可分为核心关注点横切关注点,核心关注点是业务处理的主要流程,横切关注点是与核心业务无关的通用业务如日志权限等,各个横切点离散的穿插与核心业务中。导致系统中的每一个模块代码都与这些业务具有很强的依赖性,当需要添加横切功能时,需要大幅修改已有的代码。
  • AOP即解决这个问题,使用AOP框架,能够将这些影响多个类的通用性服务抽取出来,(即做成切面),并通过配置的方式明确在那些位置插入这些服务,系统运行后,AOP框架在指定的时机自动运行这些服务从而达到核心业务逻辑和服务性逻辑分离的目的,减少了重复代码的,提高了系统的可维护性和可扩展性。

AOP常用术语:

  1. 连接点(Joinpoint):连接点是指代码中一些具有边界性质的特定位置,或程序运行时的一些时间点,AOP可以针对连接点配置切面,连接点的类型有很多,如类初始化前,类初始化后,类方法调用前后,抛出异常等,Spring框架的AOP功能只支持针对方法的连接点。
  2. 切入点(Pointcut):即要被增强的连接点。当某个连接点满足预定条件时。AOP框架能够定位到这个连接点,该连接点将被添加增强(Advice),该连接点就变成了一个切入点。Spring AOP 框架中,所有的方法执行都是连接点,通过切入点确定哪些连接点需要被处理。
  3. 增强(Advice)添加特定的连接点上的一段代码程序,增强包含了用于添加到目标连接上的一段代码逻辑,以及用于定位连接点的位置信息,在Spring AOP中提供的增强接口都带有方位名,BeforeAdvice等。
  4. 目标对象(Target):需要添加增强的目标类。即被通知的对象,如果AOP框架使用运行时代理,借助AOP框架,业务类可以只实现核心业务,而日志,事务管理等横切关注点则可以通过AOP框架添加到特定的连接点上,如果没有AOP框架,只能手工编写框架。
  5. 引入(Introduction):特殊的增强,可以为目标类添加自定义属性及方法,即使一个业务类没有实现某个接口,通过AOP框架的引入功能,也可以动态的为该业务类添加接口的实现逻辑,让业务类称为这个接口的实现类。
  6. 织入(Weaving):将增强添加到目标类具体的连接点上的工程,即将切面代码插入到目标对象上,生成代理对象,AOP框架负责将目标类和增强连接在一起,可以实现在编译期(Java 编译器),类装载期(类装载器),动态代理织入,运行期几阶段为目标类添加子类的方式,完成织入过程Spring AOP 框架默认采用动态代理织入,而AspectJ(基于Java语言框架的AOP框架)采用编译织入,和类装载织入。
  7. 代理(Prosxy):目标类被AOP框架织入增强后会产生一个代理类,融入了目标类和增强逻辑根据织入的方式的不同,代理类可能和目标类实现相同的业务接口,也可以直接就是目标类的子类,所以可使用调用目标类的方式来调用该代理类。
  8. 切面(Aspect):切面由切入点增强组成,包括增强逻辑的定义和切入点的定义AOP框架一般负责实施切面,将切面定义的增强逻辑织入到切面所指定的逻辑中。


使用AOP框架时,开发者主要工作为定义切入点和增强,通常采用XML配置文件或者注解的方式,配置好增强的信息和切入点后,AOP框架会自动生成AOP代理。

AOP的实现策略(手写AOP):

在java语言的编写的程序中,从源代码到最终运行,会经历编写源代码,编译出字节码(AspectJ),加载字节码(AspectJ,CGlib),运行程序(JDK动态代理)几个阶段。各个阶段都可以以特定的方式织入增强

JDK 动态代理:

JDK动态代理是java.lang.reflect.*包提供的方式(反射)。必须借助接口才能在运行期生成代理对象,对于使用业务接口的类,Spring默认使用JDK动态代理实现AOP不需要引入第三方包,不能针对类。只要一个类实现了某个接口,就可以通过动态代理机制在运行期动态的构造这个接口的实现对象。

按照JavaSE动态代理的要求:

Object invoke(Object proxy,Method method,Object[] args)throws Exception;
//proxy :自动生成的动态代理对象,与目标对象会实现同一接口;
//method:运行时调用的方法,此方法应为指定接口中定义的方法。
//args:调用method方法被调用时的返回值。
//返回值:代理对象的method方法被调用时的返回值,
  • 需要编写一个类实现java.lang.reflect.InvocationHandler接口。实现java.lang.reflect.InvocationHandler接口时需要重写invoke()方法: 在invoke里进行织入。
public static Object newProxyInstance(
ClassLocader loader,Class<?>[] interfaces,InvocationHandler handler)
throws IllegalArgumentException

//loader:代理类的加载器
//interfaces:代理类的所有接口,这些接口中的方法都会被拦截。
//handler:动态代理的对象。
//返回值:动态生成代理对象,此对象会实现inferface参数中包括的所有接口。
  • 构造上述实现类的实例,然后调用java.lang.Proxy.newProxyInstance()方法获取自动生成的代理对象,完成业务逻辑的方法调用

1.业务接口(动态代理方式必须定义接口

package dynamic.jdk;

public interface TestDao {
public void save();
public void modify();
public void delete();
}

2,.实现类

package dynamic.jdk;

public class TestDaoImpl implements TestDao {

public TestDaoImpl() {
// TODO Auto-generated constructor stub
}

@Override
public void save() {
// TODO Auto-generated method stub
System.out.println("保存");
}

@Override
public void modify() {
// TODO Auto-generated method stub
System.out.println("修改");
}

@Override
public void delete() {
// TODO Auto-generated method stub
System.out.println("删除");
}

}

代理类:

  • invoke()方法构建切面,代用切面方法。通过method.invoke()执行目标类方法,传递目标类实例和方法参数。返回代理类。
  • newProxyInstance()方法获取自动生成的代理对象,供外部调用。
package dynamic.jdk;

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

import aspect.Myspect;

public class JDKDynamicPorxy implements InvocationHandler {
    //声明目标类对象真实对象)
    private TestDao testdao;
    //创建代理对象,建立代理对象和真实对象的代理关系,并返回代理对象。
    public Object createProxy(TestDao testDao) {
        this.testdao=testDao;
        //获取代理类 的构造器
        ClassLoader cld = JDKDynamicPorxy.class.getClassLoader();
        //获取目标类的所有接口
        Class [] clazz = testdao.getClass().getInterfaces();
        //动态生成代理对象,需要代理类的类构造器,目标类所有接口,即目标类实例。
        return Proxy.newProxyInstance(cld, clazz, this);
    }

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        //切面对象
        Myspect myAspect = new Myspect();
        //调用切面方法
        myAspect.check();
        myAspect.except();
        //执行目标类方法(被增强的方法)对应的实例对象的该method 方法。
        Object obj = method.invoke(testdao, args);
        myAspect.log();
        myAspect.monitor();
        return obj;
    }
}
package dynamic.jdk;

public class JDKDynamicTest {

public static void main(String[] args) {
    // TODO Auto-generated method stub
    //实例化代理类
    JDKDynamicPorxy jdkdynamicProxy = new JDKDynamicPorxy();
    //实例化目标类
    TestDao testdao = new TestDaoImpl();
    //生成代理对象
    TestDao testDaoAdvice = (TestDao)jdkdynamicProxy.createProxy(testdao);
    testDaoAdvice.save();
    System.out.println("++++++");
    testDaoAdvice.modify();
    System.out.println("++++++");
    testDaoAdvice.delete();

    }

}

CGLIB动态代理:

JDK动态代理必须要提供接口才可以使用,并且增强的方法只能是接口中申明的方法,对于没有提供接口的类,采用动态字节码的方式,CGLIB为流行的动态字节码生成工具,只能采用CGLIB动态代理。

目标类

package dynamic.cglib;

public class TestDao {
    public void svae() {
		System.out.println("保存");
	}
	public void modify() {
		System.out.println("修改");
	}
	public void delete() {
		System.out.println("删除");
	}
}

构造代理类 

package dynamic.cglib;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import aspect.Myspect;

public class CglibDynamicProxy implements MethodInterceptor{
	/*
	 * 创建代理类的方法,生成CGLIb代理对象。
	 * target是目标对象,需要增强的对象,
	 * 返回目标对象的CGLIB代理对象。
	 */
	public Object createProxy(Object target) {
//创建一个动态类对象,即增强类对象,
		Enhancer enhancer = new Enhancer();
//确定要增强的类,设置其父类,
		enhancer.setSuperclass(target.getClass());
//确定代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor的方法。
		enhancer.setCallback(this);
//返回创建的代理对象。
		return enhancer.create();
	}
	/*
	 * intercept方法会在程序执行目标方法时被调用,
	 * proxy是CGLIB根据指定的父类生成的代理对象。
	 * method是拦截器
	 * args是拦截器方法的参数组
	 * methodProxy是方法的代理对象,用于执行父类的方法
	 * 返回代理结果
	 * (non-Javadoc)
	 * @see org.springframework.cglib.proxy.MethodInterceptor#intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], org.springframework.cglib.proxy.MethodProxy)
	 */

	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// TODO Auto-generated method stub
//创建一个切面
		Myspect myaspect = new Myspect();
		myaspect.check();
		myaspect.except();
		Object obj = methodProxy.invokeSuper(proxy, args);
//后增强
		myaspect.log();
		myaspect.monitor();
		return obj;

	}


}

测试

package dynamic.cglib;

public class CGLIBDynamicTest {

public static void main(String [] args) {
//创建代理对象
CglibDynamicProxy cdb = new CglibDynamicProxy();
//创建目标类对象
TestDao testDao = new TestDao();
//获取增强后的目标对象
TestDao testDaoAdvice = (TestDao)cdb.createProxy(testDao);
//执行方法
testDaoAdvice.svae();
testDaoAdvice.modify();
testDaoAdvice.delete();
}

}

jdk动态代理 vs cglib动态代理的异同:

  • jdk动态代理是以接口为中心的,相当于添加类一种对于被调用者没有太大意义的限制。我们实例化的是代理对象,而不是真正被调用的类型,这在实践中可能带来不便。
  • cglib动态代理采取的方式是创建被代理类的子类。因此要求被代理类不可以用final修饰。

jdk动态代理的优势:

  • 最小化依赖关系
  • 代码实现简单。

cglib代理优势:

  • 不需要被代理类实现额外接口。
  • 只操作我们关心的类,而不必为其他相关类增加工作量。
  • 高性能。

谈谈 Spring Aop吗?对Spring AOP 了解多少?


 Spring框架通过Java SE动态代理和cglib实现了AOP功能当明确指定目标类的实现的业务接口时,Spring使用动态代理的方式,也可以强制使用cglib,没有指定目标类的接口时,Spring使用cglib进行字节码增强

  增强(通知)的类型:根据Spring中通知在目标类中方法中的连接点的位置,分为6种类型。

  • 环绕增强:(org.aopalliance.intercept.MethodInterceptor)是在目标方法执行前和执行后实施增强,可以替换其他的增强,可应用与日志记录,事务处理等功能。
  • 前置增强:(org.springframework.aop.MethodBeforAedvice)在目标方法执行前实施增强,如果增强不抛出异常,那么连接点一定会被执行,应用于权限管理
  • 返回后增强(后置返回通知):(org.springframework.aop.AfterReturningAdvice)在目标方法成功执行后(即没有抛出异常的情况)实施增强,用于关闭流,删除临时文件等功能,
  • 后置增强(后置最终通知):(org.springframework.aop.AfterAdvice)在目标方法执行后增强,即连接点在出现任何情况下都会执行的增强,一般用于释放资源,
  • 异常增强::(org.springframework.aop.ThrowsAdvice)是在方法抛出异常后的增强
  • 引介增强:(org.springframework.aop.IntroducyionIntercetor)一种特殊的增强,能够使目标类实现某个指定的接口,可以添加属性和方法可应用于修改目标类。

基于代理类的AOP实现:

  ProxyFactoryBean:ProxyFactoryBean是org.springframework.beans.factory.FactoryBean接口的实现类,FactoryBean负责实例化一个Bean实例,ProxyFactoryBean负责为其他bean实例创建代理实例。

<!-- 定义目标对象 -->
<bean id = "testDao" class = "dynamic.jdk.TestDaoImpl"/>
<!-- 创建一个切面 -->
<bean id = "myAspect" class = "spring.proxyfactorybean.Myaspect"/>
<!-- 使名用Spring代理工厂定义一个 代理对象-->
<bean id = "testProxy" class = "org.springframework.aop.framework.ProxyFactoryBean">
<!-- 指定代理实现的接口 -->
<property name = "proxyInterfaces" value = "dynamic.jdk.TestDao"/>
<!-- 指定目标对象 -->
<property name = "target" ref = "testDao"/>
<!-- 指定切面 ,织入环绕增强-->
<property name = "interceptorNames" value = "myAspect"/>
<!-- 指定代理方式 -->
<property name = "proxyTargetClass" value = "true"/>
</bean>

ProxyFactoryBean类的常用属性

  1. target 代理的目标对象
  2. proxyInterfaces 代理需要实现的接口列表,如果为多个接口,可以使用一下格式赋值。<list><value></value></list>
  3. interceptorNames 需要织入目标的Advice
  4. proxyTargetClass 是否对类代理而不是接口,默认为false,使用JDK动态代理,当为true时,使用CGLIB动态代理。
  5. singleton 返回的代理是否为单例模式,默认为true。
  6. optimize 当设置为true时强制使用CGLIB动态代理。
package spring.proxyfactorybean;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/*
* 切面类
* 
*/
public class Myaspect implements MethodInterceptor {

public Myaspect() {
// TODO Auto-generated constructor stub
	}

	@Override
	public Object invoke(MethodInvocation arg0) throws Throwable {
// TODO Auto-generated method stub
//增强方法
		check();
		except();
//执行目标方法;
		Object obj = arg0.proceed();
//增强方法
		log();
		moitor();
		return obj;
	}

	private void moitor() {
// TODO Auto-generated method stub
		System.out.println("性能检测!!");
	}

	private void log() {
// TODO Auto-generated method stub
		System.out.println("模拟日志记录!!");
	}

	private void except() {
// TODO Auto-generated method stub
		System.out.println("模拟异常处理");
	}

	private void check() {
// TODO Auto-generated method stub
		System.out.println("模拟权限控制!!");
	}

}
package spring.proxyfactorybean;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import dynamic.jdk.TestDao;

public class ProxyFactoryBeanTest {

	public ProxyFactoryBeanTest() {
// TODO Auto-generated constructor stub
	}
	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext12.xml");
//从容器中获得增强后的目标对象
		System.out.println(applicationContext.getBean("testProxy").getClass().getName());
		TestDao testdaoAdvice =(TestDao)applicationContext.getBean("testProxy");
//执行方法
		testdaoAdvice.save();
		System.out.println("++++++++++++++++++++");
		testdaoAdvice.modify();
		System.out.println("++++++++++++++++++++");
		testdaoAdvice.delete();
	}
}

基于AspectJ配置开发AOP

AspectJ是一个基于Java语言的AOP框架,从Spring2.0后引入AspectJ的支持,建议使用AspectJ框架实现SpringAOP,有两种方式,

  • 一是基于XML配置开发AspectJ。
  • 二是基于注解开发AspectJ。

基于XML文件配置开发AspectJ

通过Xml文件定义切面,切入点及通知,所有的定义都在<aop:config>元素及其子元素中。

Spring配置文件中的<beans>元素可以包含多个<aop:config>,一个<aop:config>元素又可以包含属性和子元素,子元素包括<aop:pointcut>、<aop:advisor>、<aop:aspect>,配置时,这3个子元素必须按照此顺序定义。
        1.1配置切面
         在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素可以将定义好的Spring Bean转换成切面Bean,所以先定义一个普通的Spring Bean。配置<aop:aspect>元素时,通常会指定id和ref两个属性。

   id用来定义该切面的唯一标识名称。  ref用于引用普通的Spring Bean。
       1.2配置切入点
         当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当<aop:pointcut>元素作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效。
         定义<aop:pointcut>元素时,通常会指定id和expression两个属性,id用于指定切入点的唯一标识名称。 expression用于指定切入点关联的切入点表达式。

1.3配置通知
        使用<aop:aspect>的子元素可以配置5种常用通知,这5个子元素不支持使用子元素,但在使用时可以指定一些属性,其常用属性及其描述如下:

  •     属性名称解释
    1. pointcut该属性用于指定一个切入点表达式,Spring将在匹配该表达式的连接点时织入该通知。
    2. pointcut-ref该属性指定一个已经存在的切入点名称,通常pointcut和pointcut-ref两个属性只需要使用其中之一。
    3. method该属性指定一个方法名,指定将切面Bean中的该方法转换为增强处理。
    4. throwing该属性只对<after-throwing>元素有效,用于指定一个形参名,异常通知方法可以通过该形参访问目标方法所抛出的异常。
    5. returning该属性只对<after-returning>元素有效,用于指定一个形参名,后置通知方法可以通过该形参访问目标方法的返回值
<?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: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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 定义目标类对象 -->
<bean id = "testDao" class = "dynamic.jdk.TestDaoImpl"/>
<!-- 配置切面 -->
<bean id = "myaspect" class = "aspect.xml.MyAspect"/>
<!-- AOP配置 -->
<aop:config>
<!-- 配置切面 -->
    <aop:aspect ref = "myaspect">
    <!-- 配置切入点,通知增强那些方法 -->
    <aop:pointcut expression = "execution(* dynamic.jdk.*.*(..))" id = "myPointCut"/>
    <!-- 将通知与切入点关联 -->
    <!-- 关联前置切入点 -->
    <aop:before method = "before" pointcut-ref = "myPointCut"/>
    <!-- 关联后置增强点 -->
    <aop:after-returning method  ="afterReturning" pointcut-ref = "myPointCut"/>
    <!-- 关联环绕增强点 -->
    <aop:around method = "around" pointcut-ref = "myPointCut"/>
    <!-- 关联异常通知 -->
    <aop:after-throwing method =  "except" pointcut-ref = "myPointCut"/>
    <!-- 关联后置(最终)通知,不管目标方法是否执行都要执行 -->
    <aop:after method  ="after" pointcut-ref = "myPointCut"/>
    </aop:aspect>
    
</aop:config>
 
</beans>

基于注解开发AspectJ:

  • 基于注解开发AspectJ要比基于XML配置开发AspectJ方便,

注解名称解释

  •     @Aspect:用来定义一个切面。
  •     @pointcut:用于定义切入点表达式。在使用时还需要定义一个包含名字和任意参数的方法签名来表示切入点名称,这个方法签名就是一个返回值为void,且方法体为空的普通方法。
  •     @Before:用于定义前置通知,相当于BeforeAdvice。在使用时,通常需要指定一个value属性值,该属性值用于指定一个切入点表达式(可以是已有的切入点,也可以直接定义切入点表达式)。
  •     @AfterReturning:用于定义后置通知,相当于AfterReturningAdvice。在使用时可以指定pointcut / value和returning属性,其中pointcut / value这两个属性的作用一样,都用于指定切入点表达式。returning属性值用于表示Advice方法中定义与此同名的形参,该形参可用于访问目标方法的返回值。
  •     @Around:用于定义环绕通知,相当于MethodInterceptor。在使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。
  •     @After-Throwing:用于定义异常通知来处理程序中未处理的异常,相当于ThrowAdvice。在使用时可指定pointcut / value和throwing属性。其中pointcut/value用于指定切入点表达式,而throwing属性值用于指定-一个形参名来表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。
  •     @After:用于定义最终final 通知,不管是否异常,该通知都会执行。使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。
  •     @DeclareParents:用于定义引介通知,相当于IntroductionInterceptor (不要求掌握)。
  •     @Repository:用于定义目标类为目标对象,@Repository("Spring Bean ")
@Aspect  //对应<aop:aspect ref="myAspect">
@Component  //对应<bean id="myAspect" class="">
public class MyAspect {
 
    /**
     * 定义切入点  @Pointcut 相当于  <aop:pointcut id="myPointCut" expression="execution(* edu.njxz.demo.dynamicProxy.*.*(..))"></aop:pointcut>
     */
    @Pointcut("execution(* edu.njxz.demo.dynamicProxy.*.*(..))")
    private void myPointCut(){
 
    }
    /**
     * 前置通知,使用JoinPoint接口作为参数获得目标对象信息
     * @param jp
     */
    @Before("myPointCut()")
    public void before(JoinPoint jp){
        System.out.print("前置通知:模拟权限控制");
        System.out.println(",目标对象:"+jp.getTarget()+" ,被增强处理的方法:"+jp.getSignature().getName());
    }
 
    /**
     * 后置返回通知
     * @param jp
     */
    @AfterReturning("myPointCut()")
    public void afterReturning(JoinPoint jp){
        System.out.print("后置返回通知:模拟删除临时文件");
        System.out.println(" ,被增强处理的方法:"+jp.getSignature().getName());
    }
 
    /**
     * 环绕通知
     * @param pjp
     * @return
     * @throws Throwable
     * 必须throws Throwable
     */
    @Around("myPointCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        //开始
        System.out.println("环绕开始:执行目标方法前,模拟开启事务");
        //执行当前目标方法
        Object obj=pjp.proceed();
        //结束
        System.out.println("环绕结束:执行目标方法后,模拟关闭事务");
 
        return obj;
    }
 
    /**
     * 异常通知
     *
     */
 
    @AfterThrowing(value = "myPointCut()",throwing = "e")
    public void except(Throwable e){
        System.out.println("异常通知:"+"程序执行异常"+e.getMessage());
    }
 
    /**
     * 后置通知,(最终通知)
     */
    @After("myPointCut()")
    public void after(){
        System.out.println("最终通知:模拟释放资源");
    }
}
<?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="edu.njxz.demo.aspectAnnotation"></context:component-scan>
    <context:component-scan base-package="edu.njxz.demo.dynamicProxy"></context:component-scan>
    <!-- 启动基于注解的AspectJ支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
package com.liruilong.fileserver.common.aop;


import java.lang.annotation.*;


/** 
 * TODO Declare a log annotation as the entry point for AOP
 * @author Liruilong
 * @Date: 2020/7/29 14:38 
 * @Description: 声明一个日志注解作为AOP的切入点
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodsLog {
     FileCRUDEnum operType() default FileCRUDEnum.OPERATION;
     String remark() default "备注";
     String paramData() default "参数";
}

springboot中AOP实例

实现以注解的方式实现日志功能。

这里的切人点就是注解类似于spring的以注解形式的事务,

定义注解,日志项枚举:

package com.liruilong.fileserver.common.aop;


import java.lang.annotation.*;


/**
 * TODO Declare a log annotation as the entry point for AOP
 * @author Liruilong
 * @Date: 2020/7/29 14:38
 * @Description: 声明一个日志注解作为AOP的切入点
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodsLog {
     FileCRUDEnum operType() default FileCRUDEnum.OPERATION;
     String remark() default "备注";
     String paramData() default "参数";
}
package com.liruilong.fileserver.common.aop;

import java.util.Arrays;

/**
 * @Auther: Liruilong
 * @Date: 2020/7/29 17:31
 * @Description:
 */
public enum FileCRUDEnum {
    CREATE("创建",100),
    QUERY("查询",200),
    UPDATE("更新",300),
    DELETE("删除",400),
    OPERATION("操作",500);

    private String name;
    private Integer id;


    FileCRUDEnum(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    /**
     * @param id
     * @return java.lang.String
     * @Description  根据Id获取枚举值对应name
     * @Author Liruilong
     * @Date 2020年07月29日  17:07:07
     **/
    public static String valueof(Integer id) {
      return   Arrays.stream(FileCRUDEnum.values())
              .filter(o ->id.equals(o.getId()))
              .findAny()
              .orElse(null).name;
    }
    /**
     * @param id
     * @return com.hhwy.epes.common.aop.FileCRUDEnum
     * @Description  根据Id获取枚举值
     * @Author Liruilong
     * @Date 2020年07月29日  17:07:47
     **/
    public static FileCRUDEnum valueEnum(Integer id) {
        return   Arrays.stream(FileCRUDEnum.values())
                .filter(o ->id.equals(o.getId()))
                .findAny()
                .orElse(null);
    }




}

定义切入点、增强方式、切面,

package com.liruilong.fileserver.common.aop;


import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.liruilong.fileserver.common.util.DateUtil;
import com.liruilong.fileserver.common.util.IpUtil;
import com.liruilong.fileserver.common.util.ThreadUtil.ThreadFactoryUtil;
import com.liruilong.fileserver.common.util.UUIDUtil;
import com.liruilong.fileserver.fserve.domain.OperateLog;
import com.liruilong.fileserver.fserve.service.OperateLogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.*;

/**
 * @Auther: Liruilong
 * @Date: 2020/7/29 14:33
 * @Description:
 */
@Aspect
@Component
public class FileLogAspect {

    private Logger logger4j = LoggerFactory.getLogger(FileLogAspect.class);

    private static ExecutorService threadPool = new ThreadPoolExecutor(1, 1, 0L,
            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), ThreadFactoryUtil.builder("Log"), new ThreadPoolExecutor.AbortPolicy());


    @Autowired
    private OperateLogService operateLogService;

    @Pointcut("@annotation(com.liruilong.fileserver.common.aop.MethodsLog)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        threadPool.execute(() -> handleLog(joinPoint, "--->[begin!]", 0));
        Object obj = joinPoint.proceed();
        threadPool.execute(() -> handleLog(joinPoint, "--->[finish!]", 0));
        return obj;
    }


    @AfterThrowing(value = "pointCut()", throwing = "errorMsg")
    public void afterThrowException(JoinPoint joinpoint, Exception errorMsg) {
        threadPool.execute(() -> handleLog(joinpoint, "--->[Exception!]", 1));
    }


    /**
     * @param joinPoint
     * @param joinPoint
     * @return void
     * @Description 切入点处理器
     * @Author Liruilong
     * @Date 2020年07月29日  15:07:24
     **/
    private void handleLog(JoinPoint joinPoint, Object... o) {
        MethodsLog loggerAnnotation = giveController(joinPoint);
        if (loggerAnnotation == null) {
            return;
        }
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = null;
        String ipAddr = null;
        if (null != attributes) {
            request = attributes.getRequest();
            ipAddr = IpUtil.getIpAddr(request);
        } else {
            ipAddr = "未知";
        }
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getName();
        OperateLog operateLog = new OperateLog().setId(UUIDUtil.builder()).setModule(loggerAnnotation.remark())
                .setRemark(o[0] + ":" + Arrays.toString(joinPoint.getArgs()))
                .setClassName(className).setMethod(methodName).setIp(ipAddr).setOperType(loggerAnnotation.operType().getName())
                .setCreateTime(DateUtil.builder()).setUserId("123").setUserName("小明").setStatus((Integer) (o[1]));
        operateLog.toString();
        operateLogService.saveOperateLog(operateLog);

    }


    /**
     * <p> 由切入点对象得到注解对象<p/>
     *
     * @param joinPoint
     * @return
     */
    private static MethodsLog giveController(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        return method != null ? method.getAnnotation(MethodsLog.class) : null;
    }


    /**
     * <p>由类名得到一个元类<p/>
     *
     * @param classFindInterface
     * @param className
     * @return
     */
    public Class classFind(ClassFindInterface classFindInterface, String className) {
        Class<?> clazz = null;
        try {
            clazz = classFindInterface.classNametoClass(className);
        } catch (ClassNotFoundException e) {
            logger4j.error("˙·...·˙`˙·....·…┉═∞═…┉ ═∞═┈━═┈━═┈━═┈━═┈━═☆☆☆☆、" + e.getMessage() + "☆☆☆☆☆☆☆☆☆");
            e.printStackTrace();
        }
        return clazz;

    }

        /* @After("pointCut()")
    public void doAfter(JoinPoint joinPoint) {
        logger4j.warn("˙·...·˙`˙·....·…┉═∞═…┉ ═∞═┈━═┈━═┈━═┈━═┈━═☆、后置返回通知—-┈━═┈━═┈━═");
    }*/

   /* @Around("pointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{
        logger4j.warn("˙·...·˙`˙·....·…┉═∞═…┉ ═∞═┈━═┈━═┈━═┈━═┈━═☆、环绕前通知—-┈━═┈━═┈━═");
        Object obj= joinPoint.proceed();
        logger4j.warn("˙·...·˙`˙·....·…┉═∞═…┉ ═∞═┈━═┈━═┈━═┈━═┈━═☆、环绕后通知—-┈━═┈━═┈━═");
        return  obj;
    }*/
  /* @Before("pointCut()")
   public void doBefore(JoinPoint joinPoint) {
       threadPool.execute(() -> handleLog(joinPoint, "--->[begin!]", 0));
   }

    @AfterReturning("pointCut()")
    public void doAfterReturning(JoinPoint joinPoint) {
        threadPool.execute(() -> handleLog(joinPoint, "--->[finish!]", 0));
    }
*/



}

业务层使用

   /**
     * <per>
     * <p>构建文件目录</p>
     * <per/>
     * @param fileDO
     * @return java.util.List
     * @throws
     * @Description : TODO Build file path
     * @author Liruilong
     * @Date 2020/9/15 14:02
     **/
    @Override
    @Transactional
    @MethodsLog(operType = FileCRUDEnum.CREATE,remark = "构建文件夹",paramData = "fileDO")
    public List<FileInfoDO> mkdir(final FileDO fileDO) {
        String pathname = fileDO.getPathName();
        // 参数为空校验
        checkoutNull(pathname);
        // 参数格式校验
        // 目录分片,判断根目录是否存在。
        String[] dirNameArr = pathname.split(FileContants.SPLIT_SIGN);
        int size = dirNameArr.length;
        List<FileInfoDO> arrayList = new ArrayList<>(size);
        StringBuilder path = new StringBuilder(FileContants.SPLIT_TOKEN_SIGN);
        List pathList = new ArrayList();
        List nameList = new ArrayList();
        // 构建的时候需要一层层构建,有父层到子层。
        for (int i = 0; i < size; i++) {
            if (StringUtils.isBlank(dirNameArr[i])){
                continue;
            }
            FileInfoDO fileInfoDO = new  FileInfoDO().setAbstractFileId(UUIDUtil.builder())
                    .setFileName(dirNameArr[i]).setVersion_(FileContants.VSERSION_1)
                    .setContentType(FileContants.FILE_TYPE_DIR).setPath(path.toString());
            path.append(dirNameArr[i]).append(FileContants.SPLIT_SIGN);
            arrayList.add(0,fileInfoDO);
            pathList.add(fileInfoDO.getPath());
            nameList.add(fileInfoDO.getFileName());
        }
        // 查询条件构建
        Query query = new Query();
        query.addCriteria(where("path").in(pathList).and("fileName").in(nameList).and("lock_state").is(FileContants.LOCK_STATE));
        // 判断目录是否存在锁定
        List resList = mongoTemplate.find(query, FileInfoDO.class);
        if (resList.size()> 0){
            Iterator iterator = resList.iterator();
            while (iterator.hasNext()){
                FileInfoDO fileInfoDO = (FileInfoDO) iterator.next();
                if(path.toString().startsWith(fileInfoDO.getPath()+fileInfoDO.getFileName()+FileContants.SPLIT_SIGN)){
                    throw new FileServiceException("目录下有锁不能创建新目录!",ErrorCode.Lock.getCode());
                }
            }
        }
        mongoTemplate.insertAll(arrayList);

        if (!arrayList.isEmpty()){
            FileInfoDO fileInfoDO = arrayList.get(0);
            FileInfoDO oldFileInfoDO = dirDetail(fileInfoDO.getPath()+ fileInfoDO.getFileName()+ FileContants.SPLIT_SIGN,null);
            if (Objects.nonNull(oldFileInfoDO)){
                fileInfoDO.setAbstractFileId(oldFileInfoDO.getAbstractFileId());
            }
        }
        return arrayList;
    }

猜你喜欢

转载自blog.csdn.net/sanhewuyang/article/details/108943497
AOP