Java-Bean管理之異步代理方法

Java-Bean管理,實現同步/異步管理

    本文介紹利用Spring的Bean管理,結合自己封裝的ThreadPoolExecutor線程池,利用註解的方式,實現方法的異步管理;

    常用異步方法:不影響系統正常執行、可後臺操作、對數據安全性要求差、等等諸如此類的業務邏輯。如日誌的操作,短信發送、用戶信息的更新等。異步在一定程度上能夠提升系統的響應度,但不合理的設計或是代碼的不合理運用會導致一場災難~

    以下代碼僅供學習參考,實用性、合理性及安全性仍需檢測,也歡迎茫茫人海之中的你能夠給一板磚~

1 環境搭建

    1.1 引入Spring依賴

	<properties>
		<spring.version>4.3.2.RELEASE</spring.version>
	</properties>
	<dependencies>
		<!-- spring support -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</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-core</artifactId>
            <version>${spring.version}</version>
		</dependency>

2.核心實現

    2.1 異步處理器:InvocationHandler

package com.nieli.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.nieli.base.config.Configuration;

/**
 *@描述:
 *@版權: Copyright (c) 2018
 *@作者: nieli
 *@版本: 1.0
 *@創建日期: 2018年01月29日
 *@創建時間: 10:17
 */
public class InvocationHandler<T> implements java.lang.reflect.InvocationHandler
{
    
    private static final Logger                logger                          = LoggerFactory
                                                                                       .getLogger(InvocationHandler.class);
    
    //線程池線程名稱前綴
    public static final String                 POOL_THREAD_NAME_PREFIX         = Configuration.getString(
                                                                                       "proxy.poolThreadNamePrefix",
                                                                                       "Async-Invoke-Thread-");
    
    //異步執行線程池大小 默認:CPU個數 * 2
    private static final int                   ASYNC_THREAD_POOL_CORE          = Configuration
                                                                                       .getInt("proxy.asyncThreadPoolCore",
                                                                                               Runtime.getRuntime()
                                                                                                       .availableProcessors() * 2);
    
    //異步執行線程池隊列大小 默認:10000
    private static final int                   ASYNC_THREAD_POOL_QUEUE_CORE    = Configuration
                                                                                       .getInt("proxy.asyncThreadPoolQueueCore",
                                                                                               10000);
    
    //異步執行線程池拒絕模式 默認:callerRuns 可選:abort(拋出異常) discardOldest(丟棄最老任務) callerRuns(同步執行任務) discard(丟棄任務)
    private static final String                ASYNC_THREAD_POOL_REJECT_POLICY = Configuration.getString(
                                                                                       "proxy.asyncThreadPoolPolicy",
                                                                                       "callerRuns");
    
    //線程池對象
    private static volatile ThreadPoolExecutor threadPoolExecutor;
    
    //代理目標對象
    private T                                  target;
    
    public InvocationHandler(T target)
    {
        super();
        this.target = target;
    }
    
    /**
     *@描述:獲取拒絕策略
     *@作者:nieli
     *@日期:2018/2/1
     *@時間:18:16
     */
    private static RejectedExecutionHandler getRejectedPolicy(String policy)
    {
        if ( "abort".equalsIgnoreCase(policy) )
        {
            return new ThreadPoolExecutor.AbortPolicy();
        }
        else if ( "discardOldest".equalsIgnoreCase(policy) )
        {
            return new ThreadPoolExecutor.DiscardOldestPolicy();
        }
        else if ( "discard".equalsIgnoreCase(policy) )
        {
            return new ThreadPoolExecutor.DiscardPolicy();
        }
        else
        {
            return new ThreadPoolExecutor.CallerRunsPolicy();
        }
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        if ( method.getAnnotation(Async.class) != null )
        {
            if ( logger.isDebugEnabled() )
            {
                logger.debug("執行代理對象[{}]-方法[{}],執行類型:【異步】", target.getClass().getName(), method.getName());
            }
            //異步執行
            execute(target, method, args);
            return null;
        }
        else
        {
            if ( logger.isDebugEnabled() )
            {
                logger.debug("執行代理對象[{}]-方法[{}],執行類型:【同步】", target.getClass().getName(), method.getName());
            }
            //同步執行
            try
            {
                return method.invoke(target, args);
            }
            catch (InvocationTargetException e)
            {
                throw e.getTargetException();
            }
        }
    }
    
    public T getTarget()
    {
        return target;
    }
    
    public void setTarget(T target)
    {
        this.target = target;
    }
    
    private void execute(T target, Method method, Object[] args)
    {
        if ( threadPoolExecutor == null )
        {
            synchronized (InvocationHandler.class)
            {
                if ( threadPoolExecutor == null )
                {
                    threadPoolExecutor = new ThreadPoolExecutor(ASYNC_THREAD_POOL_CORE, ASYNC_THREAD_POOL_CORE, 60L,
                            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(ASYNC_THREAD_POOL_QUEUE_CORE),
                            new AysncInvokeRunnableFactory(), getRejectedPolicy(ASYNC_THREAD_POOL_REJECT_POLICY));
                    logger.info("異步代理執行線程池初始化成功,線程池大小:[{}],隊列大小:[{}].", ASYNC_THREAD_POOL_CORE,
                            ASYNC_THREAD_POOL_QUEUE_CORE);
                }
            }
        }
        
        //提交到線程池
        AsyncInvokeRunnable<T> invokeRunnable = new AsyncInvokeRunnable<T>(target, method, args);
        threadPoolExecutor.execute(invokeRunnable);
    }
    
    /**
     *@描述:線程工廠類
     *@作者:nieli
     *@日期:2018/1/29
     *@時間:16:17
     */
    private static class AysncInvokeRunnableFactory implements ThreadFactory
    {
        
        private static AtomicLong count = new AtomicLong(0);
        
        @Override
        public Thread newThread(Runnable runnable)
        {
            Thread thread = new Thread(runnable);
            thread.setName(POOL_THREAD_NAME_PREFIX + count.addAndGet(1));
            return thread;
        }
    }
    
    /**
     *@描述:具體執行邏輯
     *@作者:nieli
     *@日期:2018/1/29
     *@時間:16:17
     */
    private static class AsyncInvokeRunnable<T> implements Runnable
    {
        
        //代理對象
        private final T        target;
        
        //執行方法
        private final Method   method;
        
        //執行參數
        private final Object[] args;
        
        public AsyncInvokeRunnable(T target, Method method, Object[] args)
        {
            this.target = target;
            this.method = method;
            this.args = args;
        }
        
        @Override
        public void run()
        {
            try
            {
                method.invoke(target, args);
            }
            catch (InvocationTargetException e)
            {
                //獲取詳細錯誤信息,可catch自定義錯誤等
                Throwable throwble = e.getTargetException();
                logger.error("執行代理對象[{}]-方法[{}]失敗.", target.getClass().getName(), method.getName(), throwble);
            }
            catch (Exception e)
            {
                logger.error("執行代理對象[{}]-方法[{}]失敗.", target.getClass().getName(), method.getName(), e);
            }
        }
    }
    
}

    2.2 異步註解:Async

package com.nieli.proxy;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @描述: 異步代理註解類
 * @版權: Copyright (c) 2018
 * @作者: nieli
 * @版本: 1.0
 * @創建日期: 2018年3月12日
 * @創建時間: 下午6:40:54
 */

@Target({ java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async
{
}

    2.3 Bean管理:BeanHelper

package com.nieli.proxy;

import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import com.nieli.base.config.Configuration;

/**
 * @描述: bean託管類
 * @版權: Copyright (c) 2018
 * @公司: 思迪科技
 * @作者: nieli
 * @版本: 1.0
 * @創建日期: 2018年3月12日
 * @創建時間: 下午6:47:22
 */
public final class BeanHelper
{
    
    private static final Logger                  logger                = LoggerFactory.getLogger(BeanHelper.class);
    
    //是否開啓代理
    public static final boolean                  IS_OPEN_PROXY         = Configuration.getInt("proxy.openProxy", 0) == 1;
    
    //是否bean緩存
    public static final boolean                  IS_OPEN_CACHE         = Configuration.getInt("proxy.openCache", 0) == 1;
    
    //代理對象實例cache
    private static final HashMap<String, Object> TARGET_INSTANCE_CACHE = new HashMap<String, Object>();
    
    private static ApplicationContext            ctx;
    
    static
    {
        List<String> locations = null;
        try
        {
            locations = new ArrayList<String>();
            
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = (Resource[]) resolver.getResources("classpath*:/conf/spring/**/*.xml");
            
            URL url = null;
            for (Resource resource : resources)
            {
                try
                {
                    url = resource.getURL();
                    if ( url != null )
                    {
                        //apache的FilenameUtils,按運行環境格式化至標準路徑
                        String path = org.apache.commons.io.FilenameUtils.normalize(url.getPath());
                        int index = path.indexOf(org.apache.commons.io.FilenameUtils.normalize("/conf/spring/"));
                        path = index > 0 ? path.substring(index) : path;
                        locations.add(path);
                    }
                }
                catch (Exception e)
                {
                    logger.error(e.getMessage(), e);
                }
            }
            ctx = new ClassPathXmlApplicationContext((String[]) locations.toArray(new String[0]));
        }
        catch (Exception e)
        {
            logger.error("加載spring配置文件失敗", e);
        }
        finally
        {
            if ( locations != null )
            {
                locations.clear();
                locations = null;
            }
        }
    }
    
    public static <T> T getBean(String beanId, Class<T> clazz)
    {
        T target = null;
        //如果開啓代理
        if ( IS_OPEN_PROXY )
        {
            //如果開啓代理對象緩存,那麼先從緩存對象中找
            if ( IS_OPEN_CACHE && TARGET_INSTANCE_CACHE.containsKey(beanId) )
            {
                target = (T) TARGET_INSTANCE_CACHE.get(beanId);
            }
            //緩存對象中未找到,則獲取對象詳細信息,判斷是否滿足代理配置
            else
            {
                target = getContext().getBean(beanId, clazz);
                /**
                 * 1.接口註解爲異步
                 * 2.對象確認爲接口
                 * 3.spring返回的子對象,其超類確認爲指定的接口
                 */
                if ( clazz.getAnnotation(Async.class) != null && clazz.isInterface()
                        && clazz.isAssignableFrom(target.getClass()) )
                {
                    InvocationHandler<T> invocationHandler = new InvocationHandler<T>(target);
                    target = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },
                            invocationHandler);
                    //將代理實例添加至緩存
                    if ( IS_OPEN_CACHE )
                    {
                        TARGET_INSTANCE_CACHE.put(beanId, target);
                    }
                }
            }
        }
        //未開啓代理,返回spring bean結果
        else
        {
            target = getContext().getBean(beanId, clazz);
        }
        
        if ( logger.isDebugEnabled() )
        {
            logger.debug("bean:[{}]返回的對象爲:[{}]", beanId, target.getClass());
        }
        return target;
    }
    
    /**
     *@描述:獲取Spring上下文對象
     *@作者:nieli
     *@日期:2018/2/2
     *@時間:12:30
     */
    public static ApplicationContext getContext()
    {
        return ctx;
    }
}

3.測試

    3.1 測試接口類

package com.nieli.proxy.service;

import com.nieli.proxy.Async;

/**
 * @描述: 
 * @版權: Copyright (c) 2018
 * @公司: 思迪科技
 * @作者: nieli
 * @版本: 1.0
 * @創建日期: 2018年3月12日
 * @創建時間: 下午6:53:36
 */
//標記爲異步class
@Async
public interface TestService
{
    //異步執行
    @Async
    void login(long v);
    
    //同步執行
    void logout(long v);
}

    3.2 測試實現類

package com.nieli.proxy.service.impl;

import com.nieli.proxy.service.TestService;

/**
 * @描述: 
 * @版權: Copyright (c) 2018
 * @公司: 思迪科技
 * @作者: nieli
 * @版本: 1.0
 * @創建日期: 2018年3月13日
 * @創建時間: 上午9:53:32
 */
public class TestServiceImpl implements TestService
{
    
    @Override
    public void login(long v)
    {
        System.out.println("this is login :" + v);
    }
    
    @Override
    public void logout(long v)
    {
        System.out.println("this is logout :" + v);
    }
    
}

    3.3 Spring配置(配置文件路徑:classpath:/conf/spring/**.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

	<bean id="test" class="com.nieli.proxy.service.impl.TestServiceImpl" />
</beans>

    3.4 單元測試類

package com.nieli.proxy;

import java.util.concurrent.atomic.AtomicLong;

import com.nieli.proxy.service.TestService;

/**
 * @描述: 
 * @版權: Copyright (c) 2018
 * @公司: 思迪科技
 * @作者: nieli
 * @版本: 1.0
 * @創建日期: 2018年3月12日
 * @創建時間: 下午6:53:15
 */
public class Test
{
    public static void main(String[] args) throws InterruptedException
    {
        TestService service = BeanHelper.getBean("test", TestService.class);
        
        service.login(1);
        service.logout(2);
        
        Thread.sleep(2000L);
        System.exit(0);
    }
    
}

    3.5 Console輸出



使用說明:

    定義Service,並通過spring配置文件注入後,將含有異步執行的Bean註解爲異步Bean(@Async)
再將需要異步執行的方法標識爲異步方法,如圖:

    Spring配置文件自動掃描路徑:resource/conf/spring/*.xml

    僅供學習參考,未經實際測試建議勿上生產!程序哪裏存在問題或者值得優化的,歡迎來搞,歡迎拍磚!

其他(非文章展示)代碼說明:

com.nieli.base.config.Configuration    =》    xml配置文件讀取工具類
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章