使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(六)-- 依赖注入

本篇将介绍Asp.Net Core中一个非常重要的特性:依赖注入,并展示其简单用法。

第一部分、概念介绍

Dependency Injection:又称依赖注入,简称DI。在以前的开发方式中,层与层之间、类与类之间都是通过new一个对方的实例进行相互调用,这样在开发过程中有一个好处,可以清晰的知道在使用哪个具体的实现。随着软件体积越来越庞大,逻辑越来越复杂,当需要更换实现方式,或者依赖第三方系统的某些接口时,这种相互之间持有具体实现的方式不再合适。为了应对这种情况,就要采用契约式编程:相互之间依赖于规定好的契约(接口),不依赖于具体的实现。这样带来的好处是相互之间的依赖变得非常简单,又称松耦合。至于契约和具体实现的映射关系,则会通过配置的方式在程序启动时由运行时确定下来。这就会用到DI。

 

第二部分、DI的注册与注入

借用这个系列之前的框架结构,添加如下接口和实现类 

 1 using System.Collections.Generic;
 2 using WebApiFrame.Models;
 3 
 4 namespace WebApiFrame.Repositories
 5 {
 6     public interface IUserRepository
 7     {
 8         IEnumerable<User> GetAll();
 9 
10         User GetById(int id);
11     }
12 }
IUserRepository.cs
 1 using System.Collections.Generic;
 2 using System.Linq;
 3 using WebApiFrame.Models;
 4 
 5 namespace WebApiFrame.Repositories
 6 {
 7     public class UserRepository : IUserRepository
 8     {
 9         private IList<User> list = new List<User>()
10         {
11             new User(){ Id = 1, Name = "name:1", Sex = "Male" },
12             new User(){ Id = 2, Name = "name:2", Sex = "Female" },
13             new User(){ Id = 3, Name = "name:3", Sex = "Male" },
14         };
15 
16         public IEnumerable<User> GetAll()
17         {
18             return list;
19         }
20 
21         public User GetById(int id)
22         {
23             return list.FirstOrDefault(i => i.Id == id);
24         }
25     }
26 }
UserRepository.cs

一、注册

修改 Startup.cs 的ConfigureServices方法,将上面的接口和实现类注入到DI容器里

1         public void ConfigureServices(IServiceCollection services)
2         {
3             // 注入MVC框架
4             services.AddMvc();
5 
6             // 注册接口和实现类的映射关系
7             services.AddScoped<IUserRepository, UserRepository>();
8         }

修改 UsersController.cs 的构造函数和Action方法

 1 using System;
 2 using Microsoft.AspNetCore.Mvc;
 3 using WebApiFrame.Models;
 4 using WebApiFrame.Repositories;
 5 
 6 namespace WebApiFrame.Controllers
 7 {
 8     [Route("api/[controller]")]
 9     public class UsersController : Controller
10     {
11         private readonly IUserRepository userRepository;
12 
13         public UsersController(IUserRepository userRepo)
14         {
15             userRepository = userRepo;
16         }
17 
18         [HttpGet]
19         public IActionResult GetAll()
20         {
21             var list = userRepository.GetAll();
22             return new ObjectResult(list);
23         }
24 
25         [HttpGet("{id}")]
26         public IActionResult Get(int id)
27         {
28             var user = userRepository.GetById(id);
29             return new ObjectResult(user);
30         }
31 
32         #region 其他方法
33         // ......
34         #endregion
35     }
36 }

启动程序,分别访问地址 http://localhost:5000/api/users 和 http://localhost:5000/api/users/1 ,页面将展示正确的数据。

从上面的例子可以看到,在 Startup.cs 的ConfigureServices的方法里,通过参数的AddScoped方法,指定接口和实现类的映射关系,注册到DI容器里。在控制器里,通过构造方法将具体的实现注入到对应的接口上,即可在控制器里直接调用了。

除了在ConfigureServices方法里进行注册外,还可以在Main函数里进行注册。注释掉 Startup.cs ConfigureServices方法里的注入代码,在 Program.cs 的Main函数里添加注入方法

 1 using Microsoft.AspNetCore.Hosting;
 2 using Microsoft.Extensions.DependencyInjection;
 3 using WebApiFrame.Repositories;
 4 
 5 namespace WebApiFrame
 6 {
 7     public class Program
 8     {
 9         public static void Main(string[] args)
10         {
11             var host = new WebHostBuilder()
12                 .UseKestrel()
13                 .ConfigureServices(services=>
14                 {
15                     // 注册接口和实现类的映射关系
16                     services.AddScoped<IUserRepository, UserRepository>();
17                 })
18                 .UseStartup<Startup>()
19                 .Build();
20 
21             host.Run();
22         }
23     }
24 }

此方法等效于 Startup.cs 的ConfigureServices方法。

二、注入

添加三个测试接口和实现类

 1 namespace WebApiFrame
 2 {
 3     public interface ITestOne
 4     {
 5         
 6     }
 7 
 8     public class TestOne : ITestOne
 9     {
10         
11     }
12 }
ITestOne.cs
 1 namespace WebApiFrame
 2 {
 3     public interface ITestTwo
 4     {
 5         
 6     }
 7 
 8     public class TestTwo : ITestTwo
 9     {
10         
11     }
12 }
ITestTwo.cs
 1 namespace WebApiFrame
 2 {
 3     public interface ITestThree
 4     {
 5 
 6     }
 7 
 8     public class TestThree : ITestThree
 9     {
10 
11     }
12 }
ITestThree.cs

修改 Startup.cs 的ConfigureServices方法,将接口和实现类的映射关系注册到DI容器

 1         public void ConfigureServices(IServiceCollection services)
 2         {
 3             // 注入MVC框架
 4             services.AddMvc();
 5 
 6             // 注册接口和实现类的映射关系
 7             services.AddScoped<ITestOne, TestOne>();
 8             services.AddScoped<ITestTwo, TestTwo>();
 9             services.AddScoped<ITestThree, TestThree>();
10         }

添加 DemoController.cs 类

 1 using System.Threading.Tasks;
 2 using Microsoft.AspNetCore.Http;
 3 using Microsoft.AspNetCore.Mvc;
 4 
 5 namespace WebApiFrame
 6 {
 7     [Route("[controller]")]
 8     public class DemoController : Controller
 9     {
10         private readonly ITestOne _testOne;
11         private readonly ITestTwo _testTwo;
12         private readonly ITestThree _testThree;
13 
14         public DemoController(ITestOne testOne, ITestTwo testTwo, ITestThree testThree)
15         {
16             _testOne = testOne;
17             _testTwo = testTwo;
18             _testThree = testThree;
19         }
20 
21         [HttpGet("index")]
22         public async Task Index()
23         {
24             HttpContext.Response.ContentType = "text/html";
25             await HttpContext.Response.WriteAsync($"<h1>ITestOne => {_testOne}</h1>");
26             await HttpContext.Response.WriteAsync($"<h1>ITestTwo => {_testTwo}</h1>");
27             await HttpContext.Response.WriteAsync($"<h1>ITestThree => {_testThree}</h1>");
28         } 
29     }
30 }

启动程序,访问地址 http://localhost:5000/demo/index ,页面显示了每个接口对应的实现类

通常依赖注入的方式有三种:构造函数注入、属性注入、方法注入。在Asp.Net Core里,采用的是构造函数注入。

在以前的Asp.Net MVC版本里,控制器必须有一个无参的构造函数,供框架在运行时调用创建控制器实例,在Asp.Net Core里,这不是必须的了。当访问控制器的Action方法时,框架会依据注册的映射关系生成对应的实例,通过控制器的构造函数参数注入到控制器中,并创建控制器实例。

三、构造函数的选择

上一个例子展示了在.Net Core里采用构造函数注入的方式实现依赖注入。当构造函数有多个,并且参数列表不同时,框架又会采用哪一个构造函数创建实例呢?

为了更好的演示,新建一个.Net Core控制台程序,引用下面两个nuget包。DI容器正是通过这两个包来实现的。

"Microsoft.Extensions.DependencyInjection": "1.0.0"
"Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0"

同样新建四个测试接口和实现类,并在Main函数添加注册代码。最终代码如下

 1 using Microsoft.Extensions.DependencyInjection;
 2 using System;
 3 
 4 namespace DiApplicationTest
 5 {
 6     public class Program
 7     {
 8         public static void Main(string[] args)
 9         {
10             IServiceCollection services = new ServiceCollection();
11             services.AddScoped<ITestOne, TestOne>()
12                 .AddScoped<ITestTwo, TestTwo>()
13                 .AddScoped<ITestThree, TestThree>()
14                 .AddScoped<ITestApp, TestApp>()
15                 .BuildServiceProvider()
16                 .GetService<ITestApp>();
17 
18             Console.ReadLine();
19         }
20     }
21 
22     public interface ITestOne { }
23     public interface ITestTwo { }
24     public interface ITestThree { }
25 
26     public class TestOne : ITestOne { }
27     public class TestTwo : ITestTwo { }
28     public class TestThree : ITestThree { }
29 
30     public interface ITestApp { }
31     public class TestApp : ITestApp
32     {
33         public TestApp(ITestOne testOne, ITestTwo testTwo, ITestThree testThree)
34         {
35             Console.WriteLine($"TestApp({testOne}, {testTwo}, {testThree})");
36         }
37     }
38 }

启动调试,在cmd窗口可以看见打印内容

这里注册了四个接口和对应的实现类,其中一个接口的实现类 TestApp.cs 拥有一个三个参数的构造函数,这三个参数类型分别是其他三个接口。通过GetServices方法通过唯一的一个构造函数创建了 TestApp.cs 的一个实例。

接下来在 TestApp.cs 里添加一个有两个参数的构造函数,同时修改Main函数内容,去掉一个接口的注册

 1     public class TestApp : ITestApp
 2     {
 3         public TestApp(ITestOne testOne, ITestTwo testTwo)
 4         {
 5             Console.WriteLine($"TestApp({testOne}, {testTwo})");
 6         }
 7 
 8         public TestApp(ITestOne testOne, ITestTwo testTwo, ITestThree testThree)
 9         {
10             Console.WriteLine($"TestApp({testOne}, {testTwo}, {testThree})");
11         }
12     }
 1         public static void Main(string[] args)
 2         {
 3             IServiceCollection services = new ServiceCollection();
 4             services.AddScoped<ITestOne, TestOne>()
 5                 .AddScoped<ITestTwo, TestTwo>()
 6                 //.AddScoped<ITestThree, TestThree>()
 7                 .AddScoped<ITestApp, TestApp>()
 8                 .BuildServiceProvider()
 9                 .GetService<ITestApp>();
10 
11             Console.ReadLine();
12         }

再次启动调试,查看cmd窗口打印内容

当有多个构造函数时,框架会选择参数都是有效注入接口的构造函数创建实例。在上面这个例子里, ITestThree.cs 和 TestThree.cs 的映射关系没有注册到DI容器里,框架在选择有效的构造函数时,会过滤掉含有ITestThree接口类型的参数的构造函数。

接下来在 TestApp.cs 再添加一个构造函数。为了方便起见,我给每个构造函数添加了编号标识一下。

 1     public class TestApp : ITestApp
 2     {
 3         // No.1
 4         public TestApp(ITestOne testOne)
 5         {
 6             Console.WriteLine($"TestApp({testOne})");
 7         }
 8 
 9         // No.2
10         public TestApp(ITestOne testOne, ITestTwo testTwo)
11         {
12             Console.WriteLine($"TestApp({testOne}, {testTwo})");
13         }
14 
15         // No.3
16         public TestApp(ITestOne testOne, ITestTwo testTwo, ITestThree testThree)
17         {
18             Console.WriteLine($"TestApp({testOne}, {testTwo}, {testThree})");
19         }
20     }

再次启动调试,查看cmd窗口打印内容

结果显示框架选择了No.2号构造函数。框架会选择参数列表集合是其他所有有效的构造函数的参数列表集合的超集的构造函数。在这个例子里,有No.1和No.2两个有效的构造函数,No.2的参数列表集合为[ITestOne, ITestTwo],No.1的参数列表集合为[ITestOne],No.2是No.1的超集,所以框架选择了No.2构造函数创建实例。

接下来修改下 TestApp.cs 的构造函数,取消Main函数里 ITestThree.cs 注册代码的注释

 1     public class TestApp : ITestApp
 2     {
 3         // No.2
 4         public TestApp(ITestOne testOne, ITestTwo testTwo)
 5         {
 6             Console.WriteLine($"TestApp({testOne}, {testTwo})");
 7         }
 8 
 9         // No.4
10         public TestApp(ITestTwo testTwo, ITestThree testThree)
11         {
12             Console.WriteLine($"TestApp({testTwo}, {testThree})");
13         }
14     }

启动调试,发现会抛出一个 System.InvalidOperationException 异常,异常内容表明框架无法选择一个正确的构造函数,不能创建实例。

在这个例子里,两个构造函数的参数列表集合分别为[ITestOne, ITestTwo]和[ITestTwo, ITestThree],因为谁也无法是对方的超集,所以框架不能继续创建实例。

总之,框架在选择构造函数时,会依次遵循以下两点规则:

1. 使用有效的构造函数创建实例

2. 如果有效的构造函数有多个,选择参数列表集合是其他所有构造函数参数列表集合的超集的构造函数创建实例

如果以上两点都不满足,则抛出 System.InvalidOperationException 异常。

四、Asp.Net Core默认注册的服务接口

框架提供了但不限于以下几个接口,某些接口可以直接在构造函数和 Startup.cs 的方法里注入使用

 

第三部分、生命周期管理

框架对注入的接口创建的实例有一套生命周期的管理机制,决定了将采用什么样的创建和回收实例。

下面通过一个例子演示这三种方式的区别

在第二部分的第二点的例子里添加以下几个接口和实现类

 1 using System;
 2 
 3 namespace WebApiFrame
 4 {
 5     public interface ITest
 6     {
 7         Guid TargetId { get; }
 8     }
 9 
10     public interface ITestTransient : ITest { }
11     public interface ITestScoped : ITest { }
12     public interface ITestSingleton : ITest { }
13 
14     public class TestInstance : ITestTransient, ITestScoped, ITestSingleton
15     {
16         public Guid TargetId
17         {
18             get
19             {
20                 return _targetId;
21             }
22         }
23 
24         private Guid _targetId { get; set; }
25 
26         public TestInstance()
27         {
28             _targetId = Guid.NewGuid();
29         }
30     }
31 }
ITest.cs
 1 namespace WebApiFrame
 2 {
 3     public class TestService
 4     {
 5         public ITestTransient TestTransient { get; }
 6         public ITestScoped TestScoped { get; }
 7         public ITestSingleton TestSingleton { get; }
 8 
 9         public TestService(ITestTransient testTransient, ITestScoped testScoped, ITestSingleton testSingleton)
10         {
11             TestTransient = testTransient;
12             TestScoped = testScoped;
13             TestSingleton = testSingleton;
14         }
15     }
16 }
TestService.cs

修改 Startup.cs 的ConfigureServices方法里添加注册内容

 1         public void ConfigureServices(IServiceCollection services)
 2         {
 3             // 注入MVC框架
 4             services.AddMvc();
 5 
 6             // 注册接口和实现类的映射关系
 7             services.AddTransient<ITestTransient, TestInstance>();
 8             services.AddScoped<ITestScoped, TestInstance>();
 9             services.AddSingleton<ITestSingleton, TestInstance>();
10             services.AddTransient<TestService, TestService>();
11         }

修改 DemoController.cs 内容

 1 using System.Threading.Tasks;
 2 using Microsoft.AspNetCore.Http;
 3 using Microsoft.AspNetCore.Mvc;
 4 
 5 namespace WebApiFrame
 6 {
 7     [Route("[controller]")]
 8     public class DemoController : Controller
 9     {
10         public ITestTransient _testTransient { get; }
11         public ITestScoped _testScoped { get; }
12         public ITestSingleton _testSingleton { get; }
13         public TestService _testService { get; }
14 
15         public DemoController(ITestTransient testTransient, ITestScoped testScoped, ITestSingleton testSingleton, TestService testService)
16         {
17             _testTransient = testTransient;
18             _testScoped = testScoped;
19             _testSingleton = testSingleton;
20             _testService = testService;
21         }
22 
23         [HttpGet("index")]
24         public async Task Index()
25         {
26             HttpContext.Response.ContentType = "text/html";
27             await HttpContext.Response.WriteAsync($"<h1>Controller Log</h1>");
28             await HttpContext.Response.WriteAsync($"<h6>Transient => {_testTransient.TargetId.ToString()}</h6>");
29             await HttpContext.Response.WriteAsync($"<h6>Scoped => {_testScoped.TargetId.ToString()}</h6>");
30             await HttpContext.Response.WriteAsync($"<h6>Singleton => {_testSingleton.TargetId.ToString()}</h6>");
31             
32             await HttpContext.Response.WriteAsync($"<h1>Service Log</h1>");
33             await HttpContext.Response.WriteAsync($"<h6>Transient => {_testService.TestTransient.TargetId.ToString()}</h6>");
34             await HttpContext.Response.WriteAsync($"<h6>Scoped => {_testService.TestScoped.TargetId.ToString()}</h6>");
35             await HttpContext.Response.WriteAsync($"<h6>Singleton => {_testService.TestSingleton.TargetId.ToString()}</h6>");
36         }
37     }
38 }

启动调试,连续两次访问地址 http://localhost:5000/demo/index ,查看页面内容

对比内容可以发现,在同一个请求里,Transient对应的GUID都是不一致的,Scoped对应的GUID是一致的。而在不同的请求里,Scoped对应的GUID是不一致的。在两个请求里,Singleton对应的GUID都是一致的。

 

第三部分、第三方DI容器

除了使用框架默认的DI容器外,还可以引入其他第三方的DI容器。下面以Autofac为例,进行简单的演示。

引入Autofac的nuget包

"Autofac.Extensions.DependencyInjection": "4.0.0-rc3-309"

在上面的例子的基础上修改 Startup.cs 的ConfigureServices方法,引入autofac的DI容器,修改方法返回值

 1         public IServiceProvider ConfigureServices(IServiceCollection services)
 2         {
 3             // 注入MVC框架
 4             services.AddMvc();
 5 
 6             // autofac容器
 7             var containerBuilder = new ContainerBuilder();
 8             containerBuilder.RegisterType<TestInstance>().As<ITestTransient>().InstancePerDependency();
 9             containerBuilder.RegisterType<TestInstance>().As<ITestScoped>().InstancePerLifetimeScope();
10             containerBuilder.RegisterType<TestInstance>().As<ITestSingleton>().SingleInstance();
11             containerBuilder.RegisterType<TestService>().AsSelf().InstancePerDependency();
12             containerBuilder.Populate(services);
13 
14             var container = containerBuilder.Build();
15             return container.Resolve<IServiceProvider>();
16         }

启动调试,再次访问地址 http://localhost:5000/demo/index ,会得到上个例子同样的效果。

转载于:https://www.cnblogs.com/niklai/p/5683219.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/372603.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于JAX-WS的webService开发实例

最近因为工作原因接触到webService&#xff0c;所以记录下开发中碰到的问题&#xff0c;方便自己以后复习&#xff0c;顺便发扬一下开源精神。刚刚接触webServie如果有什么错误欢迎大家指正。 本地环境&#xff1a;myEclipse10.6 tomcat7 JDK7 jaxws-ri-2.2.10 第一步&#xff…

完整的WebApplication JSF EJB JPA JAAS –第2部分

视图–创建和JSF设置 本教程是第1部分的继续。 让我们创建一个新的Dynamic Web Project 。 如下图所示创建它&#xff1a; 注意&#xff1a;在某些时候&#xff0c;Eclipse会询问您是否要添加JSF功能&#xff08;自动完成&#xff09;&#xff0c;然后启用它。 就像下面的屏幕…

Android使用绘图Path总结

Path作为Android中一种相对复杂的绘图方式&#xff0c;官方文档中的有些解释并不是很好理解&#xff0c;这里作一个相对全面一些的总结&#xff0c;供日后查看&#xff0c;也分享给大家&#xff0c;共同进步。 1.基本绘图方法 addArc(RectF oval, float startAngle, float swee…

2017.3.23下午

下午通过对OSPF基本原理进一步的学习&#xff0c;对上午学习的内容进行了复习。 转载于:https://www.cnblogs.com/bgd140206206/p/6606192.html

编写Eclipse插件教程–第1部分

Eclipse是三个最受欢迎的Java开发IDE之一。 其成功的原因之一是其可扩展性。 对于任何知道该怎么做并且已经做到的人来说&#xff0c;编写eclipse插件都可以非常轻松快捷。 不幸的是&#xff0c;第一次在Eclipse中进行操作可能会非常耗时且令人沮丧。 Eclipse框架非常庞大&…

简单Window下 Android Studio的安装

&#xff08;1&#xff09;首先安装JDK 下载JDK 本人觉得官方网站下JDK比较慢&#xff0c;可以直接百度JDK&#xff0c;&#xff08;如果是64位 百度搜索记得64位&#xff09; 类似于这样的下载 安装可以看下教程&#xff0c;包括环境变量的配置 如何安装JDK &#xff08;2&…

日期处理一之NSLalendar的使用

一、日期和时间模式 日期和时间格式由日期和时间模式字符串组成&#xff0c;在日期和时间模式字符串中未加引号的A到‘Z’和a到‘z’被解释为模式字母&#xff0c;用来表示日期或时间。字符串元素&#xff0c;文本可以使用单引号&#xff08;‘’&#xff09;引起来使用。定义以…

十大最受欢迎的新Eclipse插件

Eclipse Marketplace仍然是发现有趣且相关的Eclipse插件的地方。 通过Eclipse Marketplace客户端&#xff0c;每月成功安装100,000多个基于Eclipse的产品。 我们提供了过去30天 以来所有时间最受欢迎的插件列表。 我认为看看过去12个月中最受欢迎的新插件会很有趣。 以下列出了…

《Java技术》第二次作业计科1501赵健宇

&#xff08;一&#xff09;学习总结 1.使用Eclipse关联jdk源代码,查看String类的equals&#xff08;&#xff09;方法 equals&#xff08;&#xff09;方法截图 “”比较的是地址。equals方法他同样使用号进行内存地址的比较。但是equals方法重写如果号比较不相等&#xff0c;…

JUnit的内置Hamcrest Core Matcher支持

在用JUnit和Hamcrest改进assertEquals的文章中&#xff0c;我简要讨论了Hamcrest “ 核心 ”匹配器与JUnit的现代版本“结合”在一起的情况。 在那篇文章中&#xff0c;我特别关注了JUnit的assertThat&#xff08;T&#xff0c;Matcher&#xff09;静态方法与Hamcrest核心is()匹…

Java开发高性能网站需要关注的事

转自&#xff1a;http://www.javabloger.com/java-development-concern-those-things/ 近期各家IT媒体举办的业内技术大会让很多网站都在披露自己的技术内幕与同行们分享&#xff0c;大到facebook&#xff0c;百度&#xff0c;小到刚起步的网站。facebook&#xff0c;百度之类的…

并发–顺序线程和原始线程

我不久前参与了一个项目&#xff0c;该项目的报告流程如下&#xff1a; 用户会要求举报 报告要求将被翻译成较小的部分 每个零件的报告将基于零件/节的类型由报告生成器生成 组成报告的各个部分将重新组合成最终报告&#xff0c;并返回给用户 我的目标是展示如何从错误的实…

linux夏令时配置文件,Linux夏令时是怎么调整的?

以法国巴黎为例&#xff1a;root121 zoneinfo]# ln -s /usr/share/zoneinfo/Europe/Paris /etc/localtime[root121 zoneinfo]# date2015年 10月 13日 星期二 03:45:09 CEST[root121 zoneinfo]# date -RTue, 13 Oct 2015 03:45:31 0200[root121 zoneinfo]# zdump -v /etc/localt…

Kali Linux渗透基础知识整理(二)漏洞扫描

Kali Linux渗透基础知识整理系列文章回顾 漏洞扫描 网络流量NmapHping3NessuswhatwebDirBusterjoomscanWPScan网络流量 网络流量就是网络上传输的数据量。 TCP协议 TCP是因特网中的传输层协议&#xff0c;使用三次握手协议建立连接。当主动方发出SYN连接请求后&#xff0c;等待…

嵌入式软件设计第09实验报告

学号&#xff1a;140201133 姓名&#xff1a;李宇昕 组别&#xff1a;第3组 实验地点&#xff1a;D19 一、实验目的&#xff1a; 1.熟悉WWW技术中的SSI&#xff08;Server Side Include&#xff09;技术。 2.学会使用SSI技术编写代码把当前开发板内…

TeamCity工件:HTTP,Ant,Gradle和Maven

您可以通过几种方式检索TeamCity工件&#xff1f; 我说有很多选择 &#xff01; 如果您使用的是Java构建工具&#xff0c;那么可以使用简单的HTTP请求&#xff0c;Ant Ivy&#xff0c;Gradle和Maven下载和使用TeamCity构建配置生成的二进制文件。 怎么样&#xff1f; 继续阅读…

AutoCAD如何方便截图放到Word文档,改成白底黑字

将模型视图切换到布局2即可 比如下图所示的效果 先回到模型视图把所有线条颜色都改成白色&#xff0c;然后添加适当的标注&#xff08;比如要受力分析&#xff0c;则在CAD中绘制箭头也很方便的&#xff09;&#xff0c;文字说明。然后切换到布局2就OK 可以截图了。 转载于:http…

iOS--支付宝环境集成

1.下载支付宝SDK以及Demo https://doc.open.alipay.com/doc2/detail?treeId54&articleId103419&docType1 2.新建文件夹“AliSDK”&#xff0c;将压缩包内的文件拷贝到该文件夹下&#xff0c;完成后如下图所示&#xff1a; 3.将文件夹拷贝到项目中&#xff0c; 4.执行完…

Java EE 6测试第二部分– Arquillian和ShrinkWrap简介

在Java EE 6测试的第一部分中&#xff0c;我简要介绍了使用Glassfish嵌入式容器的EJB 3.1 Embeddable API&#xff0c;以演示如何启动该容器&#xff0c;如何在项目类路径中查找bean以及运行非常简单的集成测试。 这篇文章重点介绍Arquillian和ShrinkWrap以及为什么它们是用于企…

【腾讯Bugly干货分享】Android内存优化总结实践

本文来自于腾讯Bugly公众号&#xff08;weixinBugly&#xff09;&#xff0c;未经作者同意&#xff0c;请勿转载&#xff0c;原文地址&#xff1a;https://mp.weixin.qq.com/s/2MsEAR9pQfMr1Sfs7cPdWQ 导语 智能手机发展到今天已经有十几个年头&#xff0c;手机的软硬件都已经发…