Java基础增强-(注解,动态代理,类加载器,servlet3.0新特性)

1.   Annotation注解html

1.1.  Annotation概述

Annotation是JDK 5.0之后提供对元数据的支持,能够在编译、加载和运行时被读取,并执行相应的处理。所谓Annotation就是提供了一种为程序元素设置元数据的方法,可用于修饰包、类、构造器、方法、成员变量、参数和局部变量的声明,这些信息被存储在Annotation的“name=value”对中。java

Annotation能被用来为程序元素(类、方法、成员变量等)设置元数据,好比一段代码的做者或者告诉编译器禁止一些特殊的错误,不会影响代码的执行。mysql

1.2.  基本Annotation

在Java中提供了3个基本Annotation的用法,使用Annotation时要在其前面增长@符号,并把该Annotation看成一个修饰符使用,用于修饰它支持的程序元素。这3个基本Annotation都定义在java.lang包下,能够经过查看API文档来了解。web

  • @Override:限定重写父类方法。

@Override就是用来指定方法覆载的,它能够强制一个子类必须覆盖父类的方法。sql

 

public class Fruit {
    public void info(){
        System.out.println("这是一个水果,想吃吗?");
    }
}
public class Apple extends Fruit {
    @Override
    public void info() {
        System.out.println("这不只是一个水果,它是苹果.");
    }

 

 

若是Apple类的info()方法名写成了inf()的话,编译器会报错。值得注意的是,@Override只能修饰方法,不能修饰其余程序元素。数据库

  • @Deprecated:标示已过期。

@Deprecated用于表示某个程序元素已过期,当其余程序使用已过期的类、方法时,编译器将会给出警告。设计模式

 

public class Fruit {
    @Deprecated
    public void info(){
        System.out.println("这是一个水果,想吃吗?");
    }
}
public class DeprecatedTest {
    public static void main(String[] args) {
        // 使用info()方法时将会出现划线,表示该方法已过期.
        new Fruit().info();
    }
}

 

  • @SuppressWarnings:抑制编译器警告。

@SuppressWarnings表示被该Annotation修饰的代码取消显示指定的编译器警告。数组

 

public class SuppressWarningsTest {
    public static void main(String[] args) {
        @SuppressWarnings("rawtypes")
        /*
         * List集合在定义时,没有指定泛型类型.
         *  * 默认状况下,出现编译器警告.
         *  * 使用@SuppressWarnings注释后,取消警告信息.
         */
        List list = new ArrayList();
    }
}

 关于jdk的注解@SuppressWarnings详解缓存

@SuppressWarnings
J2SE 提供的一个批注或者注解。该批注的做用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默,即忽略这些警告信息。安全

在日常的编码过程当中,咱们常用到的是unchecked,serial这些。

@SuppressWarnings()中可传入一个字符串数组,数组中列出须要忽略的状况。
若是传入多种状况,这几种状况的处理同时执行。例如:

@SuppressWarnings({"unchecked","serial"})
public void test(){
  //
}


如果只忽略一种状况的话,就能够写成这样

@SuppressWarnings("unchecked")
public void test(){
  //
}


如下是主要的几种状况:
关键字 用途
deprecation  使用了已过期或者不推荐使用的类或方法时的警告
unchecked  执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型
fallthrough  当 Switch 程序块直接通往下一种状况而没有 Break 时的警告
path  在类路径、源文件路径等中有不存在的路径时的警告
serial  当在可序列化的类上缺乏 serialVersionUID 定义时的警告
all  关于以上全部状况的警告

 

1.3.  自定义Annotation

自定义一个Annotation类型使用@interface关键字,定义一个新的Annotation类型与定义一个接口很是像(只是多了一个@符号)。

 

// 自定义一个Annotation类型
public @interface Test {
}

 

在自定义一个Annotation类型一般能够用于修饰程序中的类、方法、变量、接口等。通常状况下,使用Annotation会在代码以前使用。

 

// 自定义Annotation类型定义在类上.
@Test
public class AnnotationTest {
    // 自定义Annotation类型定义在成员变量上.
    @Test
    private int i;
    // 自定义Annotation类型定义在构造函数上.
    @Test
    public AnnotationTest(){}
    // 自定义Annotation类型定义在方法上.
    @Test
    // 自定义Annotation类型定义在方法参数上.
    public void fun(@Test String str){
        // 自定义Annotation类型定义在变量上.
        @Test
        int z;
    }
}

 

1.4.  Annotation属性

自定义Annotation不只能够是这种简单形式,还能够包含成员变量。自定义的Annotation的成员变量以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。

 

/**
 * 自定义带有username和password属性的Annotation
 * */
public @interface UserInfo {
    String username();
    String password();
}

 

使用带有属性的自定义Annotation时,必须使用其属性指定值,不然会报错。

 

@UserInfo(username="zhangwuji",password="123")
public class UserInfoTest {
}

 

自定义Annotation不只能够设置属性,还能够为属性设置默认值,使用default关键字。

/**
 * 自定义带有username和password属性的Annotation
*  * 为username属性设置默认值.
 * @author 金云龙
 */
public @interface UserInfo {
    String username() default "zhangwuji";
    String password();
}

 

若是为自定义Annotation的属性设置了默认值,则在使用时能够不为该属性指定值(使用默认值)。也能够在使用该Annotation时为其属性指定值,则默认值不会起做用。

自定义Annotation中具备名为value的属性,在使用该Annotation时若是只使用value属性的话,能够不写属性名直接指定值。

 

@UserInfo("jiaozhu")
public class UserInfoTest {
}

 

Annotation的属性类型只能是基本类型、String、Enum、Class及上述类型的一维数组类型。

1.5.  @Target注解

@Target修饰自定义Annotation,指定该自定义Annotation能够用于修饰哪些程序单元,例如方法、成员变量等。@Target注解包含一个ElementType类型的value属性,该属性值只能是以下几个:

  • ElementType.ANNOTATION_TYPE:指定该策略的Annotation只能修饰Annotation。
  • ElementType.CONSTRUCTOR:指定该策略的Annotation只能修饰构造器。
  • ElementType.FIELD:指定该策略的Annotation只能修饰成员变量。
  • ElementType.LOCAL_VARIABLE:指定该策略的Annotation只能修饰局部变量。
  • ElementType.METHOD:指定该策略的Annotation只能修饰方法定义。
  • ElementType.PACKAGE:指定该策略的Annotation只能修饰包定义。
  • ElementType.PARAMETER:指定该策略的Annotation只能修饰参数。
  • ElementType.TYPE:指定该策略的Annotation能够修饰类、接口或枚举定义。

如下是@Target注解的源码和ElementType的源码:

 

@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
public enum ElementType {
    TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE
}

 

1.6.  @Retention注解

@Retention修饰自定义Annotation,指定自定义Annotation的生命周期。@Retention包含一个RetentionPolicy类型的value属性,该属性值只能是以下几个:

  • RetentionPolicy.CLASS:编译器将把Annotation记录在class文件中。当运行Java程序时,JVM不可获取Annotation信息。这时默认值。
  • RetentionPolicy.RUNTIME:编译器将把Annotation记录在class文件中。当运行Java程序时,JVM也能够获取Annotation信息,程序能够经过反射获取该Annotation信息。
  • RetentionPolicy.SOURCE:Annotation只保留在源代码中,编译器直接丢弃这种Annotation。

如下是@Retention注解的源码和RetentionPolicy的源码:

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
public enum RetentionPolicy {
    SOURCE, CLASS, RUNTIME
}

 

1.7.  反射读取Annotation

使用Annotation修饰了类、方法、成员变量等以后,这些Annotation不会本身生效,必须经过相应程序提取并处理Annotation信息。Java提供的Annotation接口是全部注解的父接口,在JDK 5.0新增长AnnotatedElement接口,该接口提供读取运行时Annotation的方法。只有当自定义的Annotation使用了@Retention(RetentionPolicy.RUNTIME)时,该Annotation才会在运行可见,JVM才能读取保存在class文件的Annotation信息。

如下是AnnotatedElement接口提供的方法API:

方法摘要

<T extends Annotation>
T

getAnnotation(Class<T> annotationClass)
若是存在该元素的指定类型的注释,则返回这些注释,不然返回 null。

Annotation[]

getAnnotations()
返回此元素上存在的全部注释。

Annotation[]

getDeclaredAnnotations()
返回直接存在于此元素上的全部注释。

boolean

isAnnotationPresent(Class<? extends Annotation> annotationClass)
若是指定类型的注释存在于此元素上,则返回 true,不然返回 false。

实际获取某类使用的Annotation信息的方式以下:

public class AnnotatedElementTest {
    public static void main(String[] args) throws Exception {
        // 获取对应类的Class对象.
        Class<UserInfoTest> clazz = UserInfoTest.class;
        // 获取对应类方法的Method对象.
        Method method = clazz.getMethod("fun");
        // 获取类上的注解.
        UserInfo anno1 = clazz.getAnnotation(UserInfo.class);
        // 打印该注解的username属性值.
        System.out.println(anno1.username());
        // 获取方法上的注解.
        UserInfo anno2 = method.getAnnotation(UserInfo.class);
        // 打印该注解的username属性值.
        System.out.println(anno2.password());
    }
}

 

1.8.  注解配置JDBC案例

使用JDBC链接MySQL数据库时,须要driverClassName、url、username和password四个参数。而以前的作法是将这四个参数写入一个配置文件,在JDBCUtils工具类中读取配置文件。目前能够将四个参数定义为一个注解,在JDBCUtils工具类中经过反射获取对应注解定义的四个参数内容。具体作法以下:

  • 定义一个Annotation用于定义JDBC链接MySQL数据库所需的四个参数内容。

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface JDBCInfo {
    String driverClassName();
    String url();
    String username();
    String password();
}

 

  • 定义JDBCUtils工具类,使用Annotation配置四个参数内容,并使用反射进行读取。

 

public class JDBCUtils {
    @JDBCInfo(driverClassName = "com.mysql.jdbc.Driver", url = "jdbc:mysql://localhost:3306/jdbc", username = "root", password = "root")
    public static Connection getConnection() throws Exception {
        // 获取注解修饰目标对应的反射对象.
        Method method = JDBCUtils.class.getDeclaredMethod("getConnection");
        // 判断是否存在目前注解
if (method.isAnnotationPresent(JDBCInfo.class)) {
            // 获取注解信息
            JDBCInfo jdbcInfo = method.getAnnotation(JDBCInfo.class);
            // 读取注解属性信息
            String driverClassName = jdbcInfo.driverClassName();
            String url = jdbcInfo.url();
            String username = jdbcInfo.username();
            String password = jdbcInfo.password();
            // Class类加载驱动
            Class.forName(driverClassName);
            // 返回链接对象
            return DriverManager.getConnection(url, username, password);
        }
        return null;
    }
}

 

 

  • 编写一个测试类用于测试JDBCUtils工具类是否正确。

 

public class JDBCTest {
    public static void main(String[] args) throws Exception {
        Connection conn = JDBCUtils.getConnection();
        String sql = "select * from products";
        PreparedStatement statement = conn.prepareStatement(sql);
        ResultSet rs = statement.executeQuery();

        while (rs.next()) {
            System.out.println(rs.getString("name") + "," + rs.getDouble("price"));
        }

        rs.close();
        statement.close();
        conn.close();
    }
}

 

2.   动态代理

  1. 2.   

2.1.  动态代理概述

代理模式是Java设计模式中的一种,其特征为代理类与委托类有一样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及过后处理消息等。代理类与委托类之间一般存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象自己并不真正实现业务,而是经过调用委托类对象的相关方法来提供具体业务。

在Java中的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,经过这个类和接口能够生成JDK动态代理或动态代理对象。

按照代理的建立时间不一样,能够分为两种:

  • 静态代理:手动建立,再对其编译。在程序运行前,代理类的.class文件就已经存在。
  • 动态代理:在程序运行时,经过反射机制动态建立而成。

2.2.  动态代理原理

动态代理的实现原理有些相似于过滤器的实现原理,但有所不一样。动态代理的代理类与委托类之间的关系更像是明星与经纪人之间的关系,也就是说,若是你想找某个明星演出的话,并非找他本人,而是找到他的经纪人就能够了。动态代理的实现过程很相似于这个过程,具体请看下图:

 

 

2.3.  Proxy代理类

Proxy类是Java的java.lang.reflect包下提供的,该类用于建立动态代理类和代理对象的静态方法,它也是全部动态代理类的父类。若是在程序中为一个或多个接口动态地生成实现类,就能够用Proxy类来建立动态代理类;若是须要为一个或多个接口动态地建立实例,也可使用Proxy类来建立动态代理实例。

方法摘要

static InvocationHandler

getInvocationHandler(Object proxy)
返回指定代理实例的调用处理程序。

static Class<?>

getProxyClass(ClassLoader loader, Class<?>... interfaces)
返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。

static boolean

isProxyClass(Class<?> cl)
当且仅当指定的类经过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。

static Object

newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回一个指定接口的代理类实例,该接口能够将方法调用指派到指定的调用处理程序。

  • static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces):建立一个动态代理类所对应的Class对象,该代理类将实现interfaces所指定的多个接口。第一个ClassLoader参数指定生成动态代理类的类加载器。
  • static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):直接建立一个动态代理对象,该代理对象的实现类实现了interfaces指定的系列接口,执行代理对象的每一个方法时都会被替换执行InvocationHandler对象的invoke()方法。

2.4.  InvocationHandler

InvocationHandler接口提供了invoke()方法,用于替换代理对象的每个方法。真实业务类能够经过代理类对象调用InvocationHandler接口提供的invoke()方法,来替代调用委托类的真实方法。

如下是InvocationHandler的API内容:

方法摘要

Object

invoke(Object proxy, Method method, Object[] args)
在代理实例上处理方法调用并返回结果。

  • Object invoke(Object proxy, Method method, Object[] args):在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。

²  参数proxy:表示代理类对象,也就是Proxy.newProxyInstance()方法返回的对象,一般用不上。

²  参数method:表示当前被调用方法的反射对象,

²  参数args:表示调用目标方法时传入的实参。

2.5.  实现动态代理

利用Java提供的Proxy类和InvocationHandler接口来生成动态代理类或动态代理对象,具体实现步骤以下:

  • 定义一个业务接口,该接口提供具体业务方法的定义。

 

public interface Person {
    void sayMe();
    void sayHello(String name);
}

 

  • 定义一个InvocationHandler接口的实现类,并重写invoke()方法。

 

public class MyInvocationHandler implements InvocationHandler {
    /**
     * 执行动态代理对象的全部方法时,都会被替换成执行下面的invoke()方法.
     *  * 参数proxy:表明动态代理对象.
*  * 参数method:表明正在执行的方法.
     *  * 参数args:表明调用目标方法时传入的实参.
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("---正在执行的方法: "+method);
        if(args == null){
            System.out.println("当前调用的方法没有参数.");
        }else{
            System.out.println("当前调用的方法须要传入的实参为:");
            for (Object val : args) {
                System.out.println(val);
            }
        }
        return null;
    }
}

 

 

  • 编写一个用于测试动态代理的测试类。

 

public class ProxyTest {
    public static void main(String[] args) {
        // 建立一个InvocationHandler对象
        InvocationHandler handler = new MyInvocationHandler();
        // 经过Proxy类使用指定的InvocationHandler来生成动态代理对象
        Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler);
        // 调用动态代理对象的业务方法
        p.sayMe();
        p.sayHello("张无忌");
    }
}

 

2.6.  动态代理的做用

经过Java提供的Proxy类和InvocationHandler接口生成的动态代理类,能够阻止调用委托类的方法、过滤参数及修改对应方法的返回值等做用。实现业务接口方法的实现类即委托类,具体操做以下:

  • 建立一个实现类,实现Person接口,并重写业务方法。

 

public class Fanbingbing implements Person {
    @Override
    public void sayMe() {
        System.out.println("我真的是范冰冰哦!");
    }

   @Override

 
 

   public String sayHello(String name) {

 
 

      System.out.println("你好:"+name+",我等你好久了...");

 
 

      return "我终于见到范冰冰啦!";

 
 

   }

 
 

}

 

 

 

  • 编写一个用于测试动态代理的测试类。

 

public class FanbingbingTest {
    public static void main(String[] args) {
        Person p = (Person) Proxy.newProxyInstance(
                Person.class.getClassLoader(),
                Fanbingbing.class.getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        // 经过method的getName()方法获取业务方法名,进行阻止.
                        if (method.getName().equals("sayMe")) {
                            System.out.println("你想多了,哪那么容易见到啊!");
                            return null;
                        }
                        // 经过args获取实参,进行修改
                        if(method.getName().equals("sayHello")){
                            String name = (String)args[0];
                            method.invoke(Class.forName("app.java.proxy.Fanbingbing").newInstance(), "某局长");
                        }
                        // 修改返回值
                        if(method.getName().equals("sayHello")){
                            return "都是假的!";
                        }
                        return null;
                    }
                });
        p.sayMe();
        p.sayHello("张无忌");
    }
}

 

2.7.  权限控制案例

目前已经掌握注解和动态代理的内容,下面利用注解和动态代理来完成权限控制的功能。首先,完成基本业务的功能,具体以下操做:

  • 在MySQL数据库中建立相关数据库表及初始化必要数据记录。

 

CREATE TABLE userinfo(
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(40),
  PASSWORD VARCHAR(40)
);

INSERT INTO userinfo VALUES(NULL,'zhangwuji','123');
INSERT INTO userinfo VALUES(NULL,'zhouzhiruo','123');
INSERT INTO userinfo VALUES(NULL,'zhaomin','123');

CREATE TABLE PRIVILEGES(
   id INT PRIMARY KEY AUTO_INCREMENT,
   NAME VARCHAR(40)
);

INSERT INTO PRIVILEGES VALUES(NULL,'添加图书');
INSERT INTO PRIVILEGES VALUES(NULL,'修改图书');
INSERT INTO PRIVILEGES VALUES(NULL,'查看图书');
INSERT INTO PRIVILEGES VALUES(NULL,'删除图书');

CREATE TABLE userprivilege(
   user_id INT ,
   privilege_id INT,
   FOREIGN KEY(user_id) REFERENCES userinfo(id),
   FOREIGN KEY(privilege_id) REFERENCES PRIVILEGES(id),
   PRIMARY KEY(user_id,privilege_id)
);

INSERT INTO userprivilege VALUES(1,1);
INSERT INTO userprivilege VALUES(1,2);
INSERT INTO userprivilege VALUES(1,3);

 

  • 建立一个JavaBean用于封装用户信息。

 

public class User {
    private int id;
    private String username;
    private String password;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;

}

   public String getPassword() {

      return password;

   }

   public void setPassword(String password) {

      this.password = password;

   }

}

 

 

  • 建立一个JSP页面用于用户登陆功能的显示。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>My JSP 'login.jsp' starting page</title>
  </head>
  <body>
    <form action="../login" method="post" >
        用户名 <input type="text" name="username" /> 
        密码 <input type="password" name="password"/>
        <input type="submit" value="登录" />
    </form>
  </body>
</html>
  • 建立一个Servlet用于处理用户登陆逻辑。
public class LoginServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            User user = new User();
            BeanUtils.populate(user, request.getParameterMap());
            QueryRunner runner = new QueryRunner(JDBCUtils.getDataSource());
            String sql = "select * from userinfo where username = ? and password = ?";
            User existUser = runner.query(sql, new BeanHandler<User>(User.class), user.getUsername(), user.getPassword());
            if (existUser == null) {
                response.sendRedirect("/21_java/proxy/login.jsp");
            } else {
                request.getSession().setAttribute("user", existUser);
                response.sendRedirect("/21_java/proxy/book.jsp");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

 

  • 建立一个JSP页面用于登陆成功的功能列表显示
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>My JSP 'book.jsp' starting page</title>
  </head>
  <body>
    <h1>图书管理</h1>
    <h2>${empty user?'未登录':user.username }</h2>
    <a href="../book?operate=add">添加图书</a>
    <a href="../book?operate=del">删除图书</a>
    <a href="../book?operate=edit">修改图书</a>
    <a href="../book?operate=search">查看图书</a>
  </body>
</html>

 

  • 建立一个Servlet用于对具体业务的操做逻辑处理。
public class BookServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String operate = request.getParameter("operate");
        User user = (User) request.getSession().getAttribute("user");
        BookService service = new BookServiceImpl();
        
        if("add".equals(operate)){
            service.addBook(user);
        }else if("del".equals(operate)){
            service.delBook(user);
        }else if("edit".equals(operate)){
            service.editBook(user);
        }else if("search".equals(operate)){
            service.searchBook(user);
        }
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

 

  • 建立一个业务接口和业务实现类用于处理具体业务流程。
public interface BookService {
    public void addBook(User user);
    public void delBook(User user);
    public void editBook(User user);
    public void searchBook(User user);
}

 

public class BookServiceImpl implements BookService {
    public void addBook(User user){
        System.out.println("添加图书成功...");
    }
    public void delBook(User user){
        System.out.println("删除图书成功...");
    }
    public void editBook(User user){
        System.out.println("修改图书成功...");
    }
    public void searchBook(User user){
        System.out.println("查看图书成功...");
    }
}
  • 配置Web工程的web.xml文件。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <display-name></display-name>
  <servlet>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>app.java.proxy.demo.LoginServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>BookServlet</servlet-name>
    <servlet-class>app.java.proxy.demo.BookServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/login</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>BookServlet</servlet-name>
    <url-pattern>/book</url-pattern>
  </servlet-mapping>    
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

完成到目前为止,已经将一个图书操做(增删改查)的功能完成。下面利用注解和动态代理来完成权限控制的功能,具体操做以下:

  • 建立一个Annotation注解用于配置权限。
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @Inherited
    public @interface PrivilegeInfo {
        String value();
    }
  • 利用Annotation注解为业务接口及实现类的方法配置权限。
    public interface BookService {
        @PrivilegeInfo("添加图书")
        public void addBook(User user);
        @PrivilegeInfo("删除图书")
        public void delBook(User user);
        @PrivilegeInfo("修改图书")
        public void editBook(User user);
        @PrivilegeInfo("查看图书")
        public void searchBook(User user);
    }
  • 建立一个业务工厂类用于获取对应业务类实例对象。
    public class ServiceFactory {
        public static BookService getBookService(){
            // 获取真实业务实现类对象
            final BookService service = new BookServiceImpl();
            // 经过Proxy代理类获取对应业务实现类的代理对象
            BookService serviceProxy = (BookService) Proxy.newProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                    // 判断对应业务实现类是否存在注解信息
                    if (method.isAnnotationPresent(PrivilegeInfo.class)) {
                        // 获取注解修饰对象
                        PrivilegeInfo info = method.getAnnotation(PrivilegeInfo.class);
                        // 获取注解修饰属性值(当前业务方法的权限)
                        String needPrivilege = info.value();
                        // 获取当前用户信息
                        User existUser = (User) args[0];
                        // 查询当前用户的全部权限
                        QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());
                        String sql = "select privileges.name from privileges,userprivilege where privileges.id = userprivilege.privilege_id and userprivilege.user_id = ?";
                        List<Object> privilegeList = queryRunner.query(sql,new ColumnListHandler("name"), existUser.getId());
                        // 判断当前用户是否具备权限
                        for (Object userPrivilege : privilegeList) {
                            if (needPrivilege.equals(userPrivilege)) {
                                // 具备权限
                                return method.invoke(service, args);
                            }
                        }
                        // 没有权限
                        throw new RuntimeException("权限不足,没法访问");
                    }
                    // 不须要权限
                    return method.invoke(service, args);
                }
            });
            return serviceProxy;
        }
    }
  • 修改用于对具体业务的操做逻辑处理的Servlet代码。
    BookService service = ServiceFactory.getBookService();

 

3.   类加载器

3.0.什么是类加载器,做用是什么?

类加载器就加载字节码文件(.class)

3.1.  类加载器概述

类加载器负责加载全部的类,系统为全部被载入内存中的类生成一个java.lang.Class实例。一旦一个类被加入JVM中,同一个类就不会被再次加入了。正如一个对象有一个惟一的标识同样,一个载入JVM的类也有一个惟一的标识。在Java中,一个类用其全限定类名(包括包名和类名)做为标识;但在JVM中,一个类用其全限定类名和其类加载器做为其惟一标识。

例如如下案例,在JVM中两个同名的Person类是彻底不一样的,之间也互不兼容,由于类加载器不一样。

 

 

上述状况转换成代码以下:

  • 定义一个Person类。

 

public class Person {
    private Person instance; 
    public void setPerson(Object instance) { 
        this.instance = (Person) instance; 
    } 
}

 

  • 编写一个测试方法,用于测试经过两个不一样类加载器加载Person类获得不一样的Class实例。
    @Test
    public void ClassIdentityTest() throws Exception{
        String classDataRootPath = "WebRoot\\WEB-INF\\classes"; 
        FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath); 
        FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath); 
        String className = "app.java.classloader.Person";
        try { 
            Class<?> class1 = fscl1.findClass(className); 
            Object obj1 = class1.newInstance(); 
            Class<?> class2 = fscl2.findClass(className); 
            Object obj2 = class2.newInstance(); 
            Method setSampleMethod = class1.getMethod("setPerson", java.lang.Object.class); 
            setSampleMethod.invoke(obj1, obj2); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
    }

 

  • 上述代码运行后,报以下错误,表示虽然两个对象的类名称相同,可是因为经过不一样类加载器获得Class实例,JVM不会认为是相同的。

 

 

3.2.  类加载器分类

当JVM启动时,会造成由三个类加载器组成的初始类加载器层次结构。

  • Bootstrap ClassLoader:根类加载器。
  • Extension ClassLoader:扩展类加载器。
  • System ClassLoader:系统类加载器。

Bootstrap ClassLoader被称为引导(或原始或根)类加载器,它负责加载Java的核心类。在JVM中,当执行java.exe命令时,使用-Xbootclasspath选项或使用-D选项指定sun.boot.class.path系统属性值能够指定加载附加的类。根类加载器很是特殊,它并非java.lang.ClassLoader的子类,而是由JVM自身实现的。

 

    @Test
    public void BootstrapTest(){
        // 获取根类加载器所加载的所有URL数组
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        // 遍历输出根类加载器加载的所有URL
        for (int i = 0; i < urls.length; i++) {
            System.out.println(urls[i].toExternalForm());
        }
    }

 

Extension ClassLoader被称为扩展类加载器,它负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext或由java.ext.dirs系统属性指定的目录)中的JAR包的类。经过这种方式能够为Java扩展核心类之外的新功能,只要把本身开发的类打包成JAR文件,而后放入%JAVA_HOME%/jre/lib/ext路径便可。

 

    @Test
    public void ExtensionTest() throws Exception{
        // 位于jre/lib/ext/dnsns.jar
        DNSNameService dnsNameService = new DNSNameService();
        System.out.println(dnsNameService.getClass().getClassLoader());
    }

 

System ClassLoader被称为系统或应用类加载器,它负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类径路。程序能够经过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。若是没有特别指定,则用户自定义的类加载器都以系统类加载器做为父加载器。

 

    @Test
    public void SystemTest() throws Exception{
        // 获取当前类的实例对象
        ClassLoaderTest classLoaderTest = new ClassLoaderTest();
        System.out.println(classLoaderTest.getClass().getClassLoader());
    }

 

如下是JVM中4种类加载器的层次结构:

 

 

 

    @Test
    public void ClassLoaderTreeTest() throws Exception{
        // 获取当前类的类加载器
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        // 判断类加载器是否为空
        while (classLoader != null) {
            // 打印当前类加载器的名称
            System.out.print(classLoader.getClass().getName()+"->");
            // 获取当前类加载器的父级
            classLoader = classLoader.getParent();
        }
        System.out.println(classLoader);
    }

 

上述代码中能够看出JVM中类加载器的层次结构。最后输出的是null的愿意是由于根类加载器并非Java提供的,而是JVM提供的,因此不能获取。

3.3.  类加载机制

JVM的类加载器机制主要有以下三种:

  • 全盘负责。所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其余Class也将由该类加载器负责载入,除非显式使用另一个类加载器来载入。
  • 父类委托。所谓父类委托,则是先让父类加载器视图加载该Class,只有在父类加载器没法加载该类时才尝试从本身的类路径中加载该类。
  • 缓存机制。缓存机制将会保证全部加载过的Class都会被缓存,当程序中须要使用某个Class时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区中。这也是为何修改Class后,必须从新启动JVM,程序所作的修改才会生效的缘由。

3.4.  自定义类加载器

JVM中除根类加载器以外的全部类加载器都是ClassLoader子类的实例,能够经过扩展ClassLoader的子类,并重写该ClassLoader提供的方法来实现自定义的类加载器。

ClassLoader类具备以下两个关键方法:

  • loadClass(String name, boolean resolve):该方法为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象。
  • findClass(String name):根据指定名称来查找类。

因为loadClass()方法的执行步骤为1)利用findLoadedClass()方法来检查是否已经加载类。2)在父类加载器调用loadClass()方法。3)调用findClass()方法查找类。重写findClass()方法能够避免默认类加载器的父类委托和缓冲机制,这里推荐重写findClass()方法。

下面来实现一个文件系统类加载器,用于加载存储在文件系统上的Java字节代码。

public class FileSystemClassLoader extends ClassLoader {

    private String rootDir;

    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] getClassData(String className) {
        String path = classNameToPath(className);
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }

        return baos.toByteArray();

 
 

      } catch (IOException e) {

 
 

        e.printStackTrace();

 
 

      }

 
 

      return null;

 
 

   }

 
 

 

 
 

   private String classNameToPath(String className) {

 
 

      return rootDir + File.separatorChar

 
 

           + className.replace('.', File.separatorChar) + ".class";

 
 

   }

 
 

}

 

 

 

3.5.  Tomcat类加载器

对于运行在 Java EE™容器中的 Web 应用来讲,类加载器的实现方式与通常的 Java 应用有所不一样。不一样的 Web 容器的实现方式也会有所不一样。以 Apache Tomcat 来讲,每一个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不一样的是它是首先尝试去加载某个类,若是找不到再代理给父类加载器。这与通常类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐作法,其目的是使得 Web 应用本身的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围以内的。这也是为了保证 Java 核心库的类型安全。

绝大多数状况下,Web 应用的开发人员不须要考虑与类加载器相关的细节。下面给出几条简单的原则:

  • 每一个 Web 应用本身的 Java 类文件和使用的库的 jar 包,分别放在WEB-INF/classes和WEB-INF/lib目录下面。
  • 多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由全部 Web 应用共享的目录下面。
  • 当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。

4.   Servlet3.0新特性

  1. 4.   

4.1.  注解替代配置文件

须要注意的是,若是使用Servlet 3.0版本的话:首先Tomcat服务器必须使用7.0版本以上的(老版本不提供3.0版本),其次Eclipse建立Web工程时选择3.0版本。

Servlet 3.0版本容许使用注解方式来替代web.xml文件中配置Servlet、Filter和Listener的信息,首先回忆一下web.xml文件是如何配置Servlet、Filter和Listener的:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <!-- 配置Servlet -->
  <servlet>
    <servlet-name>ServletTest</servlet-name>
    <servlet-class>app.java.servlet.ServletTest</servlet-class>
    <init-param>
        <param-name>longestory</param-name>
        <param-value>龙哥有话说</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>ServletTest</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
  <!-- 配置Filter -->
  <filter>
      <filter-name>FilterTest</filter-name>
      <filter-class>app.java.servlet.FilterTest</filter-class>
      <init-param>
        <param-name>longestory</param-name>
        <param-value>龙哥有话说</param-value>
    </init-param>
  </filter>
  <filter-mapping>
      <filter-name>FilterTest</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>
  <!-- 配置Listener -->
  <listener>
      <listener-class>app.java.servlet.ListenerTest</listener-class>
  </listener>
</web-app>

 

  • 使用注解替代web.xml文件配置Servlet。

 

@WebServlet(urlPatterns="/*",initParams={@WebInitParam(name="longestory",value="龙哥有话说")},loadOnStartup=0)
public class ServletTest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

 

  • 使用注解替代web.xml文件配置Filter。

 

@WebFilter(urlPatterns="/*",initParams={@WebInitParam(name="longestory",value="龙哥有话说")})
public class FilterTest implements Filter {
    @Override
    public void init(FilterConfig arg0) throws ServletException {}
    @Override
    public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain arg2) throws IOException, ServletException {
    }
    @Override
    public void destroy() {}
}

 

  • 使用注解替代web.xml文件配置Listener。

 

@WebListener
public class ListenerTest implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent arg0) {}
    @Override
    public void sessionDestroyed(HttpSessionEvent arg0) {}
}

 

4.2.  Serlvet文件上传

Servlet 3.0的另外一个新特性就是提供了处理文件上传的功能,使用Servlet 3.0的内容实现文件上传须要如下几个内容:

  • 在处理文件上传的Servlet上增长@MultipartConfig注解,表示当前Servlet符合MIME类型的multipart/form-data。

Optional Element Summary

int

fileSizeThreshold
The size threshold after which the file will be written to disk

java.lang.String

location
The directory location where files will be stored

long

maxFileSize
The maximum size allowed for uploaded files.

long

maxRequestSize
The maximum size allowed for multipart/form-data requests

  • Part接口,表示容许接收MIME类型是multipart/form-data类型的POST请求。

Method Summary

void

delete()
Deletes the underlying storage for a file item, including deleting any associated temporary disk file.

java.lang.String

getContentType()
Gets the content type of this part.

java.lang.String

getHeader(java.lang.String name)
Returns the value of the specified mime header as a String.

java.util.Collection<java.lang.String>

getHeaderNames()
Gets the header names of this Part.

java.util.Collection<java.lang.String>

getHeaders(java.lang.String name)
Gets the values of the Part header with the given name.

java.io.InputStream

getInputStream()
Gets the content of this part as an InputStream

java.lang.String

getName()
Gets the name of this part

long

getSize()
Returns the size of this fille.

void

write(java.lang.String fileName)
A convenience method to write this uploaded item to disk.

利用Servlet 3.0实现文件上传的功能,具体实现代码以下:

  • 建立一个JSP页面用于文件上传显示。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>My JSP 'index.jsp' starting page</title>
  </head>
  <body>
    <form action="uploadFile" method="post" enctype="multipart/form-data">
        文件描述:<input type="text" name="filetext"><br>
        <input type="file" name="upload"><br>
        <input type="submit" value="上传">
    </form>
  </body>
</html>

 

  • 建立一个Servlet用于处理文件上传逻辑。
  • @WebServlet(urlPatterns="/uploadFile")
    @MultipartConfig(
    fileSizeThreshold = 10 * 1024,//缓存大小,当上传的文件超出这个大小时会生成临时文件
    location = "/temp",// 存放临时文件的目录
    maxFileSize = 5 * 1024,// 单个文件大小限制
    maxRequestSize = 10 * 1024// 整个请求大小限制
    )
    public class UploadFileServlet extends HttpServlet {
        @Override
        public void doPost(HttpServletRequest req, HttpServletResponse resp)
                throws ServletException, IOException {
            // 处理请求中文乱码
            req.setCharacterEncoding("utf-8");
            // 接收上传文件的描述内容
            String filetext = req.getParameter("filetext");
            // 接收上传文件的内容
            Part part = req.getPart("upload");
            // 获取上传文件的真实名称
            String cd = part.getHeader("Content-Disposition");
            int index = cd.indexOf("filename=\"") + 10;
            String filename = cd.substring(index, cd.length() - 1);
            // 读取上传目录的绝对路径
            String path = getServletContext().getRealPath("/upload");
            // 将上传文件进行保存
            part.write(path + "/" + filename);
        }
        @Override
        public void doGet(HttpServletRequest req, HttpServletResponse resp)
                throws ServletException, IOException {
            doPost(req, resp);
        }
    }

     

 

 

4.3.  Servlet异步处理

所谓异步处理就是指数据发送方发送数据给数据接收方后,没必要等待数据接收方做出响应,能够继续发送下一个数据的通讯方式。

Servlet 3.0版本提供了相关异步处理的功能,具体实现方式以下:

  • 须要使用注解在对应Servlet配置asyncSupported=true,表示当前Servlet支持异步。
  • 经过Request对象的startAsync(Request, Response)方法获取异步上下文对象。
  • 经过异步上下文对象调用start(new Runnable(){})方法开始异步处理,Runnable类的run()方法提供具体异步的逻辑代码。

根据上述步骤,经过一个代码实例演示。

 

@WebServlet(urlPatterns="/async",asyncSupported=true)
public class AsyncServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        // 设置响应类型及编码格式
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().print("立刻开始:<br/>");
        // 刷新缓冲区
        resp.getWriter().flush();
        // 获取异步上下文对象
        final AsyncContext ac = req.startAsync(req, resp);
        // 开始异步处理
        ac.start(new Runnable() {
            @Override
            public void run() {
                try {
                    for (char ch = 'A'; ch <= 'Z'; ch++) {
                        resp.getWriter().print(ch);
                        resp.getWriter().flush();
                        Thread.sleep(250);
                    }
                    // 表示异步处理完毕
                    ac.complete();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }
}
相关文章
相关标签/搜索