注解 & 动态代理

た 入场券 2022-12-01 12:17 437阅读 0赞

1、设置Eclipse,关联Tomcat

开发过程中,环境发生变化(重新关联tomcat)。

  • 删除servers窗口下的tomcat
  • Project Explorer 窗口下的server
  • 重新关联window —> preferences —> servers —> runtime —> tomcat

2、注解

2.1 什么是注解?
注解和接口、类一样,都是属于数据类型。


2.2 注解作用
(1)编译检查
(2)配置 (后期使用最多)
(3)生成帮助文档


2.3 注解的特点
(1)注解可以在变量,方法,类之上加载。

(2)注解可以有属性也可以没有属性
@Override @Test(timeout=1000)

(3)注解有作用范围(源码,编译期间,运行期间)

  • 源码期间有效: 例如String类之上的@Author,@Since,@See
    作用:使用命令javadoc命令将当前的源码生成帮助文件,可以识别String类上的相关的注解。
  • 编译期间有效:@Override,@Deprecated,@Suppresswarning
    作用:告诉编译器部分信息。
  • 运行期间有效::@Test
    作用:当我们再当前代码上以Junit方式运行时,Junit会运行方法上包含@Test注解的方法。

2.4 回顾JDK中出现的3种注解
@Override,@Deprecated,@Suppresswarning


2.5 自定义注解
(1)格式:

  1. public @interface 注解名称{
  2. public 属性类型 属性名称1();
  3. public 属性类型 属性名称2() default 默认值;
  4. }

(2)自定义注解属性支持的类型:
支持基本数据类型(8种)。
String,Class,Annotation(非自定义注解),枚举类型。
以及以上类型的一维数组类型。

(3)注解作用:标注作用,配置信息等。

(4)配置:开发的时候部分信息不希望写死在程序中。

例如:数据库的用户名和密码,可以将用户名和密码存放在.txt,.properties,.xml文件中,利用程序来读取文件中的内容。

(5)框架:一大堆工具类组合,目的是加速项目开发。

后期的学习中,框架部分hibernate,spring,struts2很多信息需要配置,提供了2种形式配置:(xml,注解)

(6)什么时候用注解来做配置?
如果配置信息不会发生的修改,例如servlet路径,建议使用注解的形式。

如果配置信息需要发生频繁的修改,例如数据库的用户名和密码信息,建议采用传统方法 (.txt, .properties,.xml)。

测试使用自定义注解

  1. @MyAnno01(timeout=100,c=java.util.Date.class,strs={ "aaa","bbb"})
  2. public void test01(){
  3. }

(7)通过反射读取字节码上的注解信息


2.6案例:模拟Junit

(1)自定义注解@MyTest
通过元注解@Rentention,@Target声明当前注解作用域,以及目标对象,如果没有声明,在运行期间是无法获取到注解的信息。

(2)定义UserDao
创建4个方法addUser,delUser,uptUser,getUser,在前三个方法上加载注解。

(3)定义类MyJunit,模拟JUnit
将UserDao.class文件加载到内存,获取到字节码文件上所有的方法。遍历方法,判断每个方法上是否加载了@MyTest注解,如果当前方法上设置@MyTest,执行当前的方法。

注解要求:
开发中地位:类似dom4j解析XML文件,XML文件的解析程序员不会去解析,配置XML文件。

后期的开发中一般不会自定义注解,反射读取注解信息。


3、使用动态代理解决网站的字符集编码问题

3.1 什么是设计模式?
软件开发过程中,遇到相似问题,将问题的解决方式抽取模型(套路)。
例如:单例,工厂,适配器,装饰者,动态代理等。

3.2 谷歌汽车场景
(1)假设java设计了汽车开发约定
public interface ICar { start,run,stop三个方法}

(2)Google实现了这个接口
public class GoogleCar implements ICar { }

如果希望在将谷歌Car接入到生态圈平台时,增强汽车启动功能。
使用装饰者模式。

场景:二次开发的时候,无法获取到源码,无法使用继承前提下,要对已经存在对象上的功能进行增强。

前提条件:可以获取到被装饰的对象GoogleCar实现的所有接口。

实现思路:自定定义装饰类实现ICar接口,为自定义装饰类传递被装饰的对象。

弊端:如果被实现的接口中的方法过多,装饰类中的方法过多冗余。


3.3 动态代理模式

原理:通过JVM在内存中创建类似MyCar.class文件。

要创建MyCar.class文件告诉虚拟机:

  • 被创建的字节码文件上应该有多少方法。
  • 被创建的字节码上的方法如何来实现。

测试代码:

  1. // 假设这是Sun公司定义的一套规范,无人驾驶汽车接口
  2. public interface ICar {
  3. void start();
  4. void run();
  5. void stop();
  6. }
  7. // 这是Google公司对ICar接口的实现类
  8. public class GoogleCar implements ICar {
  9. @Override
  10. public void start() {
  11. System.out.println("GoogleCar开始启动!");
  12. }
  13. @Override
  14. public void run() {
  15. System.out.println("GoogleCar正在行驶!");
  16. }
  17. @Override
  18. public void stop() {
  19. System.out.println("GoogleCar最后停车!");
  20. }
  21. }
  22. public class Test01 {
  23. public static void main(String[] args) {
  24. // 第一个参数:固定值,指定用哪个字节码加载器,去加载内存中创建出的字节码文件
  25. // 第二个参数:正在被创建的字节码文件中应该有哪些方法
  26. // 第三个参数:被创建的字节码上的各个方法应该怎么处理
  27. ICar car = (ICar) Proxy.newProxyInstance(Test01.class.getClassLoader(), GoogleCar.class.getInterfaces(), new InvocationHandler() {
  28. @Override
  29. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  30. // 如果当前方法是start()方法
  31. if (method.getName().equalsIgnoreCase("start")) {
  32. // 在这里对start()方法进行功能加强
  33. System.out.println("启动之前,查询天气!");
  34. System.out.println("启动之前,查询路况!");
  35. }
  36. // 在这里执行原有的方法
  37. method.invoke(new GoogleCar(), args);
  38. return null;
  39. }
  40. });
  41. car.start();
  42. car.run();
  43. car.stop();
  44. }
  45. }

3.4 什么是字节码加载器?

jdk有一些程序,专门将各种字节码文件加载到内存。
这类程序简称为字节码加载器。

如何将字节码文件class文件加载到内存?
底层实现过程,利用IO流技术,获取到文件中的数据加载到内存。

字节码加载器:3种

  • 系统引导类加载器
    这类字节码加载器主要用于平时使用比较频繁的类,而且要求加载速度一定要快,比如说String.class、int.class等。
    这种加载器不是java类,底层使用c或者c++写的。
  • 扩展类加载器
    jre文件下的ext包内的都是通过扩展类加载器加载到内存的,其底层也是一个java类。
  • 应用类加载器
    程序员自己写的所有类都属于应用类,获取应用类的加载器就是应用类加载器。底层通过Java实现。

3.5 案例:动态代理解决全站乱码问题

步骤:
1、new DynamicWeb Project —> Index.html

  1. <h3>post方式提交中文</h3>
  2. <form action="/day18_v3/ServletDemo" method="post">
  3. User:<input type="" name="username"/><br/>
  4. <input type="submit"/>
  5. </form>
  6. <h3>get方式提交中文</h3>
  7. <form action="/day18_v3/ServletDemo" method="get">
  8. User:<input type="" name="username"/><br/>
  9. <input type="submit"/>
  10. </form>

2、ServletDemo

要求实现:无论是在post/get方法,执行以下语句不存在中文乱码问题。
String username = request.getParameter(“username”);
System.out.println(username);

3、在过滤器中,为request上的getParameter()功能进行增强

思路:
判断当前的请求是get/post,用request.getMethod();方法。
如果是post,设置一句话:request.setCharacterEncoding(“utf-8”);
放行。

如果是get,获取value:String value = request.getParameter(name);
将value进行转码:String newValue = new String(value.getBytes(“iso-8859-1”), “utf-8”);
放行。

测试代码:

  1. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException {
  2. // 先将request对象转换为HTTPServletRequest类型,并设置为final修饰
  3. final HttpServletRequest req = (HttpServletRequest)request;
  4. // 生成代理对象,增强req对象的getParameter()方法
  5. HttpServletRequest myRequest = (HttpServletRequest)Proxy.newProxyInstance(this.class.getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {
  6. @Override
  7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8. Object obj = null;
  9. // 如果当前方法是getParameter()方法
  10. if (method.getName().equalsIgnoreCase("getParameter")) {
  11. // 在这里对getParameter()方法进行功能加强
  12. String md = req.getMethod();
  13. if ("get".equalsIgnoreCase(md)) { // 如果是get请求
  14. // 将请求数据转码
  15. String value = (String)method.invoke(req, args);
  16. return new String(value.getBytes("iso-8859-1"), "utf-8");
  17. } else if ("post".equalsIgnoreCase(md)) { // 如果是post请求
  18. // 设置编码格式
  19. req.setCharacterEncoding("utf-8");
  20. }
  21. }
  22. // 在这里执行原有的方法
  23. obj = method.invoke(req, args);
  24. return obj;
  25. }
  26. });
  27. // 放行代理对象
  28. chain.doFilter(myRequest, response);
  29. }

发表评论

表情:
评论列表 (有 0 条评论,437人围观)

还没有评论,来说两句吧...

相关阅读