详细说明Spring--AOP
这篇博客较长,耐心读完或许会有“柳暗花明又一村”的感觉哦!
为什么?
我先不说AOP是什么,我先说说为什么要用AOP,依照惯例,我还是先举一个例子:
先把项目结构展现出来:
我们先在com.jd.calculator.service里定义一个接口
ICalculatorService
:package com.jd.calculator.service;public interface ICalculatorService {//定义几个简单的加减法//加法int add(int a, int b);//除法int div(int a, int b);//乘法int mul(int a, int b); }
然后去实现这个接口:
package com.jd.calculator.service;import org.springframework.stereotype.Service;@Service public class CalculatorService implements ICalculatorService{@Overridepublic int add(int a, int b) {System.out.println("The add method begins");System.out.println("The parameters of the add method are"+a+","+b);int result = a + b;return result;}@Overridepublic int div(int a, int b) {System.out.println("The div method begins");System.out.println("The parameters of the div method are"+a+","+b);int result = a / b;return result;}@Overridepublic int mul(int a, int b) {System.out.println("The mul method begins");System.out.println("The parameters of the mul method are"+a+","+b);int result = a * b;return result;} }
我们会发现上面的三个方法除了方法名和具体的计算不同,其他的几乎完全一样,这就造成代码的冗余,开发的效率也会很低。
虽然我们可以再定义一个方法,把相同的部分放进去,然后其他方法再调用它。但假如我们的需求变了,不单单是在输出结果之前要输出一些东西,我们还想要输出结果之后再输出一些东西,出错了也要输出一些东西,那这时如果还采取这种方法就也会显得很复杂了。那这时该怎么办呢?
这时就要用到AOP了。
是什么?
AOP(Aspect Oriented Programming 面向切面编程)是一种指在程序运行期间动态的将某段代码切入到指定方法的指定位置进行运行的编程方式,这种编程方式实现了代码复用,是对传统OOP(Object Oriented Programming,面向对象编程 )的补充。目前,Aspectj是Java社区里最完整最流行的AOP框架,在Spring 2.0以上版本中可以通过Aspectj注解或基于XML配置AOP。
AOP用到上面的例子就是只关注代码重复的部分。
怎么做?
第一种方法:通过注解来实现AOP
@Before
:
- 首先需要配置一些jar包:
- 在Spring 配置文件(也就是
application.xml
)里添加:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
,它可以自动生成代理类(不理解没关系,接着往下看) - 在
com.jd.aspects
包里自定义一个切面类:CalculatorAspects
package com.jd.aspects;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;@Aspect //要想生成代理类,要加这个注解
@Component
public class CalculatorAspects {@Before("execution(public int com.jd.calculator.service.CalculatorService.*(..))")//匹配这些的才能生成代理类public void before(JoinPoint js){Object [] args = js.getArgs();//获取参数String name = js.getSignature().getName();//获取方法名System.out.println("The "+name+" method begins");System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);}
}
我们一点一点说上面的代码:
根据
@Component
可以在IOC容器中自动实例化(即生成对象,这里就不做过多讨论了);aspects是切面的意思,你可以理解成从某一点(或一系列点)切入,然后再切出。这个
CalculatorAspects
类 加上@Aspect
这个注解,就表示一个切面(即代理类),至于从哪里切入,且看第3点;@Before("execution(public int com.jd.calculator.service.CalculatorService.*(..))")
,我们先看Before,before什么意思?之前的意思,就是说要做一件事之前,先做另一件事情,我们看到Object [] args = js.getArgs();//获取参数String name = js.getSignature().getName();//获取方法名System.out.println("The "+name+" method begins");System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);
这段代码和
CalculatorService
类中的System.out.println("The add method begins");System.out.println("The parameters of the add method are"+a+","+b);
这段代码一样,具有同样的作用,而且都要在计算输出结果之前输出。这时你能够理解
@Before
这个注解的作用了吧:就是在做某件事之前,先做另一件事。那这时你又疑惑了,是哪件事之前呢?换句话说,切入点在哪呢?切入点就在public int com.jd.calculator.service.CalculatorService.*(..))
,(*
代表所有;..
代表参数,可以没有,也可以有多个),意思是说在执行com.jd.calculator.service
包中的CalculatorService
类 里的public int * ()
这种方法之前,要先执行加了@Before
注解的方法。加了这个
@Aspect
注解之后就会生成一个目标类(即@Before
所指向的类)的代理类,代理什么?代理的就是重复代码的那一部分。
(以下这段话不影响其他点的理解,可以不用看)那么还有一个问题,即是谁生成的这个代理类?直接告诉你答案,当在<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
中加proxy-target-class="true"
时,是由CGLIB
生成的代理类,这个代理类是目标类的子类;若不加proxy-target-class=""
,(默认值为false),则是由JDK生成的代理类,这个代理类是接口实现类。JoinPoint
就是连接点,它表示在程序中明确指明的点,就是说切点附近的东西,它里面常有的方法有:方法名 作用 例子 getSignature() getName() 获取目标方法名 add getDeclaringType().getSimpleName() 获取目标类的简单类名 CalculatorService getDeclaringTypeName() 获取目标类的完整类名 com.jd.calculator.service.CalculatorService Modifier.toString(JoinPoint.getSignature().getModifiers()) 获取目标方法声明的类型 public Object[] getArgs() 获取传入目标方法的参数[列表](从0开始) a,b getThis() 代理对象自己 com.jd.calculator.service.CalculatorService@28d18df5 getTarget() 被代理的对象 com.jd.calculator.service.CalculatorService@28d18df5
@After
、@AfterReturning
、@AfterThrowing
、@Around
:
好了,通过注解的方式实现AOP的方法已经说的大概了,到这里你是否会猜想:既然有了@Before
,那会不会有@After
,还真让你给说对了,而且不但有@After
,还有@AfterReturning
、@AfterThrowing
、@Around
。
这时聪明的你又想:既然都这样了,那它们会不会和@Before
的用法差不多啊!没错,果然smart,@After
、@AfterReturning
、@AfterThrowing
的用法确实和@Before
的用法差不多,只是@AfterReturning
和@AfterThrowing
还多了一些东西,且看下面慢慢道来:
@After
的用法和@Before
的用法完全一样,不一样的地方就是@After
修饰的方法是在目标方法执行后才执行的。@AfterReturning
,从名字中我们可以猜到被@AfterReturning
修饰的方法是在目标方法执行后才执行的,而且还会有一个返回通知:通常是计算的结果。实现的代码例如:@AfterReturning(value = "execution(public int com.jd.calculator.service.CalculatorService.*(..))",returning = "obj")//obj就是返回通知的内容public void afterReturning(JoinPoint js,Object obj){String name = js.getSignature().getName();//获取方法名System.out.println("The "+name+" method result:"+obj);}
@AfterThrowing
,同样的,我们可以看出被@AfterThrowing
修饰的方法是在目标方法执行后才执行的,那我就想问Throwing什么意思?异常的意思。意思就是说被@AfterThrowing
修饰的方法是在目标方法执行过程中出错,才执行的;如果目标方法没有出错,就不执行。实现的代码例如:@AfterThrowing(value = "execution(public int com.jd.calculator.service.CalculatorService.*(..))",throwing = "e")public void afterThrowing(JoinPoint js,Throwable e){System.out.println("错误的信息:"+e.getMessage());e.printStackTrace();//将错误打印出来}
你发现前几个注解都是见名知意,那@Around
是干什么用的?我们先来思考一番:如果要在执行一个目标方法时,我们想分别在目标方法执行前、执行后、执行过程出错时都先输出一些东西、且返回返回一些值,那这时我们要写四个方法,还要分别标注@Before
、@After
、@AfterReturning
和@AfterThrowing
四个注解,这样很麻烦,这时就可以用@Around
了,@Around
就是用来整合前四种的,它的结构如下:
try {try {doBefore();// @Before注解所修饰的方法method.invoke();// 执行目标对象内的方法(即目标方法)} finally {doAfter();// @After注解所修饰的方法}doAfterReturning();// @AfterReturning注解所修饰的方法
} catch (Throwable e) {doAfterThrowing();// @AfterThrowing注解所修饰的方法
}
上面的代码看懂了吧?好,看懂了我们来那上面的例子实战一番:
@Around("execution(public int com.jd.calculator.service.CalculatorService.*(..))")public Object around(ProceedingJoinPoint js){try {Object [] args = js.getArgs();//获取参数String name = js.getSignature().getName();//获取方法名Object result = null;try {//BeforeSystem.out.println("The "+name+" method begins");System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);//目标方法result = js.proceed();}finally {//AfterSystem.out.println("The "+name+" method ends");}//AfterReturningSystem.out.println("The "+name+" method result:"+result);//返回结果return result;}catch (Throwable e){//AfterThrowingSystem.out.println("错误的信息:"+e.getMessage());}return -1;}
在这里有几点需要特别注意一下:
- 首先就是参数类型,
@Around
修饰的方法必须声明ProceedingJoinPoint
类型的参数,该变量可以决定是否执行目标方法 ,这与上面四种生命的JoinPoint
类型不同; @Around
修饰的方法必须有返回值,且返回值类型为目标方法的返回值类型或其父类。而@Before
、@After
、@AfterRunning
和@AfterThrowing
修饰的方法是没有返回值的。
@Pointcut
还有一点就是我们在实现AOP时,都是这样写注解的:
@Before("execution(public int com.jd.calculator.service.CalculatorService.*(..))")
@After("execution(public int com.jd.calculator.service.CalculatorService.*(..))")
@AfterReturning(value = "execution(public int com.jd.calculator.service.CalculatorService.*(..))",returning = "obj")
@AfterThrowing(value = "execution(public int com.jd.calculator.service.CalculatorService.*(..))",throwing = "e")
@Around("execution(public int com.jd.calculator.service.CalculatorService.*(..))")
你发现了什么?哈哈,是不是还是代码冗余啊,所以这时可以用到另一个注解:@Pointcut
,pointcut表示一组joinPoint(连接点),以上重复的代码可以通过单独自定义一个@Pointcut
注解修饰的空方法来解决:
@Pointcut("execution(public int com.jd.calculator.service.CalculatorService.*(..))")public void pointcut(){}
然后其他注解调用即可:
@Before("pointcut()")
@After("pointcut()")
@AfterReturning("pointcut()")
@AfterThrowing("pointcut()")
@Around("pointcut()")
是不是简便多了?
@order(num)
如果一个目标方法匹配多个切面中相同类型增强方法(即上面四种注解修饰的方法),那这几个切面谁先执行、谁后执行呢?默认的都是按类名的字典顺序(A~Z)依次执行的;但如果加上 @order(num)
就会按照指定的顺序依次执行所有匹配的切面,其中num
数值越小,优先级越高(即被 @order(1)
修饰的切面 比 被 @order(2)
修饰的切面 先执行)。
好了,终于说完了用注解的方式去实现AOP,如果大家对AOP有了较好的理解,那接下来的用xml
文件来配置AOP就更容易理解了。
第二种方法:通过xml
文件来实现AOP:
这次我们通过xml
文件来实现AOP,也就是说像什么@Aspects
、@Order
、@Before
、@After
、@Order
等的注解都不需要再加了,
只需要自定义普通的类(但它通过配置后还起到切面的作用,所以我们就还叫它切面类)就可以了,例如:
package com.jd.aspects;import org.aspectj.lang.JoinPoint;public class CalculatorAspects {//定义普通的方法,然后在xml文件里进行配置public void before(JoinPoint js){Object [] args = js.getArgs();//获取参数String name = js.getSignature().getName();//获取方法名System.out.println("The "+name+" method begins");System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);}public void after(JoinPoint js){String name = js.getSignature().getName();//获取方法名System.out.println("The "+name+" method ends");}public void afterReturning(JoinPoint js,Object obj){String name = js.getSignature().getName();//获取方法名System.out.println("The "+name+" method result:"+obj);}public void afterThrowing(JoinPoint js,Throwable e){System.out.println("错误的信息:"+e.getMessage());} }
在Spring配置文件(也就是
application.xml
)里进行如下配置:
<!--因为上面的CalculatorAspects类未加'@Component'注解,所以这里需要先通过<bean></bean>来实例化对象--><bean id="calculatorAspects" class="com.jd.aspects.CalculatorAspects"></bean><!--直接在xml里配置代理类--><aop:config proxy-target-class="true"><aop:pointcut id="p" expression="execution(public int com.jd.calculator.service.CalculatorService.*(..))"/><aop:aspect ref="calculatorAspects" order="1"><aop:before pointcut-ref="p" method="before"></aop:before><aop:after pointcut-ref="p" method="after"></aop:after><aop:after-returning pointcut-ref="p" method="afterReturning" returning="obj"></aop:after-returning><aop:after-throwing pointcut-ref="p" method="afterThrowing" throwing="e"></aop:after-throwing></aop:aspect></aop:config>
我们会发现这个配置和使用注解有很多相似的地方,但更加简便:
其中的
proxy-target-class=""
和上面讲的一样,这里就不再赘述了;接着是
pointcut
和上面讲的也基本一样,这里指定一个id便于下面调用,expression=""
指向目标类;再往下:
<aop:aspect ref="calculatorAspects" order="1"></aop:aspect>
这个就是具体的某个切面类了,其中
ref=""
就是指向某个切面类,order=""
就是这个切面类的优先执行顺序;具体的:
<aop:before pointcut-ref="p" method="before"></aop:before><aop:after pointcut-ref="p" method="after"></aop:after><aop:after-returning pointcut-ref="p" method="afterReturning" returning="obj"></aop:after-returning><aop:after-throwing pointcut-ref="p" method="afterThrowing" throwing="e"></aop:after-throwing>
pointcut-ref=""
调用上面的pointcut
,指向目标类,method=""
执行切面类中具体的方法,其中after-returning
和after-throwing
分别多了一个returning=""
和throwing=""
。若直接使用around(环绕增强),则可以在
CalculatorAspects
类(代理类)中只定义一个aroud()
方法即可:package com.jd.aspects;import org.aspectj.lang.JoinPoint;public class CalculatorAspects {//定义普通的方法,然后在xml文件里进行配置public Object around(ProceedingJoinPoint js){try {Object [] args = js.getArgs();//获取参数String name = js.getSignature().getName();//获取方法名Object result = null;try {//beforeSystem.out.println("The "+name+" method begins");System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);//目标对象中的方法result = js.proceed();}finally {//afterSystem.out.println("The "+name+" method ends");}//afterReturningSystem.out.println("The "+name+" method result:"+result);return result;}catch (Throwable e){//AfterThrowingSystem.out.println("+++++++++++++++"+e.getMessage());}return -1;} }
需要注意的点也与上面的一样!
在:
<aop:aspect ref="calculatorAspects" order="1"></aop:aspect>
里只需要写入以下配置即可:
<aop:around pointcut-ref="p" method="around"></aop:around>
好了(长舒一口气…),终于把AOP相关的主要内容给说完了,不知你从我这里收获到一些没有?
相关文章:

ES6中的Promise详解
Promise 在 JavaScript 中很早就有各种的开源实现,ES6 将其纳入了官方标准,提供了原生 api 支持,使用更加便捷。 定义 Promise 是一个对象,它用来标识 JavaScript 中异步操作的状态(pending, resolve, rejectÿ…

Jquery 将表单序列化为Json对象
大家知道Jquery中有serialize方法,可以将表单序列化为一个“&”连接的字符串,但却没有提供序列化为Json的方法。不过,我们可以写一个插件实现。 我在网上看到有人用替换的方法,先用serialize序列化后,将&替换成…

ASP.NET常用函数
Abs(number) 取得数值的绝对值。 Asc(String) 取得字符串表达式的第一个字符ASCII 码。 Atn(number) 取得一个角度的反正切值。 CallByName (object, procname, usecalltype,[args()]) 执行一个对象的方法、设定或传回对象的属性。 CBool(expression) 转换表达式为Boolean …

简单快速修改大量重复代码(Intellij IDEA)
血与泪的教训啊!!!刚开始不知道,一味地疯狂点鼠标和键盘,点到手抽筋才想起来百度一下如何快速修改大量重复代码,呜呜呜~~~ 给大家分享一下吧,可以节约大家大量的时间哦: …

5.3Role和Claims授权「深入浅出ASP.NET Core系列」
5.3Role和Claims授权「深入浅出ASP.NET Core系列」 原文:5.3Role和Claims授权「深入浅出ASP.NET Core系列」希望给你3-5分钟的碎片化学习,可能是坐地铁、等公交,积少成多,水滴石穿,码字辛苦,如果你吃了蛋觉得味道不错&…

電子商務新紀元-WebService With BizSnap
電子商務新紀元-WebService With BizSnap WebService SOAP(Simple Object Access Protocol) Web Services Description Language (WSDL) DELPHI 的SOAP 撰寫WebService Server 程式 撰寫Client 端程式 魔法的秘密 傳送複雜型態資料(Complex Type) 傳送檔案或圖形 處理資料庫 C…

mysql优化1
1.以空间换时间,减少连表查询的次数,适当增加冗余字段 例如: 计算的字段,可以事先统计完,方数据库中,来一个加一个,而不用现场计算 2.字段类型: 整型 > date,time >enum >char >varchar >blob,text 字符串需要考虑字符集和校对集,因此比整型慢 time会考虑时期,用…

用XP做服务器突破10人限制
用XP做服务器突破10人限制用微软提供的小工具 MetaEdit,最新版本是2.2。下载地址:http://download.microsoft.com/download/iis50/Utility/5.0/NT45/EN-US/MtaEdt22.exe安装好以后将树型目录展开至LM \ W3SVC直接在W3SVC文件夹上单击,选择右边列表中Name…

Java反射(详述版)
一、什么是反射? 我们先来看一个例子: package venus; public class Student {public String name;public Student(){System.out.println("无参构造方法");}public void doHomework(){System.out.println(name "正在做作业~~~");…

s9.16作业,员工信息表
转载https://blog.csdn.net/qq_35883464/article/details/83151464 实现员工信息表文件存储格式如下:id,name,age,phone,job1,Alex,22,13651054608,IT2,Egon,23,13304320533,Tearcher3,nezha,25,1333235322,IT 现在需要…

Dubbo+zookeeper使用方法以及注意事项
Dubbozookeeper使用方法以及注意事项最近在一个项目中想做一个数据库查询的服务,目的是将数据库查询这块从程序中脱离出来,形成一个公共的服务平台,大家都可以调用,经过考虑决定选用Dubbozookeeper这个经典的组合来实现࿰…

淘淘经受了一次考验...
18日去香港给一个潜在客户做了一次演示,顺便帮淘淘买了一罐奶粉,因为听说香港的奶粉质量比较好,是原装进口的。下午6点多回到深圳,没想到,听到一个不太好的消息。淘淘的奶奶推车带淘淘出去的时候,不小心&am…

使用MasterPage遇到的问题
最近重新拿起.NET,发现一切变的那么的陌生了,发展得真是快啊。。。在使用MasterPage时,要从一个页面的Form提交数据到另一个页面的Form,应该如何处理?不使用MasterPage的时候,可以直接使用PostBackUrl"…

Machine Learning——DAY1
监督学习:分类和回归 非监督学习:聚类和非聚类 1.分类和聚类的区别: 分类(Categorization or Classification)就是按照某种标准给对象贴标签(label),再根据标签来区分归类。 聚类是指事先没有“标签”而通过某种成团分析找出事物之…

Java的注解
一、注解的概念: 注解并不是一开始就有的,JDK5之前是没有注解的,JDK5及其以后JDK版本才开始支持Java注解! Java注解(Annotation)也叫做元数据,以注解名在代码中存在,它是一种在源代码…

activemq的学习,第一篇
本地的activemq的地址: http://localhost:8161/admin/ win10的启动avtivemq E:\Program Files\ActiveMQ\apache-activemq-5.15.3\bin\win64 win64里面的activemq.bat 消息队列的学习 学习地址2 这是spring集合activemq的地址:github pom.xml引入的依赖&a…

CS Tip 16: 利用注释
译自: http://soup.co.za/weblog/archive/2006/04/07/CS-Tip-16_3A00_-Commenting-out-controls.aspx 当您在修改皮肤时您可以修改任何HTML标记,但是除了带有runat"Server"的除外,删除掉将会产生错误 如果您不想某个控件显示在页面上您可以注释…

今天没有浪费时间,我努力了
7月12日经过暴雨洗礼过的清晨,我晚起了一会,我伧促的洗了把脸,瞪起朦胧胧的双眼,迈着疲惫步子,重复着这条凌乱的街道,10分钟多一点,我就到了公交站点,像往常一样,挤上了8…

动态规划——洛谷_P1057传球游戏
题目: 题目描述 上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球&…

多线程1(进程、[创建]线程与生命周期)
一、进程与线程 什么是进程?我们先说说什么是程序? 程序(Program)是为实现特定目标或解决特定问题而用计算机语言(比如Java、C等)编写的命令序列的集合。 进程(process)就是指一个程…

网络操作系统第四章
1. 磁盘的数据结构包括哪些内容? 答:分区,卷,磁盘分区,主分区,扩展分区,逻辑分区,逻辑驱动器,引导分区。 2. 什么是基本磁盘和动态磁盘? (…

广播风暴系列专题(一)广播风暴:发现-端口
近日防火墙经常地检测到"svchost UDP/连入192.168.1.255/137 192.168.1.66/137 1055656/1065732 UDP_WAIT 过滤 8:48:31 C:\WINDOWS\system32\svchost.exe";"services UDP/连出 192.168.1.21/137 192.168.1.255/137 920736/913476 UDP_WAIT 过滤 11:46:3…

显示一个顶层的提示信息
vb里常作,大概的思路就是显示一个顶层的窗体,提示暂时不要动。c#里更简单了MsgDlg msgnewMsgDlg(); msg.TopMosttrue; msg.Show(); System.Windows.Forms.Application.DoEvents();

ArcGIS Engine开发-TOCControl中实现图层的拖放
TOCControl非常好,不用写一行代码就可以将整个地图的图层信息况显示出来;TOCControl也非常坏,提供的接口非常少,我认为有用的只有三个:HitTest,SetBuddyControl,Update,而且Update方法一执行,整…

多线程2(常用的方法:join、interrupt、currentThread、isAlive、setDaemon...)
常用的方法: 1、join()方法:join()方法:执行该方法的线程进入阻塞状态,直到调用该方法的线程结束后再由阻塞状态转为就绪状态。 示例: package venus;import java.util.Date;public class Test {public static void m…
Oracle总结第二篇【视图、索引、事务、用户权限、批量操作】
前言 在Oracle总结的第一篇中,我们已经总结了一些常用的SQL相关的知识点了…那么本篇主要总结关于Oralce视图、序列、事务的一些内容… 在数据库中,我们可以把各种的SQL语句分为四大类… (1)DML(数据操纵语言ÿ…

物联网应用介绍
•物联网的研究背景(概念 | 本质 | 特征 | 发展现状)物联网是新一代信息技术的高度集成和综合运用,已成为全球新一轮科技革命与产业变革的核心驱动和经济社会绿色、智能、可持续发展的关键基础与重要引擎。国家十三五规划纲要明确提出“积极推…

Oracle使用手册(三)---存储过程与触发器
--存储过程/**//*--1.过程的语法结构--参见:http://newland.cnblogs.com/archive/2006/04/05/367531.html--2.执行存储过程begin 存储过程名;end;--创建好的存储过程可以被任何程序调用*/--3.带参数的存储过程/**//* 参数类型 在PL/SQL过程中,可以有3种类型的…

数据结构之【线性表】(顺序表、链表的基本操作实现)
概念线性表:是N个数据元素的有限序列。 顺序表:用一组地址连续的存储单元依次存储【线性表 】的数据元素。(区别于有序表:表中的数据元素存在非递增或非递减有序) 链表:用一组任意的存储单元来存储【线性表…

基于android的天气预报的设计与实现
目录 应用开发技术及开发平台介绍应用需求分析应用功能设计及其描述应用UI展示①开发技术: 本系统是采用面向对象的软件开发方法,基于Android studio开发平台,以Android作为本系统的开发语言实现音乐播放器预定的需求功能。 ②平台介绍 硬件平…