代理是学习Spring的一个重要基础,今天就来探讨一下这个技术。
理解代理
代理这个词对于广告满天飞的现代社会应该是很常见了,和它具有相同意义还有中介、经纪人等词。我们就以找影视明星拍片为例来理解代理。
假如你有一个很好的剧本,现在想找A明星来做主角,可以两种方式:第一种是直接找A明星本人,第二种是找A明星的经纪人。但是明星的主要功能是拍戏,如果诸如报酬、档期、宣传等工作也让A明星来负责,势必会减少他/她的拍戏时间,所以更好的方式是去寻求他/她的经纪人,让经纪人来做这些拍戏以外的工作
但同时经济人也得有和明星A一样的功能,比如A会演戏、唱歌、跳舞,那么经纪人也得有这些功能,只不过经纪人的功能是让明星A去完成的,经纪人本身只提供这种服务的接口。而我们所说的明星A便是目标对象,经纪人便是代理对象。
这个和我们的代理有很多相似的地方,假如有一个方法(设为M1)的功能是把UTF-8编码下的字符转化成GBK编码下的字符。那么这个方法的功能就是进行转化、对于判断传入的是不是UTF-8编码下的字符这种事情应该传入之前就处理完成,所以这时候就要有一个代理方法(设为M2)在M1执行之前做个处理。当然M1执行完毕之后M2也可能做一些处理。
代理对象的要点
1.代理对象存在的价值主要用于拦截对目标对象的访问。
2.代理对象应该具有和目标对象相同的方法。
动态代理
在我们刚才的解释中,每个目标对象都要有一个实在的代理对象,但如果说能在程序运行期间给我们动态生成一个代理对象可以大大减小编写的代码的压力。所以动态代理的概念就是:不用手动编写一个代理对象,不需要编写与目标对象相同的方法,运行时在内存中 动态生成代理对象(字节码对象级别的代理对象)。
JDK提供的动态代理
基于万物皆对象的思想,JDK1.5之后为我们提供了用于专用于动态生成代理对象的类:java.lang.reflect.Proxy。有一个很重要的静态方法:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHander h)
在解释参数之前,我们先看一个接口InvocationHander,JDK对它的解释是:Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the method of its invocation handler.(每个代理实例都有一个关联的调用处理程序。当在代理实例上调用方法时,将方法调用编码并调度到其调用处理程序的方法。) 它只有一个invoke()方法。我们之后执行时真正起作用的也是这个方法。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
对newProxyInstance()方法参数的解释在下面的例子中。
- 明星接口
public interface BrightStar {
public void sing();
public String dancing();
public String ShootFilm(String filmName);
}
- A明星类
public class StarA implements BrightStar{
@Override
public void sing() {
System.out.println("A is singing...");
}
@Override
public String dancing() {
return "Hai cao wu";
}
@Override
public String ShootFilm(String filmName) {
return filmName;
}
}
- 测试动态代理
public class Test {
public static void main(String[] args) {
StarA starA = new StarA();
BrightStar newProxyInstance = (BrightStar)Proxy.newProxyInstance(
//代理类的类加载器,获取目标类加载器即可
StarA.class.getClassLoader(),
//代理类应该实现的接口,由于代理类和目标类需要继承相同的接口,使用目标类的接口即可
StarA.class.getInterfaces(),
//使用匿名内部类传入InvocationHandler的实例
new InvocationHandler() {
/*
* proxy:传入代理对象。
* method:被执行的方法。
* args:传入的参数。
* 例子: newProxyInstance.ShootFilm("我不是药神");
* proxy:newProxyInstance; method:ShootFilm; args:"我不是药神"
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行前的操作
System.out.println("before");
Object invoke = method.invoke(starA, args);
//执行后的操作
System.out.println("after");
return invoke;
}
});
//调用方法
newProxyInstance.sing();
System.out.println("------------------------------------");
String dancing = newProxyInstance.dancing();
System.out.println(dancing);
System.out.println("------------------------------------");
String film = newProxyInstance.ShootFilm("我不是药神");
System.out.println(film);
/*
Console :
before
A is singing...
after
------------------------------------
before
after
Hai cao wu
------------------------------------
before
after
我不是药神
*/
}
}
下面介绍一个经典案例,使用动态代理解决Web工程的全局编码问题。
- 前端代码
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert_Title_Here</title>
</head>
<style>
</style>
<body>
<!--主体部分-->
<h2>get方式</h2>
<form action="/ProxySolveCoding/test" method="get">
<input name="name" type="text"/>
<input type="submit" value="提交"/>
</form>
<h2>post方式</h2>
<form action="/ProxySolveCoding/test" method="post">
<input name="name" type="text"/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
- Servlet
@WebServlet("/test")
public class Test extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = request.getParameter("name");
System.out.println(name);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
- filter
@WebFilter(urlPatterns="/*")
public class FilterCoding implements Filter {
public void init(FilterConfig filterConfig) throws ServletException { }
public void destroy() { }
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final HttpServletRequest req = (HttpServletRequest)request;
HttpServletRequest proReq = (HttpServletRequest)Proxy.newProxyInstance(
req.getClass().getClassLoader(),
req.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equalsIgnoreCase("getParameter")) {
String gm = req.getMethod();
if(gm.equalsIgnoreCase("get")) {
//处理get方式的请求
String before = (String) method.invoke(req, args);
System.out.println(before);
String after = new String(before.getBytes("iso-8859-1"), "utf-8");
return after;
}else {
//处理post形式的请求
req.setCharacterEncoding("utf-8");
}
}
return method.invoke(req, args);
}
});
chain.doFilter(proReq, response);
}
}
但是小码农这个代码在自己电脑是有问题的。在我电脑上的URL默认编码是UTF-8,因为我之前调过,大家使用应该是没有问题的。
网友评论