NopCommerce学习(2) EntityFramework
NopCommerce-EntityFramework开发:主要是Controller-Service-Repository的开发方式
操作数据库,主要对象是BaseEntity,IDbContext和NopObjectContext,IRepository和EfRepository,NopEntityTypeConfiguration(可以使用EntityFramework提供的EntityTypeConfiguration不使用NopCommerce拓展的NopEntityTypeConfiguration)
以下代码GitBub地址:https://github.com/heshengli/nop
BaseEntity,实体基类
/// <summary>
/// Base class for entities
/// </summary>
public abstract partial class BaseEntity
{
/// <summary>
/// Gets or sets the entity identifier
/// </summary>
public int Id { get; set; }
public override bool Equals(object obj)
{
return Equals(obj as BaseEntity);
}
private static bool IsTransient(BaseEntity obj)
{
return obj != null && Equals(obj.Id, default(int));
}
private Type GetUnproxiedType()
{
return GetType();
}
public virtual bool Equals(BaseEntity other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (!IsTransient(this) &&
!IsTransient(other) &&
Equals(Id, other.Id))
{
var otherType = other.GetUnproxiedType();
var thisType = GetUnproxiedType();
return thisType.IsAssignableFrom(otherType) ||
otherType.IsAssignableFrom(thisType);
}
return false;
}
public override int GetHashCode()
{
if (Equals(Id, default(int)))
return base.GetHashCode();
return Id.GetHashCode();
}
public static bool operator ==(BaseEntity x, BaseEntity y)
{
return Equals(x, y);
}
public static bool operator !=(BaseEntity x, BaseEntity y)
{
return !(x == y);
}
}
IDbContext,数据上下文接口
public interface IDbContext
{
/// <summary>
/// Get DbSet
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <returns>DbSet</returns>
IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity;
/// <summary>
/// Save changes
/// </summary>
/// <returns></returns>
int SaveChanges();
/// <summary>
/// Execute stores procedure and load a list of entities at the end
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <param name="commandText">Command text</param>
/// <param name="parameters">Parameters</param>
/// <returns>Entities</returns>
IList<TEntity> ExecuteStoredProcedureList<TEntity>(string commandText, params object[] parameters)
where TEntity : BaseEntity, new();
/// <summary>
/// Creates a raw SQL query that will return elements of the given generic type. The type can be any type that has properties that match the names of the columns returned from the query, or can be a simple primitive type. The type does not have to be an entity type. The results of this query are never tracked by the context even if the type of object returned is an entity type.
/// </summary>
/// <typeparam name="TElement">The type of object returned by the query.</typeparam>
/// <param name="sql">The SQL query string.</param>
/// <param name="parameters">The parameters to apply to the SQL query string.</param>
/// <returns>Result</returns>
IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] parameters);
/// <summary>
/// Executes the given DDL/DML command against the database.
/// </summary>
/// <param name="sql">The command string</param>
/// <param name="doNotEnsureTransaction">false - the transaction creation is not ensured; true - the transaction creation is ensured.</param>
/// <param name="timeout">Timeout value, in seconds. A null value indicates that the default value of the underlying provider will be used</param>
/// <param name="parameters">The parameters to apply to the command string.</param>
/// <returns>The result returned by the database after executing the command.</returns>
int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = null, params object[] parameters);
/// <summary>
/// Detach an entity
/// </summary>
/// <param name="entity">Entity</param>
void Detach(object entity);
/// <summary>
/// Gets or sets a value indicating whether proxy creation setting is enabled (used in EF)
/// </summary>
bool ProxyCreationEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether auto detect changes setting is enabled (used in EF)
/// </summary>
bool AutoDetectChangesEnabled { get; set; }
}
IRepository,基础操作接口
/// <summary>
/// Repository
/// </summary>
public partial interface IRepository<T> where T : BaseEntity
{
/// <summary>
/// Get entity by identifier
/// </summary>
/// <param name="id">Identifier</param>
/// <returns>Entity</returns>
T GetById(object id);
/// <summary>
/// Insert entity
/// </summary>
/// <param name="entity">Entity</param>
void Insert(T entity);
/// <summary>
/// Insert entities
/// </summary>
/// <param name="entities">Entities</param>
void Insert(IEnumerable<T> entities);
/// <summary>
/// Update entity
/// </summary>
/// <param name="entity">Entity</param>
void Update(T entity);
/// <summary>
/// Update entities
/// </summary>
/// <param name="entities">Entities</param>
void Update(IEnumerable<T> entities);
/// <summary>
/// Delete entity
/// </summary>
/// <param name="entity">Entity</param>
void Delete(T entity);
/// <summary>
/// Delete entities
/// </summary>
/// <param name="entities">Entities</param>
void Delete(IEnumerable<T> entities);
/// <summary>
/// Delete entity by id
/// </summary>
/// <param name="id"></param>
void Delete(int id);
/// <summary>
/// Gets a table
/// </summary>
IQueryable<T> Table { get; }
/// <summary>
/// Gets a table with "no tracking" enabled (EF feature) Use it only when you load record(s) only for read-only operations
/// </summary>
IQueryable<T> TableNoTracking { get; }
}
NopObjectContext,继承DbContext, IDbContext,具体实现
/// <summary>
/// Object context
/// </summary>
public class NopObjectContext : DbContext, IDbContext
{
#region Ctor
public NopObjectContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
//是否开启懒加载
//((IObjectContextAdapter) this).ObjectContext.ContextOptions.LazyLoadingEnabled = true;
}
#endregion
#region Utilities
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//dynamically load all configuration
//System.Type configType = typeof(LanguageMap); //any of your configuration classes here
//var typesToRegister = Assembly.GetAssembly(configType).GetTypes()
var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
.Where(type => !String.IsNullOrEmpty(type.Namespace))
.Where(type => type.BaseType != null && type.BaseType.IsGenericType &&
type.BaseType.GetGenericTypeDefinition() == typeof(NopEntityTypeConfiguration<>));
//ef自带对象
//var typesToRegister2 = Assembly.GetExecutingAssembly().GetTypes()
//.Where(type => !String.IsNullOrEmpty(type.Namespace))
//.Where(type => type.BaseType != null && type.BaseType.IsGenericType &&
//type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
foreach (var type in typesToRegister)
{
dynamic configurationInstance = Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
//...or do it manually below. For example,
//modelBuilder.Configurations.Add(new LanguageMap());
//移除复数表名的约定
//modelBuilder.Conventions.Remove<PluralizingEntitySetNameConvention>();
//检测数据库是否存在
//Database.SetInitializer<ObjectContext>(null);
base.OnModelCreating(modelBuilder);
}
/// <summary>
/// Attach an entity to the context or return an already attached entity (if it was already attached)
/// </summary>
/// <typeparam name="TEntity">TEntity</typeparam>
/// <param name="entity">Entity</param>
/// <returns>Attached entity</returns>
protected virtual TEntity AttachEntityToContext<TEntity>(TEntity entity) where TEntity : BaseEntity, new()
{
//little hack here until Entity Framework really supports stored procedures
//otherwise, navigation properties of loaded entities are not loaded until an entity is attached to the context
var alreadyAttached = Set<TEntity>().Local.FirstOrDefault(x => x.Id == entity.Id);
if (alreadyAttached == null)
{
//attach new entity
Set<TEntity>().Attach(entity);
return entity;
}
//entity is already loaded
return alreadyAttached;
}
#endregion
#region Methods
/// <summary>
/// Create database script
/// </summary>
/// <returns>SQL to generate database</returns>
public string CreateDatabaseScript()
{
return ((IObjectContextAdapter)this).ObjectContext.CreateDatabaseScript();
}
/// <summary>
/// Get DbSet
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <returns>DbSet</returns>
public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
{
return base.Set<TEntity>();
}
/// <summary>
/// Execute stores procedure and load a list of entities at the end
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <param name="commandText">Command text</param>
/// <param name="parameters">Parameters</param>
/// <returns>Entities</returns>
public IList<TEntity> ExecuteStoredProcedureList<TEntity>(string commandText, params object[] parameters) where TEntity : BaseEntity, new()
{
//add parameters to command
if (parameters != null && parameters.Length > 0)
{
for (int i = 0; i <= parameters.Length - 1; i++)
{
var p = parameters[i] as DbParameter;
if (p == null)
throw new Exception("Not support parameter type");
commandText += i == 0 ? " " : ", ";
commandText += "@" + p.ParameterName;
if (p.Direction == ParameterDirection.InputOutput || p.Direction == ParameterDirection.Output)
{
//output parameter
commandText += " output";
}
}
}
var result = this.Database.SqlQuery<TEntity>(commandText, parameters).ToList();
//performance hack applied as described here - http://www.nopcommerce.com/boards/t/25483/fix-very-important-speed-improvement.aspx
bool acd = this.Configuration.AutoDetectChangesEnabled;
try
{
this.Configuration.AutoDetectChangesEnabled = false;
for (int i = 0; i < result.Count; i++)
result[i] = AttachEntityToContext(result[i]);
}
finally
{
this.Configuration.AutoDetectChangesEnabled = acd;
}
return result;
}
/// <summary>
/// Creates a raw SQL query that will return elements of the given generic type. The type can be any type that has properties that match the names of the columns returned from the query, or can be a simple primitive type. The type does not have to be an entity type. The results of this query are never tracked by the context even if the type of object returned is an entity type.
/// </summary>
/// <typeparam name="TElement">The type of object returned by the query.</typeparam>
/// <param name="sql">The SQL query string.</param>
/// <param name="parameters">The parameters to apply to the SQL query string.</param>
/// <returns>Result</returns>
public IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] parameters)
{
return this.Database.SqlQuery<TElement>(sql, parameters);
}
/// <summary>
/// Executes the given DDL/DML command against the database.
/// </summary>
/// <param name="sql">The command string</param>
/// <param name="doNotEnsureTransaction">false - the transaction creation is not ensured; true - the transaction creation is ensured.</param>
/// <param name="timeout">Timeout value, in seconds. A null value indicates that the default value of the underlying provider will be used</param>
/// <param name="parameters">The parameters to apply to the command string.</param>
/// <returns>The result returned by the database after executing the command.</returns>
public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = null, params object[] parameters)
{
int? previousTimeout = null;
if (timeout.HasValue)
{
//store previous timeout
previousTimeout = ((IObjectContextAdapter)this).ObjectContext.CommandTimeout;
((IObjectContextAdapter)this).ObjectContext.CommandTimeout = timeout;
}
var transactionalBehavior = doNotEnsureTransaction
? TransactionalBehavior.DoNotEnsureTransaction
: TransactionalBehavior.EnsureTransaction;
var result = this.Database.ExecuteSqlCommand(transactionalBehavior, sql, parameters);
if (timeout.HasValue)
{
//Set previous timeout back
((IObjectContextAdapter)this).ObjectContext.CommandTimeout = previousTimeout;
}
//return result
return result;
}
/// <summary>
/// Detach an entity
/// </summary>
/// <param name="entity">Entity</param>
public void Detach(object entity)
{
if (entity == null)
throw new ArgumentNullException("entity");
((IObjectContextAdapter)this).ObjectContext.Detach(entity);
}
#endregion
#region Properties
/// <summary>
/// Gets or sets a value indicating whether proxy creation setting is enabled (used in EF)
/// </summary>
public virtual bool ProxyCreationEnabled
{
get
{
return this.Configuration.ProxyCreationEnabled;
}
set
{
this.Configuration.ProxyCreationEnabled = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether auto detect changes setting is enabled (used in EF)
/// </summary>
public virtual bool AutoDetectChangesEnabled
{
get
{
return this.Configuration.AutoDetectChangesEnabled;
}
set
{
this.Configuration.AutoDetectChangesEnabled = value;
}
}
#endregion
}
EfRepository,IRepository接口的具体实现
/// <summary>
/// Entity Framework repository
/// </summary>
public partial class EfRepository<T> : IRepository<T> where T : BaseEntity
{
#region Fields
private readonly IDbContext _context;
private IDbSet<T> _entities;
#endregion
#region Ctor
/// <summary>
/// Ctor
/// </summary>
/// <param name="context">Object context</param>
public EfRepository(IDbContext context)
{
this._context = context;
}
#endregion
#region Utilities
/// <summary>
/// Get full error
/// </summary>
/// <param name="exc">Exception</param>
/// <returns>Error</returns>
protected string GetFullErrorText(DbEntityValidationException exc)
{
var msg = string.Empty;
foreach (var validationErrors in exc.EntityValidationErrors)
foreach (var error in validationErrors.ValidationErrors)
msg += string.Format("Property: {0} Error: {1}", error.PropertyName, error.ErrorMessage) + Environment.NewLine;
return msg;
}
#endregion
#region Methods
/// <summary>
/// Get entity by identifier
/// </summary>
/// <param name="id">Identifier</param>
/// <returns>Entity</returns>
public virtual T GetById(object id)
{
//see some suggested performance optimization (not tested)
//http://stackoverflow.com/questions/11686225/dbset-find-method-ridiculously-slow-compared-to-singleordefault-on-id/11688189#comment34876113_11688189
return this.Entities.Find(id);
}
/// <summary>
/// Insert entity
/// </summary>
/// <param name="entity">Entity</param>
public virtual void Insert(T entity)
{
try
{
if (entity == null)
throw new ArgumentNullException("entity");
this.Entities.Add(entity);
this._context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
throw new Exception(GetFullErrorText(dbEx), dbEx);
}
}
/// <summary>
/// Insert entities
/// </summary>
/// <param name="entities">Entities</param>
public virtual void Insert(IEnumerable<T> entities)
{
try
{
if (entities == null)
throw new ArgumentNullException("entities");
foreach (var entity in entities)
this.Entities.Add(entity);
this._context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
throw new Exception(GetFullErrorText(dbEx), dbEx);
}
}
/// <summary>
/// Update entity
/// </summary>
/// <param name="entity">Entity</param>
public virtual void Update(T entity)
{
try
{
if (entity == null)
throw new ArgumentNullException("entity");
this._context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
throw new Exception(GetFullErrorText(dbEx), dbEx);
}
}
/// <summary>
/// Update entities
/// </summary>
/// <param name="entities">Entities</param>
public virtual void Update(IEnumerable<T> entities)
{
try
{
if (entities == null)
throw new ArgumentNullException("entities");
this._context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
throw new Exception(GetFullErrorText(dbEx), dbEx);
}
}
/// <summary>
/// Delete entity
/// </summary>
/// <param name="entity">Entity</param>
public virtual void Delete(T entity)
{
try
{
if (entity == null)
throw new ArgumentNullException("entity");
this.Entities.Remove(entity);
this._context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
throw new Exception(GetFullErrorText(dbEx), dbEx);
}
}
/// <summary>
/// Delete entities
/// </summary>
/// <param name="entities">Entities</param>
public virtual void Delete(IEnumerable<T> entities)
{
try
{
if (entities == null)
throw new ArgumentNullException("entities");
foreach (var entity in entities)
this.Entities.Remove(entity);
this._context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
throw new Exception(GetFullErrorText(dbEx), dbEx);
}
}
public void Delete(int id)
{
try
{
T entity = this.Entities.Find(id);
if (entity == null)
throw new ArgumentNullException("entities");
this.Entities.Remove(entity);
this._context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
throw new Exception(GetFullErrorText(dbEx), dbEx);
}
}
#endregion
#region Properties
/// <summary>
/// Gets a table
/// </summary>
public virtual IQueryable<T> Table
{
get
{
return this.Entities;
}
}
/// <summary>
/// Gets a table with "no tracking" enabled (EF feature) Use it only when you load record(s) only for read-only operations
/// </summary>
public virtual IQueryable<T> TableNoTracking
{
get
{
return this.Entities.AsNoTracking();
}
}
/// <summary>
/// Entities
/// </summary>
protected virtual IDbSet<T> Entities
{
get
{
if (_entities == null)
_entities = _context.Set<T>();
return _entities;
}
}
#endregion
}
NopEntityTypeConfiguration,可拓展的抽象类
public abstract class NopEntityTypeConfiguration<T> : EntityTypeConfiguration<T> where T : class
{
protected NopEntityTypeConfiguration()
{
PostInitialize();
}
/// <summary>
/// Developers can override this method in custom partial classes
/// in order to add some custom initialization code to constructors
/// </summary>
protected virtual void PostInitialize()
{
}
}
简单demo:
实体基础代码
public class Student : BaseEntity
{
public string Name { get; set; }
public string CustomProperty { get; set; }
}
/// <summary>
/// 实体映射表nop拓展
/// </summary>
public partial class StudentMap : NopEntityTypeConfiguration<Student>
{
public StudentMap()
{
this.ToTable("Student");
this.HasKey(pa => pa.Id);
this.Property(pa => pa.Name).IsRequired();
}
}
/// <summary>
/// 原生ef,需要调整NopObjectContext的代码
/// </summary>
//public partial class StudentMap2 : EntityTypeConfiguration<Student>
//{
// public StudentMap2()
// {
// this.ToTable("Student");
// this.HasKey(pa => pa.Id);
// this.Property(pa => pa.Name).IsRequired();
// }
//}
public interface IStudentService
{
void Insert(Student stu);
void Update(Student stu);
void Delete(int id);
IEnumerable<Student> GetList();
}
public class StudentService : IStudentService
{
private readonly IRepository<Student> _iStudentRepository;
private readonly IDbContext _iDbContext;
public StudentService()
{
_iDbContext = new NopObjectContext("App");
_iStudentRepository = new EfRepository<Student>(_iDbContext);
}
//Ioc Autofac 调整
//public StudentService(IRepository<Student> iStudentRepositpory,IDbContext iDbContext)
//{
// _iDbContext = iDbContext;
// _iStudentRepository = iStudentRepositpory;
//}
public void Delete(int id)
{
_iStudentRepository.Delete(id);
}
public IEnumerable<Student> GetList()
{
return _iStudentRepository.Table.AsEnumerable();
}
public void Insert(Student stu)
{
_iStudentRepository.Insert(stu);
}
public void Update(Student stu)
{
_iStudentRepository.Update(stu);
}
}
调用代码(使用了简单的consoleApp)
class Program
{
private static readonly IStudentService _iStudentService =
new StudentService();
static void Main(string[] args)
{
Student stu = new Student() {
Name="李四",
CustomProperty="自定义属性2"
};
_iStudentService.Insert(stu);
Console.WriteLine(stu.Name+"");
Console.ReadKey();
}
}
NopCommerce学习(2) EntityFramework的更多相关文章
- NopCommerce学习(1) Caching
redis教程 http://www.runoob.com/redis/redis-tutorial.html 下载地址:https://github.com/MSOpenTech/redis/rel ...
- entityframework学习笔记--001
最近想重新好好学习一下entityframework,于是在院子里找到了一篇不错的博客.下面把学习的过程记录下来,方便以后复习. 学习过程参考大神的博客:http://www.cnblogs.com/ ...
- Web APi之EntityFramework【CRUD】(三)
前言 之前我们系统学习了EntityFramework,个人觉得有些东西不能学了就算完了,必须要学以致用,在Web API上也少不了增(C).删(D).改(U).查(R).鉴于此,我们通过EF来实现W ...
- 基于nopcommerce b2c开源项目的精简版开发框架Nop.Framework
http://www.17ky.net/soft/70612.html?v=1#0-sqq-1-39009-9737f6f9e09dfaf5d3fd14d775bfee85 项目详细介绍 该开源项目是 ...
- 基于nopCommerce的开发框架(附源码)
.NET的开发人员应该都知道这个大名鼎鼎的高质量b2c开源项目-nopCommerce,基于EntityFramework和MVC开发,拥有透明且结构良好的解决方案,同时结合了开源和商业软件的最佳特性 ...
- ABP 初探 之基于EasyUI的CURD
结束了天天加班的项目,项目虽然结束,但还是有点小问题,只能在后期优化当中完成了,本次做项目采用了,MVC.Webapi.Entityframework,在园了里看到了有关ABP的介绍,同样ABP也是最 ...
- entity framework 新手入门篇(3)-entity framework实现orderby,count,groupby,like,in,分页等
前面我们已经学习了entityframework的基本的增删改查,今天,我们将在EF中实现一些更加贴近于实际功能的SQL方法. 承接上面的部分,我们有一个叫做House的数据库,其中包含house表和 ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(转)
http://www.cnblogs.com/xishuai/p/ddd-repository-iunitofwork-and-idbcontext.html 好久没写 DDD 领域驱动设计相关的文章 ...
- .NET领域最为流行的IOC框架之一Autofac
一.前言 Autofac是.NET领域最为流行的IOC框架之一,微软的Orchad开源程序使用的就是Autofac,Nopcommerce开源程序也是用的Autofac. Orchad和Nopcomm ...
随机推荐
- thinkphp更新数据库的时候where('')为字符串
if($user->where('phone='.$phone)->save($dataList)){} if($user->where(array('phone' =>$ph ...
- Java中的深拷贝和浅拷贝(转载)
深拷贝(深复制)和浅拷贝(浅复制)是两个比较通用的概念,尤其在C++语言中,若不弄懂,则会在delete的时候出问题,但是我们在这幸好用的是Java.虽然java自动管理对象的回收,但对于深拷贝(深复 ...
- AGS API for JS代理页的使用
AGS API for JS代理页的使用 1.概述 代理页即使用后端语言编写的请求转发页面,部署在Web应用端.客户端请求先发送到该代理页,代理页再将该请求转发到服务器处理,服务器处理结果再经代理页转 ...
- 使用SlidingPaneLayout 实现仿微信的滑动返回
上周,公司的项目改版要求加上一个右滑返回上一个界面,于是就在网上找了一些开源库打算实现.但是在使用的时候遇见了许多的问题.试了两天用过 https://github.com/ikew0ng/Swipe ...
- CSS 3篇(持续更新)
1.关于盒子模型 css盒子模型 又称框模型 (Box Model) ,包含了元素内容(content).内边距(padding).边框(border).外边距(margin)几个要素.如图: 理解c ...
- tomcat + jdk 快速部署
环境: apache-tomcat-7.0.73 java version "1.8.0_112" 注释:创建普通用户,使用 sudu进行操作,本文略写. 1.JDK 1.jdk配 ...
- 根据viewport的size自动调整fontsize大小
现在的网站必须要考虑mobile上访问的友好性,bootstrap作为mobile first的前端框架得到很多应用,它通过默认就使用.col-xs-xx定义的width,同时加上@media(min ...
- Web API 2 入门——Web API 2(C#)入门(谷歌翻译)
ASP.NET Web API 2(C#)入门 在这篇文章中 本教程中使用的软件版本 创建一个Web API项目 添加模型 添加控制器 使用Javascript和jQuery调用Web API 运行应 ...
- win10系统 WMI Provider Host cpu 占用过高
今天上班,发现系统有点卡,QQ总是没响应. 打开任务管理器,发现有一个程序- WMI Provider Host 占用cpu竟然高达80% 然后通过事件查看器发现是一个pid为9832的程序造成的 然 ...
- Java日志系统
前言 各组件之间的关系: slf4j是The Simple Logging Facade for Java的简称,是一个简单日志门面抽象框架,它本身只提供了日志Facade API和一个简单的日志类实 ...