基于JAVA的RPC简单实现。
RPC即Remote Procedure Call 是一种进程间通信方式。RPC是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议,是分布式系统常见的一种通信方法。RPC采用客户机/服务器模式。
pom.xml文件,引入依赖。
4.0.0
com.wc
rpcProject-framework
pom
1.0.0-SNAPSHOT
rpc-common
rpc-proto
rpc-codec
rpc-transport
rpc-server
rpc-client
rpc-example
4.13
1.18.8
1.7.26
1.2.3
2.5
9.4.28.v20200408
1.2.62
1.8
junit
junit
${junit.version}
test
org.projectlombok
lombok
${lombok.version}
org.slf4j
slf4j-api
${slf4j-api.version}
ch.qos.logback
logback-classic
${logback-classic.version}
com.alibaba
dubbo
2.5.4
common-io
commons-io
2.5
org.eclipose.jetty
jetty-servlet
${jetty-servlet.version}
com.alibaba
fastjson
${fastjson.version}
org.apache.maven.plugins
maven-compiler-plugin
3.8.0
${java.version}
UTF-8
ReflectionUtils(反射工具类)
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
/**
* 反射工具类
*/
@Slf4j
public class ReflectionUtils {
/**
* 根据传入的Class创建相应的对象
* @param clazz 待创建对象的类
* @param 对象类型
* @return 创建的对象
*/
public static T newInstance(Class clazz) throws Exception{
try{
return clazz.newInstance();
}catch (Exception e){
log.error(e.getMessage(),e);
throw new IllegalStateException(e);
}
}
/**
* 获取传入类自有的公共方法
* @param clazz 传入类
* @return 传入类自有的公共方法
* @throws Exception
*/
public static Method[] getPublicMethods(Class clazz)throws Exception{
try{
//调用getMethods方法输出的是自身的public方法和父类Object的public方法
//调用getDeclaredMethods方法输出的是自身的public、protected、private方法
Method[] methods=clazz.getDeclaredMethods();
ArrayList list=new ArrayList<>();
for(Method m:methods){
//用Modifier类的函数来判断它是否属被某个修饰符修饰
//用getModifiers()得到了类(或者字段、或者方法)的访问修饰符int值
if(Modifier.isPublic(m.getModifiers())){
list.add(m);
}
}
return list.toArray(new Method[0]);
}catch (Exception e){
log.error(e.getMessage(),e);
throw new IllegalStateException(e);
}
}
/**
* 调用传入对象的传入方法(静态方法obj传null)
* @param obj 传入对象
* @param method 待调用方法
* @param args 传入参数
* @return
* @throws Exception
*/
public static Object invoke(Object obj,Method method,Object... args)throws Exception{
try{
return method.invoke(obj,args);
}catch (Exception e){
log.error(e.getMessage(),e);
throw new IllegalStateException(e);
}
}
}
对网络传输的数据进行序列化和反序列化,示例中是基于fastjson实现的序列化和反序列,也可以根据其它的依赖或自己定义来实现序列化和反序列化。注:如果是使用IO流来实现序列化和反序列,则可以不需要该模块。
反序列化接口
/**
* 反序列化接口
*/
public interface Decoder {
/**
* 将序列化的字节数组转为对象
* @param bytes
* @param clazz
* @param
* @return
*/
T decode(byte[] bytes,Class clazz);
}
基于fastjson的反序列化实现
import com.alibaba.fastjson.JSON;
/**
* 基于JSON的反序列化实现
*/
public class JSONDecoder implements Decoder{
/**
* 将序列化的字节数组转为对象
* @param bytes
* @param clazz
* @param
* @return
*/
@Override
public T decode(byte[] bytes, Class clazz) {
return JSON.parseObject(bytes,clazz);
}
}
序列化接口
/**
* 序列化接口
*/
public interface Encoder {
/**
* 将对象转为字节数组
* @param obj
* @return
*/
byte[] encode(Object obj);
}
基于fastjson的序列化实现
import com.alibaba.fastjson.JSON;
/**
* 基于Json的序列化实现
*/
public class JSONEncoder implements Encoder {
/**
* 将对象转为字节数组
* @param obj
* @return
*/
@Override
public byte[] encode(Object obj) {
return JSON.toJSONBytes(obj);
}
}
对网络传输中请求和响应里的信息进行封装,示例中是自定义的。注:可以根据需求自定义,也可以继承原生HttpRequest和HttpResponse来实现或者使用第三方的。该模块如不需要,可去掉。
网络传输的一个端点
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 表示网络传输的一个端点
*/
@Data//属性getset方法,ToString方法
@AllArgsConstructor//创建一个带所有字段的构造方法
@NoArgsConstructor//创建一个无参构造方法
public class Peer {
private String host;//网络传输地址
private int port;//网络传输端口
}
请求服务类
/**
* 表示请求服务
*/
@Data//属性getset方法,ToString方法
@AllArgsConstructor//创建一个带所有字段的构造方法
@NoArgsConstructor//创建一个无参构造方法
public class ServiceDescriptor {
private String clazz;//类
private String method;//方法
private String returnType;//返回方式
private String[] parameterTypes;//返回参数
public static ServiceDescriptor from(Class clazz, Method method){
ServiceDescriptor sd=new ServiceDescriptor();
sd.setClazz(clazz.getName());
sd.setMethod(method.getName());
sd.setReturnType(method.getReturnType().getName());
Class[] parameterClass=method.getParameterTypes();
String[] parameterArr=new String[parameterClass.length];
for(int i=0;i
请求封装类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 表示RPC的一个请求
*/
@Data//属性getset方法,ToString方法
@AllArgsConstructor//创建一个带所有字段的构造方法
@NoArgsConstructor//创建一个无参构造方法
public class Request {
private ServiceDescriptor service;//请求服务
private Object[] parameters;//请求参数
}
响应封装类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 表示RPC的一个响应
*/
@Data//属性getset方法,ToString方法
@AllArgsConstructor//创建一个带所有字段的构造方法
@NoArgsConstructor//创建一个无参构造方法
public class Response {
private int code=-1;//响应码,0-成功,非0失败
private String message="请求失败";//响应信息
private Object data;//响应数据
//响应集
public static final int FAIL=-1;//失败
public static final int SUCC=0;//成功
public static final int ERROR=-999;//系统错误
}
该模块是处理服务器和客户端的网络通信方式,本案例使用的是Http协议的网络通信方式,如有需要也可以使用别的通信方式。服务器使用了jetty,如有需要
pom.xml
commons-io
commons-io
${commons-io.version}
org.eclipse.jetty
jetty-servlet
${jetty-servlet.version}
com.wc
rpc-proto
${project.version}
网络请求服务端接口
/**
* 网络请求服务端接口
*/
public interface TransportServer {
/**
* 初始化
* @param port
* @param handler
*/
void init(int port,RequestHandler handler);
/**
* 启动服务并监听
*/
void satrt();
/**
* 结束服务
*/
void stop();
}
网络请求处理器接口
import java.io.InputStream;
import java.io.OutputStream;
/**
* 网络请求处理器:处理网络请求的Handler
*/
public interface RequestHandler {
/**
*
* @param inputStream
* @param outputStream
*/
void onRequest(InputStream inputStream, OutputStream outputStream);
}
Http网络服务器实现
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* http协议的网络服务端(http是短连接)
*/
@Slf4j
public class HttpTransportServer implements TransportServer {
private RequestHandler handler;//请求处理器
private Server server;//jetty服务器
@Override
public void init(int port, RequestHandler handler) {
this.handler=handler;
this.server=new Server(port);
//servlet接收请求
ServletContextHandler ctx=new ServletContextHandler();
server.setHandler(ctx);
//服务台
ServletHolder holder=new ServletHolder(new RequestServlet());
ctx.addServlet(holder,"/*");
}
@Override
public void satrt() {
try {
server.start();
server.join();
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage(),e);
}
}
@Override
public void stop() {
try {
server.stop();
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage(),e);
}
}
/**
*
*/
class RequestServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
log. info("client connect") ;
ServletInputStream input = req.getInputStream();
ServletOutputStream output = resp.getOutputStream();
if(handler!=null){
handler.onRequest(input,output);
}
output.flush();
}
}
}
网络请求客户端接口
import com.wc.rpc.proto.Peer;
import java.io.InputStream;
/**
* 网络请求客户端接口
*/
public interface TransportClient {
/**
* 创建连接
* @param peer
*/
void connect(Peer peer);
/**
* 发送数据,处理响应
* @param inputStream
* @return
*/
InputStream write(InputStream inputStream);
/**
* 关闭连接
*/
void close();
}
http协议的网络客户端实现
import com.wc.rpc.proto.Peer;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* http协议的网络客户端(http是短连接)
*/
public class HttpTransportClient implements TransportClient{
private String url;//请求地址+端口
@Override
public void connect(Peer peer) {
this.url="http://"+peer.getHost()+":"+peer.getPort();
}
@Override
public InputStream write(InputStream inputStream) {
try {
HttpURLConnection httpConn = (HttpURLConnection) new URL(url).openConnection();
httpConn.setDoOutput(true);//开启输出
httpConn.setDoInput(true);//开启读取
httpConn.setUseCaches(false);//关闭缓存
httpConn.setRequestMethod("POST");//设置请求方法:POST
httpConn.connect();//连接
IOUtils.copy(inputStream,httpConn.getOutputStream());//发送数据
int resultCode=httpConn.getResponseCode();//获取响应码
if(HttpURLConnection.HTTP_OK==resultCode){
return httpConn.getInputStream();
}else{
return httpConn.getErrorStream();
}
} catch (IOException e) {
//e.printStackTrace();
throw new IllegalStateException(e);
}
}
@Override
public void close() {
}
}
该模块负责注册生产者,接收消费者发送过来的请求,对请求进行识别处理并返回组装响应的结果。如果序列化与反序列化未使用自定义的,那该模块的中的相关代码可以去除。
pom.xml
com.wc
rpc-codec
${project.version}
com.wc
rpc-common
${project.version}
com.wc
rpc-proto
${project.version}
com.wc
rpc-transport
${project.version}
commons-io
commons-io
${commons-io.version}
server配置
import com.wc.rpc.codec.Decoder;
import com.wc.rpc.codec.Encoder;
import com.wc.rpc.codec.JSONDecoder;
import com.wc.rpc.codec.JSONEncoder;
import com.wc.rpc.transport.HttpTransportServer;
import com.wc.rpc.transport.TransportServer;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* server配置
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RpcServerConfig {
private Class extends TransportServer> transportClass= HttpTransportServer.class;
private Class extends Encoder> encodeClass= JSONEncoder.class;
private Class extends Decoder> decodeClass= JSONDecoder.class;
private int port=3000;//服务器端口
}
生产者对象类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.lang.reflect.Method;
/**
* 表示具体服务
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ServiceInstance {
private Object target;//对象
private Method method;//方法
}
对象管理类,负责注册生产者和查询对应的对象方法。
import com.wc.rpc.common.utils.ReflectionUtils;
import com.wc.rpc.proto.Request;
import com.wc.rpc.proto.ServiceDescriptor;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 管理rpc暴露的服务
*/
@Slf4j
public class ServiceManager {
private Map service;
public ServiceManager(){
this.service=new ConcurrentHashMap<>();
}
/**
* 将接口和对应的对象绑定到一起,并放入service中
* @param interfaceClass 接口的class
* @param bean 接口具体实现的子类的对象
*/
public void register(Class interfaceClass,T bean) {
try {
Method[] methods = ReflectionUtils.getPublicMethods(interfaceClass);
for(Method method:methods){
ServiceInstance sis = new ServiceInstance(bean, method);
ServiceDescriptor sd = ServiceDescriptor.from(interfaceClass, method);
service.put(sd,sis);
log.info("register service:{} {}",sd.getClazz(),sd.getMethod());
}
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage(),e);
}
}
public ServiceInstance lookup(Request request){
ServiceDescriptor sd=request.getService();
return service.get(sd);
}
}
Rpc服务器类
import com.wc.rpc.codec.Decoder;
import com.wc.rpc.codec.Encoder;
import com.wc.rpc.common.utils.ReflectionUtils;
import com.wc.rpc.proto.Request;
import com.wc.rpc.proto.Response;
import com.wc.rpc.transport.RequestHandler;
import com.wc.rpc.transport.TransportServer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import java.io.InputStream;
import java.io.OutputStream;
@Slf4j
public class RpcServer {
private RpcServerConfig config;
private TransportServer netServer;
private Encoder encoder;
private Decoder decoder;
private ServiceManager manager;
private RequestHandler handler=new RequestHandler(){
@Override
public void onRequest(InputStream inputStream, OutputStream outputStream) {
//生成响应对象
Response response=new Response();
try {
//获取传入数据
byte[] inBytes = IOUtils.readFully(inputStream, inputStream.available());
//反序列化生成请求对象
Request request=decoder.decode(inBytes,Request.class);
log.info("get Request:{}",request);
ServiceInstance instance = manager.lookup(request);
if(instance!=null){
//调用方法
Object obj =ReflectionUtils.invoke(instance.getTarget(),instance.getMethod(),request.getParameters())
response.setData(obj);
response.setCode(Response.SUCC);
response.setMessage("成功");
}else{
response.setCode(Response.FAIL);
response.setMessage("未找到该方法");
}
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage(),e);
response.setCode(Response.ERROR);
response.setMessage("error:"+e.getClass().getName()+":"+e.getMessage());
}finally{
try{
byte[] btyes = encoder.encode(response);
outputStream.write(btyes);
}catch (Exception e){
e.printStackTrace();
log.error(e.getMessage(),e);
}
}
}
};
public RpcServer(){
this(new RpcServerConfig());
}
public RpcServer(RpcServerConfig config) {
try{
this.config = config;
//网络服务
this.netServer= ReflectionUtils.newInstance(config.getTransportClass());
this.netServer.init(config.getPort(),this.handler);
//序列化
this.encoder=ReflectionUtils.newInstance(config.getEncodeClass());
this.decoder=ReflectionUtils.newInstance(config.getDecodeClass());
//服务管理
this.manager=new ServiceManager();
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage(),e);
}
}
/**
* 注册方法
* @param interfaceClass
* @param interfaceImplClass
* @param
*/
public void register(Class interfaceClass,Class> interfaceImplClass) {
try {
T bean= (T) interfaceImplClass.newInstance();
manager.register(interfaceClass,bean);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 启动方法
*/
public void start(){
netServer.satrt();
}
/**
* 停止方法
*/
public void stop(){
netServer.stop();
}
}
该模块负责连接服务器,将请求方法发送给服务器,对返回的结果进行解析并返给消费者。如果序列化与反序列化未使用自定义的,那该模块的中的相关代码可以去除。
pom.xml
com.wc
rpc-codec
${project.version}
com.wc
rpc-common
${project.version}
com.wc
rpc-proto
${project.version}
com.wc
rpc-transport
${project.version}
commons-io
commons-io
${commons-io.version}
客户端配置
import com.wc.rpc.codec.Decoder;
import com.wc.rpc.codec.Encoder;
import com.wc.rpc.codec.JSONDecoder;
import com.wc.rpc.codec.JSONEncoder;
import com.wc.rpc.proto.Peer;
import com.wc.rpc.transport.HttpTransportClient;
import com.wc.rpc.transport.TransportClient;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Arrays;
import java.util.List;
/**
* client配置
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RpcClientConfig {
private Class extends TransportClient> transportClass= HttpTransportClient.class;
private Class extends Encoder> encodeClass= JSONEncoder.class;
private Class extends Decoder> decodeClass= JSONDecoder.class;
private Class extends TransportSelector> selectorClass=RandomTransportSelector.class;
private int connectCount=1;
private List servers= Arrays.asList(new Peer("127.0.0.1",3000));
}
连接服务器的接口
import com.wc.rpc.proto.Peer;
import com.wc.rpc.transport.TransportClient;
import java.util.List;
/**
* 表示选择哪个server去连接
*/
public interface TransportSelector {
/**
* 初始化selector
* @param peers 可以连接的server端点信息
* @param count client与server可以建立多少个连接
* @param clazz client实现类的class
*/
void init(List peers,int count,Class extends TransportClient> clazz);
/**
* 关闭selector
*/
void close();
/**
* 选择一个transport与server交互
* @return
*/
TransportClient select();
/**
* 释放用完的client
* @param client
*/
void relase(TransportClient client);
}
TransportSelector的实现类,用于存储已经连接好的client
import com.wc.rpc.common.utils.ReflectionUtils;
import com.wc.rpc.proto.Peer;
import com.wc.rpc.transport.TransportClient;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* TransportSelector的实现类
*/
@Slf4j
public class RandomTransportSelector implements TransportSelector {
/**
* 用于存储已经连接好的client
*/
private List clients;
@Override
public synchronized void init(List peers, int count, Class extends TransportClient> clazz) {
count=Math.max(count,1);//两者取最大
try {
for(Peer peer:peers){
for(int i=0;i();
}
}
rpc客户端类
import com.wc.rpc.codec.Decoder;
import com.wc.rpc.codec.Encoder;
import com.wc.rpc.common.utils.ReflectionUtils;
import com.wc.rpc.proto.Peer;
import com.wc.rpc.proto.Request;
import com.wc.rpc.proto.Response;
import com.wc.rpc.proto.ServiceDescriptor;
import com.wc.rpc.transport.TransportClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@Slf4j
public class RpcClient {
private RpcClientConfig config;
private Encoder encoder;
private Decoder decoder;
private TransportSelector selector;
/**
* 无参构造方法
*/
public RpcClient(){
this(new RpcClientConfig());
}
/**
* 有参构造方法
*/
public RpcClient(RpcClientConfig rpcClientConfig){
try{
this.config=rpcClientConfig;
this.decoder=ReflectionUtils.newInstance(this.config.getDecodeClass());
this.encoder=ReflectionUtils.newInstance(this.config.getEncodeClass());
this.selector=ReflectionUtils.newInstance(this.config.getSelectorClass());
this.selector.init(this.config.getServers(),this.config.getConnectCount(),this.config.getTransportClass());
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage(),e);
}
}
/**
* 获取接口代理对象
* @param clazz
* @return
*/
public T getProxy(Class clazz){
return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//构造请求
Request request=new Request();
request.setService(ServiceDescriptor.from(clazz,method));
request.setParameters(args);
//通过网络把请求发送给server,等待server的响应
Response response=invokeRemote(request);
//获取数据
if(response==null||response.getCode()!=0){
throw new IllegalStateException("fail:"+response);
}
return response.getData();
}
/**
* 网络传输
* @param request
* @return
*/
private Response invokeRemote(Request request) {
TransportClient client=null;
Response response=null;
try{
client=selector.select();
byte[] bytes = encoder.encode(request);
//把请求发送给server,等待server的响应
Peer peer = config.getServers().get(0);
client.connect(peer);
InputStream revice = client.write(new ByteArrayInputStream(bytes));
//返回结果
byte[] resBytes = IOUtils.readFully(revice, revice.available());
response = decoder.decode(resBytes, Response.class);
}catch (Exception e){
e.printStackTrace();
log.error(e.getMessage(),e);
response=new Response();
response.setCode(Response.ERROR);
response.setMessage("invokeRemote Error:"+e.getClass()+":"+e.getMessage());
return response;
}finally {
if(client!=null){
selector.relase(client);
}
}
return response;
}
});
}
}
模拟外部调用
pom.xml
com.wc
rpc-client
${project.version}
com.wc
rpc-server
${project.version}
示例接口
public interface DemoService {
/**
* 示例方法01
* @param a
* @param b
* @return
*/
String method01(int a, int b);
/**
* 示例方法02
* @param c
* @param d
* @return
*/
String method02(int c, int d);
}
示例接口实现类
public class DemoServiceImpl implements DemoService {
@Override
public String method01(int a, int b) {
return "a="+a+",b="+b;
}
@Override
public String method02(int c, int d) {
return "c="+c+",d="+d;
}
}
示例中的生产者
import com.wc.rpc.server.RpcServer;
import com.wc.rpc.server.RpcServerConfig;
public class Provider{
public static void main(String[] args){
try{
//配置服务器
RpcServerConfig config=new RpcServerConfig();
config.setDecodeClass(JSONDecoder.class);
config.setEncodeClass(JSONEncoder.class);
config.setTransportClass(HttpTransportServer.class);
config.setPort(3000);
//生成带配置的服务器对象
RpcServer server=new RpcServer(config);
//将接口与对应的实现放入服务注册中心
server.register(DemoService.class,DemoServiceImpl.class);
//启动
server.start();
}catch (Exception e){
e.printStackTrace();
}
}
}
示例中的消费者
import com.wc.rpc.client.RandomTransportSelector;
import com.wc.rpc.client.RpcClient;
import com.wc.rpc.client.RpcClientConfig;
import com.wc.rpc.codec.JSONDecoder;
import com.wc.rpc.codec.JSONEncoder;
import com.wc.rpc.proto.Peer;
import com.wc.rpc.transport.TransportClient;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class Consumer{
public static void main(String[] args) throws Exception {
//配置客户端,和连接的服务器
RpcClientConfig config=new RpcClientConfig();
config.setDecodeClass(JSONDecoder.class);
config.setEncodeClass(JSONEncoder.class);
config.setTransportClass(HttpTransportClient.class);
config.setSelectorClass(RandomTransportSelector.class);
List list=new ArrayList();
list.add(new Peer("127.0.0.1",3000));
config.setServers(list);
//生成带配置的客户端对象
RpcClient client = new RpcClient(config);
//通过动态代理调用服务注册中心里相应的方法实现
CalcService service =client.getProxy(DemoService.class);
String method01= service.method01(1, 2);
String method02= service.method02(10, 5);
log.info("method01:"+method01);
log.info("method02:"+method02);
}
}
难点:动态代理。