Spring-AOP切面

在Java软件开发中,使用Spring框架可以轻松实现AOP(面向切面编程)的功能,通过切面增强方法来实现横切关注点的复用和解耦。然而,有些情况下我们可能需要在非Spring环境下进行开发,这时候一个简化开发流程的小工具就能派上用场。本文将介绍一个小工具,它可以在非Spring环境中直接调用Service方法,并触发AOP切面增强方法的执行,以提高开发效率和代码重用性。

在传统的Java开发中,我们通常会使用Service层来封装业务逻辑,而AOP切面则用于实现横切关注点的功能,如日志记录、性能监控等。在Spring框架中,我们可以通过配置和注解来实现AOP的功能。但在非Spring环境中,我们无法直接创建Service的动态代理对象,这时候直接调用Service的方法AOP中定义的切面方面是无法正常执行的。

为了解决这个问题,需要开发一个小工具,使非Spring环境下的开发者也能够直接调用Service方法,并触发AOP切面增强方法的执行。下面是实现这个小工具的步骤:

由于Spring中对类的增强基本都是通过动态代理实现的,为了代码的复用性,可以定义一个抽象类,命名为AbstractSpringProxy,表示创建Spring对应功能的动态代理对象。预留一个给子类实现的抽象方法List<Advisor> getAdvisors(Class<?> beanClass),表示获取需要创建动态代理对象的Bean的Advisor增强器。得到Advisor增强器后,使用Spring内置的ProxyFactory就可以很方便的创建动态代理对象。

public abstract class AbstractSpringProxy {

    protected DefaultListableBeanFactory beanFactory;

    public AbstractSpringProxy() {
        this.beanFactory = new DefaultListableBeanFactory();
    }

    protected abstract List<Advisor> getAdvisors(Class<?> beanClass);

    @SuppressWarnings("unchecked")
    public <T> T getProxy(T bean) {

        List<Advisor> advisors = getAdvisors(bean.getClass());
        if (advisors.isEmpty()) {
            return bean;
        }

        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addAdvisors(advisors);
        proxyFactory.setTargetSource(new SingletonTargetSource(bean));

        return (T) proxyFactory.getProxy(ClassUtils.getDefaultClassLoader());
    }

}

接下来需要再创建一个子类,实现抽象方法List<Advisor> getAdvisors(Class<?> beanClass)。由于AOP切面定义类有可能存在多个,所以还需要定义一个添加AOP切面类的addAdvisorClass(Class<?> clz) 方法,添加并解析AOP切面类中定义的切面方法,每一个切面方法对应一个Advisor增强器。List<Advisor> getAdvisors(Class<?> beanClass)方法中根据AOP切面类中定义的切点,过滤不符合切点表达式定义的Advisor增强器,得到需要应用到Bean的增强器列表eligibleAdvisors。

public class AspectSpringProxy extends AbstractSpringProxy {

    AspectJAdvisorFactory advisorFactory;

    List<Class<?>> aspectClass;

    List<Advisor> candidateAdvisors;

    public AspectSpringProxy() {
        super();
        this.advisorFactory = new ReflectiveAspectJAdvisorFactory(beanFactory);
        this.aspectClass = new ArrayList<>();
        this.candidateAdvisors = new ArrayList<>();
    }

    public void addAdvisorClass(Class<?> clz) {
        String name = clz.getSimpleName();
        if (advisorFactory.isAspect(clz) && !beanFactory.containsBeanDefinition(name)) {
            aspectClass.add(clz);
            RootBeanDefinition beanDefinition = new RootBeanDefinition(clz);
            beanFactory.registerBeanDefinition(name, beanDefinition);
            MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(beanFactory, name);
            candidateAdvisors.addAll(advisorFactory.getAdvisors(factory));
        }
    }

    @Override
    protected List<Advisor> getAdvisors(Class<?> beanClass) {
        List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanClass.getSimpleName());
        AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(eligibleAdvisors);
        if (!eligibleAdvisors.isEmpty()) {
            AnnotationAwareOrderComparator.sort(eligibleAdvisors);
        }
        return eligibleAdvisors;
    }

    @SuppressWarnings("unchecked")
    private List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {

        ThreadLocal<String> currentProxiedBeanName;
        try {
            Field field = ProxyCreationContext.class.getDeclaredField("currentProxiedBeanName");
            field.setAccessible(true);
            currentProxiedBeanName = (ThreadLocal<String>) field.get(null);
        } catch (Exception e) {
            throw new ApplicationException(e);
        }

        currentProxiedBeanName.set(beanName);
        try {
            return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
        }
        finally {
            currentProxiedBeanName.remove();
        }
    }

}

通过以上步骤,我们成功地开发了一个小工具,使非Spring环境下的开发者能够直接调用Service方法,并触发AOP切面增强方法的执行。这对于在非Spring环境下实现横切关注点的复用和解耦非常有帮助。接下来我们来测试一下:

假设我们定义了一个AOP切面类,该切面类拦截com.masterliu.zero.spring.aop.service包下所有以do开头的方法,并打印方法的参数列表:

@Configuration
@EnableAspectJAutoProxy
@Aspect
public class AopConfig {

    @Pointcut("execution(public * com.masterliu.zero.spring.aop.service.*.do*(..))")
    public void pointCut() {}

    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println(""+joinPoint.getSignature().getName()+"运行。。。@Before:参数列表是:{"+ Arrays.asList(args)+"}");
    }

}

在该包下新建一个DomainService,并定义两个方法,其中一个以do开头,另一个不以do开头:

@Service
public class DomainService {

    public void doSomething(String str) {
        System.out.println(str);
    }

    public void executeSomething(String str) {
        System.out.println(str);
    }

}

再简单写一个测试类:

public class SpringProxyTest {

    @Test
    public void testAspectProxy() {

        AspectSpringProxy proxy = new AspectSpringProxy();
        proxy.addAdvisorClass(AopConfig.class);

        DomainService domainService = proxy.getProxy(new DomainService());

        domainService.doSomething("doSomething...");
        domainService.executeSomething("executeSomething...");
    }

}

执行测试方法后,控制台输出如下内容:

doSomething运行。。。@Before:参数列表是:{[doSomething...]}
doSomething...
executeSomething...

可以看到,成功实现了AOP中定义的切面方法功能!

Spring声明式事务

另一个常用功能就是Spring中的声明式事务,使用Spring框架可以轻松实现声明式事务的功能,通过注解或配置的方式来管理事务的边界和属性。同理,在非Spring环境下进行开发,直接调用声明了事务的Service方法,是不可以触发声明式事务的执行的,这时候我们也需要开发一个小工具来达成这一目标。

Spring中声明式事务也是通过动态代理来实现的,与AOP切面类定义的切面方法类似,声明式事务是通过直接定义一个Advisor增强器来管理事务的提交和回滚操作,Advisor增强器定义在ProxyTransactionManagementConfiguration类中。所以我们的小工具需要直接解析ProxyTransactionManagementConfiguration类,获取到其中定义的Advisor增强器。除了Advisor增强器之外,还需要数据源和事务管理器,由于我们没有启动Spring环境,这里需要使用反射的方式直接获取到单例Bean的容器singletonObjects,将构建好的事务管理器直接put进容器中。

public class TransactionSpringProxy extends AbstractSpringProxy {

    List<Advisor> advisors;

    public TransactionSpringProxy(DataSource dataSource) {
        super();
        this.advisors = new ArrayList<>();
        init(dataSource);
    }

    @SuppressWarnings("unchecked")
    private void init(DataSource dataSource) {
        registerBeanDefinition(ConfigurationClassPostProcessor.class);
        registerBeanDefinition(ProxyTransactionManagementConfiguration.class);
        registerBeanDefinition(DataSourceTransactionManager.class);
        ConfigurationClassPostProcessor classPostProcessor = beanFactory.getBean(ConfigurationClassPostProcessor.class);
        classPostProcessor.postProcessBeanDefinitionRegistry(beanFactory);
        String[] advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                this.beanFactory, Advisor.class, true, false);
        for (String advisorName : advisorNames) {
            advisors.add(this.beanFactory.getBean(advisorName, Advisor.class));
        }

        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
        try {
            Field field = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
            field.setAccessible(true);
            Map<String, Object> singletonObjects = (Map<String, Object>) field.get(beanFactory);
            singletonObjects.put(DataSourceTransactionManager.class.getName(), transactionManager);
        } catch (Exception e) {
            throw new ApplicationException(e);
        }
    }

    @Override
    protected List<Advisor> getAdvisors(Class<?> beanClass) {
        return advisors;
    }

    private void registerBeanDefinition(Class<?> clz) {
        RootBeanDefinition def = new RootBeanDefinition(clz);
        beanFactory.registerBeanDefinition(def.getBeanClassName(), def);
    }

}

通过以上步骤,就可以在非Spring环境下触发声明式事务的执行。接下来我们来测试一下:

写一个PersonTxService,并定义两个声明了事务的方法,其中一个方法可以正常执行提交事务,另外一个方法在数据库新增操作后手动触发一个异常来达成事务回滚操作:

@Service
public class PersonTxService {

    PersonMapper personMapper;

    public PersonTxService(PersonMapper personMapper) {
        this.personMapper = personMapper;
    }

    @Transactional
    public void saveRollback(Person person) {
        save(person);
        int i = 1 / 0;
    }

    @Transactional
    public void save(Person person) {
        personMapper.insert(person);
    }

}

在测试类新增一个测试声明式事务的测试方法:

public class SpringProxyTest {

    @Test
    public void testTransactionProxy() {

        Person person = new Person();
        person.setName("赵二麻子");
        person.setAge(18);
        person.setMobile("12311112222");
        person.setIsDeleted(0L);

        MybatisTookit tookit = new MybatisTookit();

        TransactionSpringProxy proxy = new TransactionSpringProxy(tookit.getDataSource());

        PersonTxService personTxService = proxy.getProxy(new PersonTxService(tookit.getMapper(PersonMapper.class)));

        personTxService.save(person);

        person.setId(person.getId() + 1);
        person.setName("赵三麻子");
        personTxService.saveRollback(person);
    }

}

执行测试方法后,发现第一个方法可以正常向数据库插入数据,第二个方法触发了事务的回滚!

文章作者: Hakurei
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Zero
后端 spring Java
喜欢就支持一下吧