【零基础上手JavaWeb】23 Java基础加强(动态代理详解)

写在前面,大家好!我是【跨考菌】,一枚跨界的程序猿,专注于后台技术的输出,目标成为全栈攻城狮!这博客是对我跨界过程的总结和思考。如果你也对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("出生了");

	}

}

总结:

  1. 删除web.xml

  2. 在Servlet类上添加@WebServlet(urlPatterns={"/AServlet"})

  3. 在Filter类上添加@WebFilter(urlPatterns="/AFilter")

  4. 在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,应用类加载器,它负责加载这两个路径下的类!

引导
扩展
系统

特性:
服务器类加载器:先自己动手,然后再去委托
应用类加载器::先自己动手,然后再去委托

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值