Spring通过注解加载Bean的基本原理浅析(C#版)

这第一篇,同样讲的是spring的IOC功能,是如何实现通过注解来加载Bean文件的。
这是java版本的姐妹篇,C#版本。

文章原创,转载请注明出处www.neohope.org

经过无限精简之后,整体流程为:
1、初始化bean工厂
bean工厂根据配置,加载带有指定attribute的类,并放到了Dictionary中
2、从bean工厂获取一个bean
通过bean的id,实例化一个类,并返回

需要的前置知识为:
1、spring的基本知识
2、反射

然后是源码:
1、TestAttribute.cs
这是个Attribute类,声明了一个新的注解类型,用于表示bean及bean的名字

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestAttribute
{
    [AttributeUsage(AttributeTargets.Class)]
    class TestAttribute : System.Attribute
    {
        public String Ntype { get; set; }
        public String Nname { get; set; }
        public String Nauthor { get; set; }
        public String Nversion { get; set; }
        public String Nmsg { get; set; }
    }
}

2、TestBean.cs
这是个Bean,使用了Attribute作为标识,是用于具体加载的类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestAttribute
{
    [TestAttribute(Nname = "testbean", Nauthor = "neohope", Ntype = "BEAN", Nmsg = "this is just a message", Nversion = "1.0")]
    class TestBean
    {
        public String name;
        public int age;
        public String sex;
    }
}

3、ClassPathScanner.cs
通过指定assembly名,扫描assembly下所有的类型

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TestAttribute
{
    /// <summary>
    /// The class path scanner
    /// </summary>
    class ClassPathScanner
    {
        /// <summary>
        /// Get all assemblies in current domain <see cref="System.Reflection.Assembly"/>.
        /// </summary>
        /// <returns>string set of assembly name</returns>
        public static String[] FindAllAssembliesInCurrentDomain()
        {
            List<String> result = new List<String>();
            foreach (System.Reflection.Assembly assembly in System.AppDomain.CurrentDomain.GetAssemblies())
            {
                result.Add(assembly.GetName().Name);
            }

            return result.ToArray();
        }

        /// <summary>
        /// Get all module in current assembly
        /// </summary>
        /// <returns>string set of module name</returns>
        public static String[] FindAllModuleInThisAssembly()
        {
            List<String> result = new List<String>();
            //Assembly thisAssembly = this.GetType().Assembly;
            Assembly thisAssembly = System.Reflection.Assembly.GetExecutingAssembly();
            foreach (Module item in thisAssembly.GetModules())
            {
                result.Add(item.Name);
            }

            return result.ToArray();
        }


        /// <summary>
        /// Get all type in current assembly
        /// </summary>
        /// <returns>string set of type name</returns>
        public static String[] FindAllTypeInThisAssembly()
        {
            List<String> result = new List<String>();
            //Assembly thisAssembly = this.GetType().Assembly;
            Assembly thisAssembly = System.Reflection.Assembly.GetExecutingAssembly();
            foreach (Type item in thisAssembly.GetTypes())
            {
                result.Add(item.Name); 
            }

            return result.ToArray();
        }

        /// <summary>
        /// Get all type in the assembly
        /// </summary>
        /// <param name="assemblyName">the name of the assembly</param>
        /// <returns>Type set in the assembly</returns>
        public static Type[] FindAllTypeInAssemblyByName(String assemblyName)
        {
            Assembly theAssembly = null;
            foreach (System.Reflection.Assembly assembly in System.AppDomain.CurrentDomain.GetAssemblies())
            {
                if (assembly.GetName().Name.Equals(assemblyName))
                {
                    theAssembly = assembly;
                    break;
                }
            }
            if (theAssembly == null) return new Type[] { };

            List<Type> result = new List<Type>();
            foreach (Type item in theAssembly.GetTypes())
            {
                result.Add(item);
            }

            return result.ToArray();
        }
    }
}

4、ClassAttributeScanner.cs
工厂类,初始化时,通过attribute过滤bean,并将bean的名称及type放到dictionary中
获取实例时,通过bean名称,获取type,并实例化,返回

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TestAttribute
{
    internal class ClassAttributeScanner
    {
        /// <summary>
        /// Dictionary of bean name and bean class
        /// </summary>
        private static Dictionary<String, Type> beanDict = new Dictionary<string, Type>();

        /// <summary>
        ///  Load all bean class with attribute from package
        /// </summary>
        /// <param name="assemblyName">the name of the assembly</param>
        /// <returns></returns>
        protected static void loadBeanTypes(String assemblyName)
        {
            Type[] allTypes = ClassPathScanner.FindAllTypeInAssemblyByName(assemblyName);
            foreach (Type aType in allTypes)
            {
                Attribute attribute  = (TestAttribute)Attribute.GetCustomAttribute(aType, typeof(TestAttribute));
                if (attribute == null) continue;

                TestAttribute ta = (TestAttribute)attribute;
                String beanName = ta.Nname;
                beanDict[beanName] = aType;
            }
        }

        /// <summary>
        /// init the bean factory
        /// </summary>
        /// <param name="assemblyName">the name of the assembly</param>
        /// <returns></returns>
        public static void InitBeanFactory(String assemblyName)
        {
            loadBeanTypes(assemblyName);
        }

        public static Object GetBean(String beanName)
        {
            Type theType = beanDict[beanName];
            if (theType != null)
            {
                //return theType.Assembly.CreateInstance(theType.FullName); 
                return Activator.CreateInstance(theType);
            }
            else
            {
                return null;
            }
        }
    }
}

5、Program.cs
程序入口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestAttribute
{
    class Program
    {
        private static void Main(string[] args)
        {
            //String[] ret = ClassPathScanner.FindAllAssembliesInCurrentDomain();
            //String[] ret = ClassPathScanner.FindAllModuleInThisAssembly();
            //String[] ret = ClassPathScanner.FindAllTypeInThisAssembly();
            //foreach (string s in ret) Console.WriteLine(s);

            ClassAttributeScanner.InitBeanFactory("TestAttribute");
            TestBean bean = (TestBean)ClassAttributeScanner.GetBean("testbean");
            bean.name = "neohope";
        }
    }
}

文章原创,转载请注明出处www.neohope.org

Spring通过注解加载Bean的基本原理浅析(java版)

最近在看spring的源码,想写一系列的文章,来模拟spring的一些最基本功能的实现。
部分源码是从spring中抽取出来的,然后进行了无限的精简,目的是让入门的弟兄们也可以看得很清楚。
首先这第一篇,就是说的spring的IOC功能中,是如何实现通过注解来加载Bean文件的。

文章原创,转载请注明出处www.neohope.org

经过无限精简之后,整体流程为:
1、初始化bean工厂
bean工厂根据配置,加载带有指定annotation的类,并放到了map中
2、从bean工厂获取一个bean
通过bean的id,实例化一个类,并返回

需要的前置知识为:
1、spring的基本知识
2、反射

然后是源码:
1、TestAnnotation.java
这是个注解类,声明了一个新的注解类型,用于表示bean及bean的名字

package com.neohope.annotation.test;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by Hansen on 2016/3/10.
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface TestAnnotation {
    String ntype();
    String nname();
    String nauthor();
    String nversion();
    String nmsg();
}

2、TestBean.java
这是个Bean,使用了注解作为标识,是用于具体加载的类

package com.neohope.annotation.test;

/**
 * Created by Hansen on 2016/3/10.
 */

@TestAnnotation(nauthor = "neohope",nversion="1.0",ntype = "BEAN",nname = "testbean", nmsg = "this is just a message")
public class TestBean {
    public String name;
    public int age;
    public String sex;
}

3、ClassPathScanner.java
从spring中抽取的,类扫描工具类
其作用为,通过指定包名,扫描包名下所有的类
可用于jar文件中类的扫描

package com.neohope.annotation.test;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by Hansen on 2016/3/10.
 */
public class ClassPathScanner {

    /**
     * Get all class name in given packageName
     * @param packageName the package name
     * @return the result as String array
     * @throws IOException in case of I/O errors
     * @see #getAllClassFileInPackage
     */
    public static String[] getAllClassNameInPackage(String packageName) throws IOException, URISyntaxException {
        String[] classFiles = getAllClassFileInPackage(packageName);
        Set<String> result = new LinkedHashSet<String>(classFiles.length);
        for(String clazz : classFiles)
        {
            result.add(clazz.replace(".class", "").replace("/", ".").replace("\\", "."));
        }
        return result.toArray(new String[result.size()]);
    }

    /**
     * Get all class files in given packageName
     * @param packageName the package name
     * @return the result as String array
     * @throws IOException in case of I/O errors
     * @see #findAllClassPathByPackageName
     * @see #loadJarClasses
     * @see #loadFileClasses
     */
    protected static String[] getAllClassFileInPackage(String packageName) throws IOException, URISyntaxException {
        if (packageName.startsWith("/")) {
            packageName = packageName.substring(1);
        }
        if (packageName.endsWith("/")) {
            packageName = packageName.substring(0,packageName.length()-1);
        }

        URL[] rootDirResources = findAllClassPathByPackageName(packageName);
        Set<String> result = new LinkedHashSet<String>(16);
        for (URL rootDirResource : rootDirResources) {
            if (rootDirResource.toString().toUpperCase().startsWith("JAR")) {
                result.addAll(loadJarClasses(rootDirResource));
            }
            else {
                String rootEntryPath = rootDirResource.getPath().replace(packageName,"").replace("/",File.separator).substring(1);
                result.addAll(loadFileClasses(rootDirResource,rootEntryPath));
            }
        }

        return result.toArray(new String[result.size()]);
    }

    /**
     * Find all class path with the given packageName via the ClassLoader.
     * @param packageName the absolute path within the classpath
     * @return the result as URL array
     * @throws IOException in case of I/O errors
     * @see java.lang.ClassLoader#getResources
     */
    protected static URL[] findAllClassPathByPackageName(String packageName) throws IOException {
        ClassLoader cl = ClassPathScanner.class.getClassLoader();
        Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(packageName) : ClassLoader.getSystemResources(packageName));
        Set<URL> result = new LinkedHashSet<URL>();
        while (resourceUrls.hasMoreElements()) {
            URL url = resourceUrls.nextElement();
            result.add(url);
        }
        return result.toArray(new URL[result.size()]);
    }

    /**
     * Get all the class file in given class dir
     * @param rootClassDir the root directory
     * @return all the class in rootClassDir
     * @throws IOException if directory contents could not be retrieved
     */
    protected static Set<String> loadFileClasses(URL rootClassDir, String rootEntryPath) throws IOException, URISyntaxException {
        File rootDir;
        rootDir = new File(rootClassDir.toURI());
        if(!rootDir.exists() || !rootDir.isDirectory() || !rootDir.canRead())
        {
            return Collections.emptySet();
        }

        Set<String> result = new LinkedHashSet<String>(8);
        EnumClassFiles(rootDir, rootEntryPath, result);
        return result;
    }

    /**
     * Recursively get all class files in the given dir
     * @param dir the given directory
     * @param result the Set of class names to add to
     * @throws IOException if directory contents could not be retrieved
     */
    protected static void EnumClassFiles(File dir, String rootEntryPath, Set<String> result) throws IOException {
        File[] dirContents = dir.listFiles();
        if (dirContents == null) {
            return;
        }
        for (File content : dirContents) {
            if (content.isDirectory()) {
                if (!content.canRead()) {
                }
                else {
                    EnumClassFiles(content, rootEntryPath, result);
                }
            }
            else {
                if(content.toString().endsWith(".class")) {
                    result.add(content.toString().replace(rootEntryPath, ""));
                }
            }
        }
    }

    /**
     * Get all the class file in given jar path
     * @param jarPath the given jar path
     * @return the Set of matching Resource instances
     * @throws IOException in case of I/O errors
     */
    protected static Set<String> loadJarClasses(URL jarPath)
            throws IOException {
        JarFile jarFile;
        String jarFileUrl;
        String rootEntryPath = "";
        Set<String> result = new LinkedHashSet<String>(8);

        URLConnection connection = jarPath.openConnection();
        if (connection instanceof JarURLConnection) {
            JarURLConnection jarCon = (JarURLConnection) connection;
            jarFile = jarCon.getJarFile();
            jarFileUrl = jarCon.getJarFileURL().toExternalForm();
            JarEntry jarEntry = jarCon.getJarEntry();
            rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
        } else {
            return Collections.emptySet();
        }

        try {
            for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements(); ) {
                JarEntry entry = entries.nextElement();
                String entryPath = entry.getName();

                if (entryPath.startsWith(rootEntryPath) && entryPath.endsWith(".class")) {
                    result.add(entryPath);
                }
            }

        } catch (Exception ex) {
        }

        return result;
    }

    public static void main(String[] args) throws IOException, URISyntaxException {
        //String[] classes = getAllClassNameInPackage("com/neohope/annotation");
        //String[] classes = getAllClassNameInPackage("org/apache/log4j/config");

        String[] classes = getAllClassNameInPackage("/com/neohope/annotation/");
        //String[] classes = getAllClassNameInPackage("org/apache/log4j/config");
        for(String clazz : classes)
        {
            System.out.println(clazz);
        }
    }
}

4、ClassAnnotationScanner.java
工厂类,初始化时,通过annotation过滤bean,并将bean的名称及class放到map中
获取实例时,通过bean名称,获取class,并实例化,返回

package com.neohope.annotation.test;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;

/**
 * Created by Hansen on 2016/3/10.
 */

public class ClassAnnotationScanner {

    /**
     * HashMap of bean name and bean class
     */
    static HashMap<String, Class> beanMap = new HashMap<String, Class>();

    /**
     * Load all bean class with annotation from package
     * @param packageName the package name
     * @return void
     * @throws IOException in case of I/O errors
     * @throws URISyntaxException in case of URI syntax error
     * @throws ClassNotFoundException in case of class not found
     * @see ClassPathScanner#getAllClassNameInPackage
     */
    protected static void loadBeanClasses(String packageName) throws IOException, URISyntaxException, ClassNotFoundException {
        String[] classes = ClassPathScanner.getAllClassNameInPackage(packageName);
        for(String clazz : classes)
        {
            Class loadedClazz = Class.forName(clazz);
            Object obj  = loadedClazz.getAnnotation(TestAnnotation.class);
            if(obj==null)continue;

            TestAnnotation ta = (TestAnnotation)obj;
            String beanName = ta.nname();
            beanMap.put(beanName,loadedClazz);
        }
    }

    /**
     * init the bean factory
     * @param packageName the package name
     * @return void
     * @throws IOException in case of I/O errors
     * @throws URISyntaxException in case of URI syntax error
     * @throws ClassNotFoundException in case of class not found
     * @see #loadBeanClasses
     */
    public static void initBeanFactory(String packageName) throws IOException, URISyntaxException, ClassNotFoundException {
        loadBeanClasses(packageName);
    }

    /**
     * get bean by bean name
     * @param beanName the bean name
     * @return new object of the bean class
     * @throws IllegalAccessException in case of illegal access
     * @throws InstantiationException in case of instantiation error
     */
    protected static Object getBean(String beanName) throws IllegalAccessException, InstantiationException {
        Class loadedClazz = beanMap.get(beanName);
        if(loadedClazz!=null) {
            return loadedClazz.newInstance();
        }
        else
        {
            return null;
        }
    }

    public static void main(String[] args) throws IOException, URISyntaxException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        initBeanFactory("com/neohope/annotation/");
        TestBean bean = (TestBean)getBean("testbean");
        bean.name = "neohope";
    }

}

文章原创,转载请注明出处www.neohope.org

Spring01事务管理

一、事务传播

事务 说明
@Transactional(propagation=Propagation.REQUIRED) 如果有事务,那么加入事务,没有的话新建一个(默认情况)
@Transactional(propagation=Propagation.NOT_SUPPORTED) 不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY) 必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER) 必须在一个没有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.SUPPORTS) 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务。如果其他bean没有声明事务,那就不用事务

二、事务隔离级别

事务隔离级别 说明
@Transactional(isolation=Isolation.READ_UNCOMMITTED) 读取未提交数据(会出现脏读,不可重复读),基本不使用
@Transactional(isolation=Isolation.READ_COMMITED 读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation=Isolation.REPEATABLE_READ 可重复读(会出现幻读)
@Transactional(isolation=Isolation.SERIALIZABLE 串行化

三、@Transactional的属性

属性 类型 描述
value String 可选的限定描述符,指定使用的事务管理器
propagation enum:Propagation 可选的事务传播行为设置
isolation enum:Isolation 可选的事务隔离级别设置
readOnly boolean 读写或只读事务,默认读写
timeout int(in seconds granularity) 事务超时时间设置
rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组