Java基础加强-(注解,类加载器,servlet3.0新特性)
Posted Qiao_Zhi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础加强-(注解,类加载器,servlet3.0新特性)相关的知识,希望对你有一定的参考价值。
1. Annotation注解
1.1. Annotation概述
Annotation是JDK 5.0以后提供对元数据的支持,可以在编译、加载和运行时被读取,并执行相应的处理。所谓Annotation就是提供了一种为程序元素设置元数据的方法,可用于修饰包、类、构造器、方法、成员变量、参数和局部变量的声明,这些信息被存储在Annotation的“name=value”对中。
Annotation能被用来为程序元素(类、方法、成员变量等)设置元数据,比如一段代码的作者或者告诉编译器禁止一些特殊的错误,不会影响代码的执行。
1.2. 基本Annotation
在Java中提供了3个基本Annotation的用法,使用Annotation时要在其前面增加@符号,并把该Annotation当作一个修饰符使用,用于修饰它支持的程序元素。这3个基本Annotation都定义在java.lang包下,可以通过查看API文档来了解。
- @Override:限定重写父类方法。
@Override就是用来指定方法覆载的,它可以强制一个子类必须覆盖父类的方法。
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:
方法摘要 |
||
|
|
|
|
||
|
||
|
|
实际获取某类使用的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. 动态代理
- 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 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):在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
² 参数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("添加图书成功..."基础加强第17天(基础加强_注解_类加载器_动态代理)_学习目标版本