基於Spring註解實現的工廠模式
摘要:
工廠模式是大家熟知的一種設計模式,在spring BeanFactory將這模式運用自如。 前面講過如果通過xml配置的方式實現,今天我們來講講如何通過註解的方式實現工廠模式。 主要思路 1. 掃描classPath下的的類,將這些class儲存到setmap中 2. 遍歷set中的class,找出被自定義facory註解註解過的的class,以beanId,class的物件形式封裝到一個map集合裡 3. 暴露一個方法getBean,通過beanId查詢對於的class的物件,匹配成功後返回該物件
[2017-5-16
by Daniel] 今天發現了更簡單的方法,請見本人的這篇文章《
工廠模式是大家熟知的一種設計模式,在spring BeanFactory將這模式運用自如。 前面講過如果通過xml配置的方式實現,今天我們來講講如何通過註解的方式實現工廠模式。 主要思路
- 掃描classPath下的的類,將這些class儲存到setmap中
- 遍歷set中的class,找出被自定義facory註解註解過的的class,以beanId,class的物件形式封裝到一個map集合裡
- 暴露一個方法getBean,通過beanId查詢對於的class的物件,匹配成功後返回該物件
同樣回顧下常見的簡單工廠寫法
建立一個介面類Pizza
public interface Pizza{
publicfloatgetPrice();
}
MargheritaPizza 類
public classMargheritaPizzaimplementsPizza{
publicfloatgetPrice(){
System.out.println("8.5f");
return 8.5f;
}
}
CalzonePizza 類
public classCalzonePizzaimplementsPizza{
publicfloatgetPrice(){
System.out.println("2.5f" );
return 2.5f;
}
}
建立工廠類PizzaFactory
通過傳入引數id,選擇不同的例項類,如果後續不斷的增加新類,會頻繁的修改create方法,不符合開閉原則
public interfacePizza{
publicfloatgetPrice();
}
MargheritaPizza 類
public classMargheritaPizzaimplementsPizza{
publicfloatgetPrice(){
System.out.println("8.5f");
return 8.5f;
}
}
CalzonePizza 類
public classCalzonePizzaimplementsPizza{
publicfloatgetPrice(){
System.out.println("2.5f");
return 2.5f;
}
}
建立工廠類PizzaFactory
通過傳入引數id,選擇不同的例項類,如果後續不斷的增加新類,會頻繁的修改create方法,不符合開閉原則
public classPizzaFactory{
public Pizza create(String id){
if (id == null) {
throw new IllegalArgumentException("id is null!");
}
if ("Calzone".equals(id)) {
return new CalzonePizza();
}
if ("Margherita".equals(id)) {
return new MargheritaPizza();
}
throw new IllegalArgumentException("Unknown id = " + id);
}
}
使用annotation註解方式
註解方式減少對程式碼的侵入,避免xml配置的繁瑣,是spring高版喜歡使用的方式
建立ClassPathSpringScanner 掃描類
獲取當前classLoad下的所有class檔案
public class ClassPathSpringScanner {
public static final String CLASS_SUFFIX = ".class";
private ClassLoader classLoader = getClass().getClassLoader();
public Set<Class<?>> getClassFile(String packageName) throws IOException {
Map<String, String> classMap = new HashMap<>(32);
String path = packageName.replace(".", "/");
/**
* 通過classLoader載入檔案,迴圈遍歷檔案,轉換class檔案
*/
Enumeration<URL> urls = classLoader.getResources(path);
while (urls!=null && urls.hasMoreElements()) {
URL url = urls.nextElement();
String protocol = url.getProtocol();
/**
* 如果是檔案
*/
if ("file".equals(protocol)) {
String file = URLDecoder.decode(url.getFile(), "UTF-8");
File dir = new File(file);
if(dir.isDirectory()){
parseClassFile(dir, classMap);
}else {
throw new IllegalArgumentException("file must be directory");
}
}
}
Set<Class<?>> set = new HashSet<>(classMap.size());
for(String key : classMap.keySet()){
String className = classMap.get(key);
try {
set.add(getClass().forName(className));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return set;
}
/**
* 遞迴演算法把class封裝到map集合裡
* @param dir
* @param packageName
* @param classMap
*/
protected void parseClassFile(File dir, Map<String, String> classMap){
if(dir.isDirectory()){
File[] files = dir.listFiles();
for (File file : files) {
parseClassFile(file, classMap);
}
} else if(dir.getName().endsWith(CLASS_SUFFIX)) {
String name = dir.getPath();
name = name.substring(name.indexOf("classes")+8).replace("\\", ".");
addToClassMap(name, classMap);
}
}
private boolean addToClassMap(String name, Map<String, String> classMap){
if(!classMap.containsKey(name)){
classMap.put(name, name.substring(0, name.length()-6)); //去掉.class
}
return true;
}
只要被Factory註解過的類,都能通過beanId例項化物件。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Factory {
/**
* 用來表示物件的唯一id
*/
String id();
}
建立 BeanFactory 介面
public interface BeanFactory {
public Object getBean(String id);
}
建立BeanFactory 的實現類AnnApplicationContext
將掃描後得到的class封裝到一個map裡,找出有被Factory註解的類,以beanId,class物件的鍵值對形式儲存。
public class AnnApplicationContext implements BeanFactory{
private Map<String, Object> factoryClasses = new LinkedHashMap<String, Object>();
private Set<Class<?>> classSet = new HashSet();
ClassPathSpringScanner scanner = new ClassPathSpringScanner();
/*
* 建構函式初始化掃描獲取所有類
*/
public AnnApplicationContext(String packageName) {
try {
//掃描classPath下的所有類,並返回set
classSet = scanner.getClassFile(packageName);
/**
* 遍歷所有類,找出有factory註解的類,並封裝到linkedHashMap裡
*/
for (Class<?> cls : classSet){
Factory factory = (Factory) cls.getAnnotation(Factory.class);
if(factory != null)
try {
factoryClasses.put(factory.id(), cls.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 輸入的id,對應到factoryGroupedClasses的關係,例項化工廠物件
* @param beanId
* @return
*/
@Override
public Object getBean(String id) {
return factoryClasses.get(id);
}
MargheritaPizza 類
添加註釋Factory,定義beanId:Margherita
@Factory(id = "margherita")
public classMargheritaPizzaimplementsPizza{
publicfloatgetPrice(){
System.out.println("8.5f");
return 8.5f;
}
}
CalzonePizza 類
添加註釋Factory,定義beanId:Calzone
@Factory(id = "calzone")
public classCalzonePizzaimplementsPizza{
publicfloatgetPrice(){
System.out.println("2.5f");
return 2.5f;
}
}
測試下
publicstaticvoidmain(String[] args){
/**
* 掃描com.annotation.factory下的類
*/
AnnApplicationContext factoryProcessor = new AnnApplicationContext("com.annotation.factory.spring");
Pizza p= (Pizza) factoryProcessor.getBean("Calzone");
p.getPrice();
}
好了,看完程式碼應該很清楚了,註解是不是給我們帶來很多方便了。 留個思考題,如何預設通過類的名字,首個字母小寫來作為beanId
public interfacePizza{
publicfloatgetPrice();
}
MargheritaPizza 類
public classMargheritaPizzaimplementsPizza{
publicfloatgetPrice(){
System.out.println("8.5f");
return 8.5f;
}
}
CalzonePizza 類
public classCalzonePizzaimplementsPizza{
publicfloatgetPrice(){
System.out.println("2.5f");
return 2.5f;
}
}
建立工廠類PizzaFactory
通過傳入引數id,選擇不同的例項類,如果後續不斷的增加新類,會頻繁的修改create方法,不符合開閉原則
public classPizzaFactory{
public Pizza create(String id){
if (id == null) {
throw new IllegalArgumentException("id is null!");
}
if ("Calzone".equals(id)) {
return new CalzonePizza();
}
if ("Margherita".equals(id)) {
return new MargheritaPizza();
}
throw new IllegalArgumentException("Unknown id = " + id);
}
}
使用annotation註解方式
註解方式減少對程式碼的侵入,避免xml配置的繁瑣,是spring高版喜歡使用的方式
建立ClassPathSpringScanner 掃描類
獲取當前classLoad下的所有class檔案
public class ClassPathSpringScanner {
public static final String CLASS_SUFFIX = ".class";
private ClassLoader classLoader = getClass().getClassLoader();
public Set<Class<?>> getClassFile(String packageName) throws IOException {
Map<String, String> classMap = new HashMap<>(32);
String path = packageName.replace(".", "/");
/**
* 通過classLoader載入檔案,迴圈遍歷檔案,轉換class檔案
*/
Enumeration<URL> urls = classLoader.getResources(path);
while (urls!=null && urls.hasMoreElements()) {
URL url = urls.nextElement();
String protocol = url.getProtocol();
/**
* 如果是檔案
*/
if ("file".equals(protocol)) {
String file = URLDecoder.decode(url.getFile(), "UTF-8");
File dir = new File(file);
if(dir.isDirectory()){
parseClassFile(dir, classMap);
}else {
throw new IllegalArgumentException("file must be directory");
}
}
}
Set<Class<?>> set = new HashSet<>(classMap.size());
for(String key : classMap.keySet()){
String className = classMap.get(key);
try {
set.add(getClass().forName(className));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return set;
}
/**
* 遞迴演算法把class封裝到map集合裡
* @param dir
* @param packageName
* @param classMap
*/
protected void parseClassFile(File dir, Map<String, String> classMap){
if(dir.isDirectory()){
File[] files = dir.listFiles();
for (File file : files) {
parseClassFile(file, classMap);
}
} else if(dir.getName().endsWith(CLASS_SUFFIX)) {
String name = dir.getPath();
name = name.substring(name.indexOf("classes")+8).replace("\\", ".");
addToClassMap(name, classMap);
}
}
private boolean addToClassMap(String name, Map<String, String> classMap){
if(!classMap.containsKey(name)){
classMap.put(name, name.substring(0, name.length()-6)); //去掉.class
}
return true;
}
只要被Factory註解過的類,都能通過beanId例項化物件。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Factory {
/**
* 用來表示物件的唯一id
*/
String id();
}
建立 BeanFactory 介面
public interface BeanFactory {
public Object getBean(String id);
}
建立BeanFactory 的實現類AnnApplicationContext
將掃描後得到的class封裝到一個map裡,找出有被Factory註解的類,以beanId,class物件的鍵值對形式儲存。
public class AnnApplicationContext implements BeanFactory{
private Map<String, Object> factoryClasses = new LinkedHashMap<String, Object>();
private Set<Class<?>> classSet = new HashSet();
ClassPathSpringScanner scanner = new ClassPathSpringScanner();
/*
* 建構函式初始化掃描獲取所有類
*/
public AnnApplicationContext(String packageName) {
try {
//掃描classPath下的所有類,並返回set
classSet = scanner.getClassFile(packageName);
/**
* 遍歷所有類,找出有factory註解的類,並封裝到linkedHashMap裡
*/
for (Class<?> cls : classSet){
Factory factory = (Factory) cls.getAnnotation(Factory.class);
if(factory != null)
try {
factoryClasses.put(factory.id(), cls.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 輸入的id,對應到factoryGroupedClasses的關係,例項化工廠物件
* @param beanId
* @return
*/
@Override
public Object getBean(String id) {
return factoryClasses.get(id);
}
MargheritaPizza 類
添加註釋Factory,定義beanId:Margherita
@Factory(id = "margherita")
public classMargheritaPizzaimplementsPizza{
publicfloatgetPrice(){
System.out.println("8.5f");
return 8.5f;
}
}
CalzonePizza 類
添加註釋Factory,定義beanId:Calzone
@Factory(id = "calzone")
public classCalzonePizzaimplementsPizza{
publicfloatgetPrice(){
System.out.println("2.5f");
return 2.5f;
}
}
測試下
publicstaticvoidmain(String[] args){
/**
* 掃描com.annotation.factory下的類
*/
AnnApplicationContext factoryProcessor = new AnnApplicationContext("com.annotation.factory.spring");
Pizza p= (Pizza) factoryProcessor.getBean("Calzone");
p.getPrice();
}
好了,看完程式碼應該很清楚了,註解是不是給我們帶來很多方便了。 留個思考題,如何預設通過類的名字,首個字母小寫來作為beanId