写在前面,大家好!我是【跨考菌】,一枚跨界的程序猿,专注于后台技术的输出,目标成为
全栈攻城狮
!这博客是对我跨界过程的总结和思考。如果你也对Java
、后端技术
感兴趣,抑或是正在纠结于跨界,都可以关注我的动态,让我们一起学习,一起进步~
我的博客地址为:【跨考菌】的博客
上篇【零基础上手JavaWeb】22ajax原理 介绍了ajax的核心内容。本文总结一下Java泛型、注解、Servlet3、动态代理、类加载器等内容,为后面框架的学习打好基础。和【跨考菌】一起加油吧~
如果你有收获,记得帮博主一键三连哦😊
1 泛型
1.1 泛型类
泛型类:具有一个或多个类型变量的类,称之为泛型类!
class A<T> {
}
在创建泛型类实例时,需要为其类型变量赋值
A<String> a = new A<String>();
如果创建实例时,不给类型变量赋值,那么会有一个警告!
1.2 泛型方法
泛型方法:具有一个或多个类型变量的方法,称之为泛型方法!
class A<T> {
public T fun(T t1) {}
}
fun()方法不是泛型方法!它是泛型类中的一个方法!
public <T> T fun(T t1) {}
--> 它是泛型方法
- 泛型方法与泛型类没什么关系,泛型方法不一定非要在泛型类中!
泛型在类中或方法中的使用
- 泛型类中使用泛型:
成员类型
返回值和参数类型
局部变量的引用上
class A<T> {
private T bean;//泛型可在成员变量上使用
public T fun(T t) {}//泛型可以在类中的方法上(返回值和参数类型)使用!
public void fun2() {//泛型还可以在局部变量的引用类型上使用
T b = ...
new T();//不行的!
}
}
1.3 泛型的继承和实现
这里只介绍继承泛型类,实现泛型类是类似的。
1)子类不是泛型类:需要给父类传递类型常量
> 当给父类传递的类型常量为String时,那么在父类中所有T都会被String替换!
class AA1 extends A<Integer> {}
// 子类不是泛型类
public class Demo1 {
@Test
public void fun1() {
AA1<Integer> aa1 = new AA1<Integer>(); // 在创建对象的时候才确定下来
AA2<String> aa2 = new AA2<String>(); // 在创建对象的时候才确定下来
}
}
class A<T> {
private T t;
public T fun1() {
return t;
}
public void fun2(T t) {
this.t = t;
}
}
class AA1 extends A<Integer> {
}
class AA2 extends A<String> {
}
2)子类是泛型类:可以给父类传递类型常量,也可以传递类型变量
class AA3<E> extends A<E> {}
// 为T赋值为E,但是还没确定下来,需要在创建对象的时候确定下来。
public class Demo1 {
@Test
public void fun1() {
AA3<Long> aa3 = new AA3<Long>();
}
}
class A<T> {
private T t;
public T fun1() {
return t;
}
public void fun2(T t) {
this.t = t;
}
}
class AA3<E> extends A<E> { // 这里子类是泛型类,将E赋值给了T,还是需要在子类对象中赋值给E。
}
1.4 通配符
1.4.1 数组和集合的对比
1)存储任意类型的数据
Object[] objs = new Object[10];
List list = new ArrayList();
都可以实现存储任意类型
2)只能存储一种类型的数据
String[] strs = new String[10]; // 你数组可以只放一种类型
List<String> strList = new ArrayList<String>(); // 我集合在jdk1.5之后也可以通过泛型来实现了。
jdk1.5之后集合也可以通过泛型来实现数据这种只能存储一种数据的方式。
3)PK3
Object[] objArray = new String[10];
objArray[0] = new Integer(100);//ArrayStoreException
// List<Object> objList = new ArrayList<String>(); // 泛型引用和创建两端,给出的泛型变量必须相同!
// objList.add(new Integer(100));
Object[] objArray = new String[10];
规定了只能存储String类型的数据。而且objArray[0] = new Integer(100)
添加Integer数据时编译时期不会报错。
但是,List<Object> objList = new ArrayList<String>()
这条语句在编译器是会报错的。
这说明:泛型引用和创建两端,给出的泛型变量必须相同!
1.4.2 通配符?
List<?> list
/*
* 其中的?就是通配符
* 通配符只能出现在左边!即不能在new时使用通配符!!!
* List<?> list = new ArrayList<String>();
*/
/*
* ?它表示一个不确定的类型,它的值会在调用时确定下来
*/
public void print(List<?> list) {
/*
* 当使用通配符时,对泛型类中的参数为泛型的方法起到了副作用,不能再使用!
*/
// list.add("hello");
/*
* 当使用通配符时,泛型类中返回值为泛型的方法,也作废了!
*/
Object s = list.get(0);
/*
* 通配符好处:可以使泛型类型更加通用!尤其是在方法调用时形参使用通配符!
*/
}
其中的?就是通配符
通配符只能出现在左边!即不能在new时使用通配符!!!
当使用通配符时,对泛型类中的参数为泛型的方法起到了副作用,不能再使用!
public void print(List<?> list) {}
中list.add("hello");
是会报错的。因为在调用print方法时才能确定?的具体类型。
1.4.3 ?extends xxx
/*
* 给通配符添加了限定:
* 只能传递Number或其子类型
* 子类通配符对通用性产生了影响,但使用形参更加灵活
*/
public void print1(List<? extends Number> list) {
/*
* 参数为泛型的方法还是不能使用
*/
// list.add(new Integer(100));
/*
* 返回值为泛型的方法可用了!
*/
Number number = list.get(0);
}
? extends Number
表示:只能传递Number或其子类型
public void print1(List<? extends Number> list) {}
中的list.add(new Integer(100));
语句也会报错,因为?只有在被调用的时候才会被赋值,其值可能时Number的子类,不一定就是Integer,所以报错。
但是Number number = list.get(0);
语句时正常的,因为在方法中已经限定了?一定时Number的子类。
1.4.4 ? super Integer
/*
* 给通配符添加了限定
* 只能传递Integer类型,或其父类型
*/
public void print2(List<? super Integer> list) {
/*
* 参数为泛型的方法可以使用了
*/
list.add(new Integer(100));
/*
* 返回值为泛型的方法,还是不能使用
*/
Object obj = list.get(0);
}
? super Integer
限定了?只能时Integer或者其父类。
2 注解
2.1 什么是注解
语法:@注解名称
注解的作用:替代xml配置文件!
servlet3.0中,就可以不再使用web.xml文件,而是所有配置都使用注解!
注解是由框架来读取使用的!
2.2 注解的使用
- 定义注解类:框架的工作
- 使用注解:我们的工作
- 读取注解(反射):框架的工作
2.3 定义注解类
class A {} // 定义类
interface A{} // 定义接口
enum A{} // 定义枚举
@interface A{}//天下所有的注解都是Annotation的子类!
/**
* 定义注解
* @author cxf
*
*/
@interface MyAnno1 {
}
2.4 注解的作用目标
* 类
* 方法
* 构造器
* 参数
* 局部变量
* 包
@MyAnno1 // 作用在类上
public class Demo1 {
@MyAnno1 // 作用在成员上
private String name;
@MyAnno1 // 作用在构造方法上
public Demo1() {
}
@MyAnno1 // 作用在方法上
public void fun1() {
}
public void fun2(@MyAnno1 String name) { // 作用在局部参数上
@MyAnno1
String username = "hello";
}
}
/**
* 定义注解
* @author cxf
*
*/
@interface MyAnno1 {
}
2.5 注解的属性
格式:
@interface MyAnno1 {
int age();
String name();
}
- 使用注解时给属性赋值
@MyAnno1(age=100, name=“zhangSan”)
- 注解属性的默认值:在定义注解时,可以给注解指定默认值!
int age() default 100;
在使用注解时,可以不给带有默认值的属性赋值! - 名为value的属性的特权
当使用注解时,如果只给名为value的属性赋值时,可以省略“value=”,例如: @MyAnno1(value=“hello”),可以书写成 @MyAnno1(“hello”)
- 注解属性的类型
8种基本类型
String
Enum
Class
注解类型
以上类型的一维数组类型。切记Interger是不行的,包装器类型不行。
当给数组类型的属性赋值时,若数组元素的个数为1时,可以省略大括号
@MyAnno1(
a=100,
b="hello",
c=MyEnum1.A,
d=String.class,
e=@MyAnno2(aa=200, bb="world"),
f=100 // {100,200},只有一个元素时可以省略大括号。
)
public class Demo3 {
}
@interface MyAnno1 {
int a();
String b();
MyEnum1 c();
Class d();
MyAnno2 e();
int[] f();
}
2.6 注解的作用目标限定
让一个注解,它的作用目标只能在类上,不能在方法上,这就叫作用目标的限定!
- 在定义注解时,给注解添加注解,这个注解是@Target
@Target(value={ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) // 可以作用在类、方法、成员上。
@interface MyAnno1 {
}
作用目标包括:
public enum ElementType {
TYPE,FIELD,METHOD,PARAMETED,CONSTRUCTOR,LOCAL_VARIABLE,ANNOCATION_TYPE,PACKAGE
}
2.7 保留策略
- 源代码文件(SOURCE):注解只在源代码中存在,当编译时就被忽略了
- 字节码文件(CLASS):注解在源代码中存在,然后编译时会把注解信息放到了class文件,但JVM在加载类时,会忽略注解!(要想利用放射,必须要保存在内存中才行,仅仅保存在磁盘是不行的)
- JVM中(RUNTIME):注解在源代码、字节码文件中存在,并且在JVM加载类时,会把注解加载到JVM内存中(它是唯一可反射注解!)
限定注解的保留策略
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno1 {
}
3 获取反射泛型和反射注解
3.1 反射泛型获取
public class Demo1 {
@Test
public void fun1() {
new B();
}
}
abstract class A<T> {
public A() {
// 现在我想在这里获取A子类的泛型类型,即:String或者Integer。
}
}
class B extends A<String> {
}
class C extends A<Integer> {
}
实现:
public A() {
/*
* 在这里获取子类传递的泛型信息,要得到一个Class!
*/
Class clazz = this.getClass();//得到子类的类型
Type type = clazz.getGenericSuperclass();//获取传递给父类参数化类型
ParameterizedType pType = (ParameterizedType) type;//它就是A<String>
Type[] types = pType.getActualTypeArguments();//它就是一个Class数组
Class c = (Class)types[0];//它就是String
}
3.2 反射注解获取
1)要求
- 注解的保留策略必须是RUNTIME
2)反射注解需要从作用目标上返回
- 类上的注解,需要使用Class来获取
- 方法上的注解,需要Method来获取
- 构造器上的注解,需要Construcator来获取
- 成员上的,需要使用Field来获取
Class、Method、Constructor、Field:AccessibleObject 它们都有一个方法:
- Annotation getAnnotation(Class),返回目标上指定类型的注解!
- Annotation[] getAnnotations(),返回目标上所有注解!
定义注解:
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno1 {
String name();
int age();
String sex();
}
使用注解:
@MyAnno1(name="A类", age=1, sex="男")
class A {
@MyAnno1(name="fun1方法", age=2, sex="女")
public void fun1() {
}
}
获取类上的注解属性值:
public class Demo2 {
@Test
public void fun1() {
/*
* 1. 得到作用目标
*/
Class<A> c = A.class;
/*
* 2. 获取指定类型的注解
*/
MyAnno1 myAnno1 = c.getAnnotation(MyAnno1.class);
System.out.println(myAnno1.name() + ", "
+ myAnno1.age() + ", " + myAnno1.sex());
}
}
获取方法上的注解属性值:
@Test
public void fun2() throws SecurityException, NoSuchMethodException {
/*
* 1. 得到作用目标
*/
Class<A> c = A.class;
Method method = c.getMethod("fun1");
/*
* 2. 获取指定类型的注解
*/
MyAnno1 myAnno1 = method.getAnnotation(MyAnno1.class);
System.out.println(myAnno1.name() + ", "
+ myAnno1.age() + ", " + myAnno1.sex());
}
3.3 反射泛型和反射注解的应用
class BaseDAO<T> {
private QueryRunner qr = new TxQueryRunner();
public BaseDAO() {
}
public void add(T bean) throws SQLException {
}
public void update(T bean) {
}
public void delete(String uuid) {
}
public T load(String uuid) {
return null;
}
public List<T> findAll() {
return null;
}
}
上面是定义的一个数据库操作的顶层类BaseDao,加入我们现在要对数据库表的User表进行操作。
class UserDAO extends BaseDAO<User> {
public void addUser(User user) {
}
}
这样的话就给BaseDao的T赋值了为User。
1)反射泛型应用
在BaseDao中获取泛型的类型。并拼凑sql。代码如下:
class BaseDAO<T> {
private QueryRunner qr = new TxQueryRunner();
private Class<T> beanClass;
public BaseDAO() {
// 获取User类型的class类型。
beanClass = (Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
public void add(T bean) throws SQLException {
Field[] fs = beanClass.getDeclaredFields();
// 拼凑sql,几个属性就有几个?.
String sql = "insert into " + beanClass.getSimpleName() + " values(";
for(int i = 0; i < fs.length; i++) {
sql += "?";
if(i < fs.length-1) {
sql += ",";
}
}
sql += ")";
Object[] params = {/*参数值是什么*/}; // 这里的参数怎么获取呢?
qr.update(sql, params);
}
public void update(T bean) {
}
public void delete(String uuid) {
}
public T load(String uuid) {
return null;
}
public List<T> findAll() {
return null;
}
}
在构造方法中获取获取User类型的class类型。
在具体的方法中,基于反射获取class对应的属性,拼凑sql,几个属性就有几个?.
但是,还是有问题,sql的参数怎么获取呢。
2)反射注解的应用。
通常我们的数据库user表的字段和我们的bean对象User中的属性可能字段名不一致,可以通过xml文件的方式来配置。例如:<table name="User" db="user"><properity name="name" column="uname"/></table>
。这里我们完全可以通过注解来替代。
User.java:
@Table("tb_user")//它的值表示当前类对应的表
public class User {
@ID("u_id")//当前属性对应的列名,而且说明这个列是主键
private String uid;
@Column("uname")
private String username;
@Column("pwd")
private String password;
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
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;
}
}
Table注解:
public @interface Table {
String value();
}
ID注解:
public @interface ID {
String value();
}
Column注解:
public @interface Column {
String value();
}
3 Servlet3.0新特性
3.1 概述
- 注解代替web.xml配置文件
- 异步处理
- 对上传的支持
Servlet3.0在市场上没有应用!了解即可。
3.2 替代web.xml
1)url-pattern和init-param替代
<servlet>
<servlet-name>AServlet</servlet-name>
<servlet-class>cn.itcast.web.servlet.AServlet</servlet-class>
<init-param>
<param-name>p1</param-name>
<param-value>v1</param-value>
</init-param>
<init-param>
<param-name>p2</param-name>
<param-value>v2</param-value>
</init-param>
<load-on-startup>1<load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/AServlet</url-pattern>
<url-pattern>/AAServlet</url-pattern>
</servlet-mapping>
</servlet>
WebService替代:
@WebServlet(urlPatterns="/AServlet",
initParams={
@WebInitParam(name="p1", value="v1"),
@WebInitParam(name="p2", value="v2")
},
loadOnStartup=1
)
public class AServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("hello servlet3.0!");
resp.getWriter().print("hello servlet3.0!!");
}
}
2)WebFilter替代
使用@WebFilter替代filter配置。
@WebFilter(urlPatterns="/*")
public class AFilter implements Filter {
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse repsonse,
FilterChain chain) throws IOException, ServletException {
System.out.println("哈哈~,你看到我没有!");
chain.doFilter(request, repsonse);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
3)WebListener替代
使用@WebListener配置监听器的使用。
@WebListener
public class AListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("死掉了");
}
@Override
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("出生了");
}
}
总结:
-
删除web.xml
-
在Servlet类上添加@WebServlet(urlPatterns={"/AServlet"})
-
在Filter类上添加@WebFilter(urlPatterns="/AFilter")
-
在Listener类上添加@WebListener
- 注解好处:配置信息少!
- 注解缺点:不方便修改!
3.3 异步处理
1)什么是异步处理
原来,在服务器没有结束响应之前,浏览器是看不到响应内容的!只有响应结束时,浏览器才能显示结果!
现在异步处理的作用:在服务器开始响应后,浏览器就可以看到响应内容,不用等待服务器响应结束!
2)实现异步的步骤
-
得到AsyncContext,它异步上下文对象
AsyncContext ac = request.startAsync(request,response);
-
给上下文一个Runnable对象,启动它!(给上下文一个任务,让它完成!)
ac.start(new Runnable() { public void run() { ... } });
-
@WebServlet(urlPatterns="/AServlet", asyncSupported=true)
-
resp.setContentType(“text/html;charset=utf-8”);
-
IE如果不能正常输出,这说明响应体大小不足512B,那你需要多输出点废话!
-
AsyncContext#complete():通知Tomcat我们异步线程已经执行结束了!这让Tomcat才会及时的断开与浏览器的连接!
/**
* 添加WebServlet注解
* @author cxf
*
*/
@WebServlet(urlPatterns="/AServlet", asyncSupported=true)
public class AServlet extends HttpServlet {
public void doGet(final HttpServletRequest req, final HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
// 支持IE!如果输出不足512B,没有异步效果!
for(int i = 0; i <= 512; i++) {
resp.getWriter().print("a");
}
resp.getWriter().flush();
/*
* 1. 得到异步上下文对象
*/
final AsyncContext ac = req.startAsync(req, resp);
/*
* 2. 给上下文对象一个Runnable对象,让它执行这个任务
*/
ac.start(new Runnable() {
public void run() {
println("现在马上开始<br/>", resp);
sleep(2000);
for(char c = 'A'; c <= 'Z'; c++) {
println(c+"", resp);
sleep(250);
}
// 通知Tomcat我们已经执行结束了!
ac.complete();
}
});
}
public void println(String text, HttpServletResponse resp) {
try {
resp.getWriter().print(text);
resp.getWriter().flush();
} catch (IOException e) {
}
}
public void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
}
}
ac.complete();
通知tomcat我们的任务执行了。
不加的话,只能等到tomcat超时自动停止转圈圈。
3.4 文件上传
3.4.1 上传概述
上传对表单的要求:
method="post"
enctype="multipart/form-data",它的默认值是:application/x-www-form-urlencoded
<input type="file" name="必须给"/>
上传Servlet的使用:
- request.getParameter()不能再用
- request.getInputStream()使用它来获取整个表单的数据!
commons-fileupload
- 创建工厂
- 解析器
- 使用解析器来解析request对象,得到
List<FileItem>
3.4.2 Servlet3.0对上传提供了支持:
表单不变
- 在Servlet中不需要再使用commons-fileupload,而是使用Servlet3.0提供的上传组件接口!
上传的步骤:
- 使用request.getPart(“字段名”),得到Part实例,
- Part:
String getContentType():获取上传文件的MIME类型
String getName():获取表单项的名称,不是文件名称
String getHeader(String header):获取指定头的值
long getSize():获取上传文件的大小
InputStream getInputStream():获取上传文件的内容
void write(String fileName):把上传文件保存到指定路径下
默认Servlet是不支持使用上传组件:需要给Servlet添加一个注解: @MultipartConfig
它没有提供获取上传文件名称的方法:
这需要我们自己从Content-Disposition头中截取!
3.4.3 案例
@WebServlet(urlPatterns="/AServlet")
@MultipartConfig
public class AServlet extends HttpServlet {
@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
/*
* 1. getParameter()方法可以使用了!!!
*/
String username = req.getParameter("username");//可以使用了!!!
/*
* 2. 获取文件表单字段,对应的Part对象
*/
Part part = req.getPart("resume");
/*
* 3. 从Part中获取需要的数据
*/
// 获取上传文件的MIME类型
System.out.println(part.getContentType());
// 获取上传文件的字节数
System.out.println(part.getSize());
// 获取文件字段名称
System.out.println(part.getName());
// 获取头,这个头中包含了上传文件的名称
System.out.println(part.getHeader("Content-Disposition"));
// 保存上传文件
part.write("E:/xxx.jpg");
// 截取上传文件名称
String filename = part.getHeader("Content-Disposition");
int start = filename.lastIndexOf("filename=\"") + 10;
int end = filename.length() - 1;
filename = filename.substring(start, end);
System.out.println(filename);
}
}
4 动态代理
4.1 动态代理概述
方法的作用:在运行时,动态创建一组指定的接口的实现类对象!(在运行时,创建实现了指定的一组接口的对象)
interface A {
}
interface B {
}
Object o = 方法(传入new Class[]{A.class,B.class})
// 传入一组接口,返回的是一个实现了这些接口的对象。
o它实现了A和B两个接口!
4.2 动态代理参数
Object proxyObject = Proxy.newProxyInstance(ClassLoader classLoader, Class[] interfaces, InvocationHandler h);
方法作用:动态创建实现了interfaces数组中所有指定接口的实现类对象!
参数;
- ClassLoader:类加载器!
它是用来加载器的,把.class文件加载到内存,形成Class对象! - Class[] interfaces:指定要实现的接口们
- InvocationHandler:代理对象的所有方法(个别不执行,getClass())都会调用InvocationHandler的invoke()方法。
动态代理作用:
最终是学习AOP(面向切面编程),它与装饰者模式有点相似,它比装饰者模式还要灵活!
案例:
public class Demo1 {
@Test
public void fun1() {
/*
* 三大参数
* 1. ClassLoader
* 方法需要动态生成一个类,这个类实现了A、B接口,然后创建这个类的对象!
* 需要生成一个类,这个类也需要加载到方法区中,谁来加载,当然是ClassLoader!!!
*
* 2. Class[] interfaces
* 它是要实现的接口们
*
* 3. InvocationHandler
* 它是调用处理器
* 敷衍它!
*
* 代理对象的实现的所有接口中的方法,内容都是调用InvocationHandler的invoke()方法。
*/
ClassLoader loader = this.getClass().getClassLoader();
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("你好,动态代理!");
return "xxx";
}
};
// 使用三大参数创建代理对象!!!
Object o = Proxy.newProxyInstance(loader, new Class[]{A.class, B.class}, h);
// 强转成A和B类型,成功了!
A a = (A) o;
B b = (B) o;
// a.a();
// a.aa();
// b.b();
// b.bb();
// System.out.println(o.getClass().getName());
Object result = a.aaa("hello", 100);
System.out.println(result);
}
}
interface A {
public void a();
public void aa();
public Object aaa(String s, int i);
}
interface B {
public void b();
public void bb();
}
4.3 动态代理装饰目标对象
简单来说,现在我有一个目标对象,我现在想在他的方法前后都执行特定的方法,要怎么做呢?
定义一个接口Waiter.java:
// 服务员
public interface Waiter {
// 服务
public void serve();
}
定义接口的实现类ManWaiter(目标对象类):
public class ManWaiter implements Waiter {
public void serve() {
System.out.println("服务中...");
}
}
测试类:
/**
* 我们必须要掌握的是当前这个案例!
* @author cxf
*
*/
public class Demo2 {
@Test
public void fun1() {
Waiter manWaiter = new ManWaiter();//目标对象
/*
* 给出三个参数,来创建方法,得到代理对象
*/
ClassLoader loader = this.getClass().getClassLoader();
Class[] interfaces = {Waiter.class};
InvocationHandler h = new WaiterInvocationHandler(manWaiter);//参数manWaiter表示目标对象
// 得到代理对象,代理对象就是在目标对象的基础上进行了增强的对象!
Waiter waiterProxy = (Waiter)Proxy.newProxyInstance(loader, interfaces, h);
waiterProxy.serve();//前面添加“您好”, 后面添加“再见”
}
}
class WaiterInvocationHandler implements InvocationHandler {
private Waiter waiter;//目标对象
public WaiterInvocationHandler(Waiter waiter) {
this.waiter = waiter;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("您好!");
this.waiter.serve();//调用目标对象的目标方法
System.out.println("再见!");
return null;
}
}
说明:
WaiterInvocationHandler 包含一个waiter成员,他其实就是目标对象(需要被增强的对象);
在invoke方法中会在调用目标对象serve方法的前后都增强。
waiterProxy.serve();
方法在执行时自动会执行目标对象的invoke。
总结:
目标对象:被增强的对象
代理对象:需要目标对象,然后在目标对象上添加了增强后的对象!
目标方法:增强的内容
代理对象 = 目标对象 + 增强
4.4 动态代理终极版
如果我们想让代理对象和代理内容都可以灵活配置(现在代理内容我们是写死的)?
定义接口类Waiter.java:
// 服务员
public interface Waiter {
// 服务
public void serve();
public void shouQian();
}
定义接口实现类ManWaiter.java:
public class ManWaiter implements Waiter {
public void serve() {
System.out.println("服务中...");
}
public void shouQian() {
System.out.println("混蛋,给我钱!");
}
}
定义前置增强:BeforeAdvice.java:
/**
* 前置增强
* @author cxf
*
*/
public interface BeforeAdvice {
public void before();
}
定义后置增强:AfterAdvice.java:
public interface AfterAdvice {
public void after();
}
定义增强工厂类ProxyFactory :
/**
* 它用来生成代理对象
* 它需要所有的参数
* * 目标对象
* * 增强
* @author cxf
*/
/**
* 1. 创建代理工厂
* 2. 给工厂设置三样东西:
* * 目标对象:setTargetObject(xxx);
* * 前置增强:setBeforeAdvice(该接口的实现)
* * 后置增强:setAfterAdvice(该接口的实现)
* 3. 调用createProxy()得到代理对象
* * 执行代理对象方法时:
* > 执行BeforeAdvice的before()
* > 目标对象的目标方法
* > 执行AfterAdvice的after()
* @author cxf
*
*/
public class ProxyFactory {
private Object targetObject;//目标对象
private BeforeAdvice beforeAdvice;//前置增强
private AfterAdvice afterAdvice;//后置增强
/**
* 用来生成代理对象
* @return
*/
public Object createProxy() {
/*
* 1. 给出三大参数
*/
ClassLoader loader = this.getClass().getClassLoader();
Class[] interfaces = targetObject.getClass().getInterfaces(); // 获取目标类实现的所有接口
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/*
* 在调用代理对象的方法时会执行这里的内容
*/
// 执行前置增强
if(beforeAdvice != null) {
beforeAdvice.before();
}
Object result = method.invoke(targetObject, args);//执行目标对象的目标方法
// 执行后置增强
if(afterAdvice != null) {
afterAdvice.after();
}
// 返回目标对象的返回值
return result;
}
};
/*
* 2. 得到代理对象
*/
Object proxyObject = Proxy.newProxyInstance(loader, interfaces, h);
return proxyObject;
}
public Object getTargetObject() {
return targetObject;
}
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
public BeforeAdvice getBeforeAdvice() {
return beforeAdvice;
}
public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
this.beforeAdvice = beforeAdvice;
}
public AfterAdvice getAfterAdvice() {
return afterAdvice;
}
public void setAfterAdvice(AfterAdvice afterAdvice) {
this.afterAdvice = afterAdvice;
}
}
测试类:
/*
* 目标是让目标对象和增强都可以切换!
*/
public class Demo3 {
@Test
public void fun1() {
ProxyFactory factory = new ProxyFactory();//创建工厂
factory.setTargetObject(new ManWaiter());//设置目标对象
factory.setBeforeAdvice(new BeforeAdvice() {//设置前置增强
public void before() {
System.out.println("您好不好!");
}
});
factory.setAfterAdvice(new AfterAdvice() {//设置后置增强
public void after() {
System.out.println("再见不见!");
}
});
Waiter waiter = (Waiter)factory.createProxy();
waiter.shouQian();
}
public void zhuanZhang() {
/*
* 1.
* 2.
* 3.
*/
}
}
总结一下invoke参数的传参关系。
5 类加载器
5.1 什么是类加载器
把.class文件加载到JVM的方法区中,变成一个Class对象!
Class#getClassLoader()得到类加载器。
5.2 类加载器的分类
类加载器有3种:
- 引导:类库!rt.jar
- 扩展:扩展jar包 ext下的jar包。
- 系统:应用下的class,包含开发人员写的类,和第三方的jar包!classpath下的类!
系统类加载器的上层领导:扩展
扩展类加载器的上层领导:引导
引导没上层,它是BOSS
5.3 类加载器的委托机制
类加载器接收到加载类的任务时,首先交给父类来加载,父类找不到再交给父类的父类,直到找到位置,找不到的话,最后再由当前类加载器来加载。
这样可以保证类加载的唯一性。
5.4 类的解析说明
class MyApp {//被系统加载
public void method() {
A a = new A();//也由系统加载
String s = new String();//也由系统加载!
}
}
说明:
其实单看String类的话,它是由“引导”类加载起来加载的;
MyApp类是由“系统”类加载器来加载的,所有它内部的所有成员也都是由“系统”类加载器来加载。
5.5 自定义加载器
我们也可以通过继承ClassLoader类来完成自定义类加载器,自类加载器的目的一般是为了加载网络上的类,因为这会让class在网络中传输,为了安全,那么class一定是需要加密的,所以需要自定义的类加载器来加载(自定义的类加载器需要做解密工作)。
ClassLoader加载类都是通过loadClass()方法来完成的,loadClass()方法的工作流程如下:
调用findLoadedClass ()方法查看该类是否已经被加载过了,如果该没有加载过,那么这个方法返回null;
判断findLoadedClass()方法返回的是否为null,如果不是null那么直接返回,这可以避免同一个类被加载两次;
如果findLoadedClass()返回的是null,那么就启动代理模式(委托机制),即调用上级的loadClass()方法,获取上级的方法是getParent(),当然上级可能还有上级,这个动作就一直向上走;
如果getParent().loadClass()返回的不是null,这说明上级加载成功了,那么就加载结果;
如果上级返回的是null,这说明需要自己出手了,这时loadClass()方法会调用本类的findClass()方法来加载类;
这说明我们只需要重写ClassLoader的findClass()方法,这就可以了!如果重写了loadClass()方法覆盖了代理模式!
OK,通过上面的分析,我们知道要自定义一个类加载器,只需要继承ClassLoader类,然后重写它的findClass()方法即可。那么在findClass()方法中我们要完成哪些工作呢?
找到class文件,把它加载到一个byte[]中;
调用defineClass()方法,把byte[]传递给这个方法即可。
FileSystemClassLoader
public class FileSystemClassLoader extends ClassLoader {
private String classpath ;
public FileSystemClassLoader() {}
public FileSystemClassLoader (String classpath) {
this.classpath = classpath;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] datas = getClassData(name);
if(datas == null) {
throw new ClassNotFoundException("类没有找到:" + name);
}
return this.defineClass (name, datas, 0, datas.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("类找不到:" + name);
}
}
private byte[] getClassData(String name) throws IOException {
name = name.replace(".", "\\") + ".class";
File classFile = new File(classpath, name);
return FileUtils .readFileToByteArray(classFile);
}
}
ClassLoader loader = new FileSystemClassLoader("F:\\classpath");
Class clazz = loader.loadClass("cn.itcast.utils.CommonUtils");
Method method = clazz.getMethod("md5", String.class);
String result = (String) method.invoke(null, "qdmmy6");
System.out.println(result);
5.6 tomcat的类加载器
Tomcat提供了两种类加载器!
- 服务器类加载器:${CATALINA_HOME}\lib,服务器类加载器,它负责加载这个下面的类!
- 应用类加载器:KaTeX parse error: Undefined control sequence: \WEB at position 15: {CONTEXT_HOME}\̲W̲E̲B̲-INF\lib、{CONTEXT_HOME}\WEB-INF\classes,应用类加载器,它负责加载这两个路径下的类!
引导
扩展
系统
特性:
服务器类加载器:先自己动手,然后再去委托
应用类加载器::先自己动手,然后再去委托