文章目录
- Java基础 - 底层了解
- 1 Junit单元测试
- 2 反射
- 2.1 Class的常用API
- 2.2 自定义"框架"
- 3 注解Annotation
- 3.1 解析注解
- 4 动态代理
- 参考链接
Java基础 - 底层了解
1 Junit单元测试
Junit 单元测试框架
- Junit 是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应当学习学习使用Junit编写单元测试
- 此外,几乎所有的IDE工具都集成了Junit,这样就可以直接在IDE中编写并运行Junit测试
Junit优点
- Junit可以灵活的选择执行那些测试方法,可以一键执行全部测试方法
- Junit可以生成全部方法的测试报告
- 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试
单元测试快速入门
- 将Junit 的 jar包导入到项目中
- IDEA 通常整合好了 Junit框架,一般不需要导入
- 如果IDEA没有整合好,需要自己手工导入如下2个Junit的jar包到模块
- 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法
- 在测试方法上使用@Test 注解:标注该方法是一个测试方法
- 在测试方法中完成被测试方法的预期正确性测试
- 选中测试方法,选择”Junit运行“,如果测试良好则是绿色;如果测试失败,则是红色
Junit常用注解(Junit 4.xxxx版本)
注解 | 说明 |
@Test | 测试方法 |
@Before | 用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次 |
@After | 用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次 |
@BeforeClass | 用来修饰静态方法,该方法会在所有测试方法之前,只执行一次 |
@AfterClass | 用来修饰静态方法,该方法会在所有测试方法之后,只执行一次 |
- 开始执行的方法:初始化资源
- 执行完之后的方法:释放资源
public class TestUserService {
//修饰实例方法 Before , After
@Before
public void before(){
System.out.println("before");
}
@After
public void after(){
System.out.println("after");
}
//修改静态方法
@BeforeClass
public static void beforeClass(){
System.out.println("BeforeClass");
}
@AfterClass
public static void afterClass(){
System.out.println("AfterClass");
}
/**
测试方法:
1;必须是公开的,无参数,无返回值
2:测试方法必须是 @Test注解标记
*/
@Test
public void testLoginName(){
UserService user = new UserService();
String msg = user.loginName("admin", "123456");
//进行预期结果的正确性测试:断言
/**
org.junit.ComparisonFailure: 您的登录业务可能出现问题
*/
Assert.assertEquals("您的登录业务可能出现问题","登陆成功" , msg);
}
@Test
/**
java.lang.ArithmeticException: / by zero
*/
public void testSelectUsers(){
UserService user = new UserService();
user.selectUsers();
}
}
public class UserService {
/**
* 匹配 账户名 与 密码
* @param loginName 账户名
* @param passWord 密码
* @return 登录信息
*/
public String loginName(String loginName , String passWord){
if ("admin".equals(loginName) && "123456".equals(passWord)){
return "登陆成功" ;
}else {
return "您输入有误,请重新输入";
}
}
public void selectUsers(){
System.out.println(10 / 0);
System.out.println("查询到所有用户");
}
}
2 反射
- 反射:加载类,并允许 以编程的方式解剖类中的各种成分(成员变量、方法、构造器等),常用于框架
- 反射就是把java类中的各种成分映射成一个个的Java对象
- 例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
- 加载过程:Class对象 的 由来是将class文件读入内存,并为之创建一个Class对象
第一步:获取字节码对象
public class StudentTest {
public static void main(String[] args) throws Exception{
//1.类名.class
Class c1 = Student.class;
System.out.println(c1.getName()); //edu.ecjtu.d2_reflect.Student
System.out.println(c1.getSimpleName());//Student
//2.Class 静态方法forName 包名.类名
Class c2 = Class.forName("edu.ecjtu.d2_reflect.Student");
System.out.println(c1 == c2); //true
//3.对象.class
Student s = new Student();
Class c3 = s.getClass();
System.out.println(c3 == c2); //true
}
}
- 在运行期间,一个类,只有一个Class对象产生。
2.1 Class的常用API
//获取包名、类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名
//获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)
//获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
//获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)
//反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(222,"韦小宝");//执行有参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法
//反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null
//反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法
类的构造器
- 获取类的构造器,并对其进行操作
- Constructor<?>[] getConstructors() 获取全部构造器(只能是public修饰的)
- Constructor<?>[] getDeclaredConstructors() 获取全部构造器(只要存在就能拿到)
- Constructor<?> getConstructor(Class < ?> … parameterTypes) 获取某个构造器(public 修饰)
- Constructor<?> getDeclaredConstructor(Class < ?> … parameterTypes) 获取某个构造器(只要存在就能拿到)
- 获取类构造器的作用:依然是初始化对象并返回
- T newInstance (Object … initargs) 调用此构造器对象表示的构造器,并传入参数,完成对想的初始化并返回
- public void setAccessible(boolean flag) 设置为 true ,表示禁止检查访问控制(暴力反射)
//省略成员变量setXxx和getXxx
public class Cat {
public static char sex = '雄';
public static final int COUNT = 2;
private String name;
private int age;
public Cat(){
System.out.println("咖啡猫来了~~~");
}
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
private String sleep(){
return "💤";
}
private void run(){
System.out.println("🐱喜欢跑步");
}
private void eat(){
System.out.println("🐱喜欢吃猫粮");
}
private void eat(String food){
System.out.println("🐱喜欢吃"+food);
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class ConstructorsTest {
@Test
public void testConstructors(){
//1.第一步:获取类的Class对象 !!!!
Class c = Cat.class;
//2.获取全部构造器(public)
// Constructor[] constructors = c.getConstructors();
//2.获取全部构造器(任意)
Constructor[] constructors = c.getDeclaredConstructors();
//3.遍历获取每个构造器
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + "------>"
+ constructor.getParameterCount());
}
}
@Test
public void testConstructor() throws Exception{
//1.获取类的Class对象
Class c = Cat.class;
//2.获取某个构造器(public)
Constructor<Cat> constructor = c.getConstructor(String.class,int.class);
System.out.println(constructor.getName() + "------------>"
+ constructor.getParameterCount());
constructor.setAccessible(true);//禁止检查访问控制
//获取对象
Cat ci = constructor.newInstance("咖啡猫",2);
System.out.println(ci);
//3.获取任意的某个构造器
Constructor constructor1 = c.getDeclaredConstructor();
System.out.println(constructor1.getName() + "------------>"
+ constructor1.getParameterCount());
constructor1.setAccessible(true);
Cat ci1 = (Cat) constructor1.newInstance();
}
}
- 一般先 构造器 setAccessible(true) 有访问权限,然后 newInstance(…)创建对象
类的成员变量
- 获取类的成员变量
- public Field[] getFields() 获取类的全部成员变量(public )
- public Field[] getDeclaredFields() 获取类的全部成员变量(只要能存在就能拿到)
- public Field getField(String name “成员变量名”) 获取类的某个成员变量(public )
- public Field getDeclaredField(String name) 获取 类的某个成员变量(任意)
- 获取到成员变量的作用:依然是赋值、取值
- void set(Object obj,Object value) 赋值
- Object get(Object obj) 取值
- public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)
public class FieldTest {
@Test
public void testField() throws Exception{
//1.定义类的Class对象
Class c = Cat.class;
//2.获取类的成员变量(public)
// Field[] fields = c.getFields();
//获取任意的成员变量
Field[] fields = c.getDeclaredFields();
//3.遍历
for (Field field : fields) {
System.out.println(field.getName() + "--->"
+ field.getType());
}
//4.定位某个成员变量
Field fName = c.getDeclaredField("name");
System.out.println(fName.getName() + "---->"
+ fName.getType());
Field fAge = c.getDeclaredField("age");
System.out.println(fAge.getName() + "---->"
+ fAge.getType());
//5.赋值
Cat c1 = new Cat();
fName.setAccessible(true); //禁止访问控制权限
fName.set(c1,"咖啡猫");
System.out.println(c1);
//6.取值
fAge.setAccessible(true);
System.out.println(fName.get(c1));
System.out.println(fAge.get(c1)); //默认值 0
}
}
运行结果
sex—>char
COUNT—>int
name—>class java.lang.String
age—>int
name---->class java.lang.String
age---->int
咖啡猫来了~~~
Cat{name=‘咖啡猫’, age=0}
咖啡猫
0
类的成员方法
- 获取成员方法
- Method[] getMethods() 获取类的全部成员方法(public )
- Method[] getDeclaredMethods() 获取类的全部成员方法(任意)
- Method getMethod(String name ,Class<?> … parameterTypes) 获取类的某个成员方法(public )
- Method getDeclaredMethod(String name,Class<?> … parameterTypes)获取类的某个成员方法(任意)
- 成员方法的作用:依然是执行
- public Object invoke(Object obj 实体类对象 ,Object … args 传参) 触发某个对象的该方法执行
- public void setAccessible(boolean flag) 设置为true ,表示禁止检查访问控制(暴力反射)
public class MethodTest {
@Test
public void testMethod() throws Exception{
//1.定义类的Class对象
Class c = Cat.class;
//2.获取所有的方法(public)
// Method[] methods = c.getMethods();
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName() + "---->" +
method.getParameterCount() +
"---->" + method.getReturnType());
}
//3.获取某个方法
Method method = c.getDeclaredMethod("eat",String.class);
System.out.println(method.getName() + "---->" +
method.getParameterCount() +
"---->" + method.getReturnType());
//4.运行
Cat cat = new Cat();
method.setAccessible(true);
method.invoke(cat,"🐟");
Method sleep = c.getDeclaredMethod("sleep");
sleep.setAccessible(true);
Object invoke = sleep.invoke(cat);
System.out.println(invoke);
}
}
运行结果:
eat---->0---->void
eat---->1---->void
setAge---->1---->void
getAge---->0---->int
run---->0---->void
toString---->0---->class java.lang.String
getName---->0---->class java.lang.String
setName---->1---->void
sleep---->0---->class java.lang.String
eat---->1---->void
咖啡猫来了~~~
🐱喜欢吃🐟
💤
2.2 自定义"框架"
利用框架存储对象的成员变量
public class ObjectSaveFrame {
//接收 实体类对象
public static void objectSave(Object obj) throws Exception {
//打印流
PrintStream ps = new PrintStream(new FileOutputStream("src/data.txt",true));
//1.获取类的Class对象
Class c = obj.getClass();
String name = c.getSimpleName();
ps.println("--------------"+name+"--------------");//打印
//2.遍历成员变量
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
String fName = field.getName();
String s = field.get(obj)+"";//取值
ps.println(fName+"="+s); //打印
}
}
}
//测试
public class TestFrame {
@Test
public void testObjectSave() throws Exception {
//目标 : 利用框架存储对象的成员变量
Student s = new Student("小王",21,'男',180.5,"足球,篮球");//String name, int age, char sex, double height, String hobbies
Teacher t = new Teacher("刘老师",5000.0);//String name, double salary
ObjectSaveFrame.objectSave(s);
ObjectSaveFrame.objectSave(t);
}
}
3 注解Annotation
- Java代码里的特殊标记,比如:@Override,@Test等,作用:让其他程序根据注解信息来决定怎么执行该程序
- 注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上等位置处
- 注解本质上是一个接口,Java中所有注解都是继承了Annotation接口的
- @注解(…):其实就是一个实现类对象,实现了该注解以及Annotation接口
注解的原理
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。
而memberValues的来源是Java常量池
这个运行时生成的动态代理对象是可以导出到文件的,方法有两种
在代码中加入System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
在运行时加入jvm 参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
原文链接:
注解的分类
- 1 元注解 :修饰注解的注解,元注解是一种基本注解,但是它能够应用到其他的注解上面(给其他普通注解进行解释)
@Retention
、@Documented
、@Target
、@Inherited
、@Repeatable
5 种
- 1.
@Target
声明被修饰的注解只能在哪些位置使用,@Target(ElementType. TYPE)
- TYPE,类、接口和枚举
- FIELD,成员变量
- METHOD,成员方法
- PARAMETER,方法参数
- CONSTRUCTOR,构造器
- LOCAL_VARIABLE,局部变量
- PACKAGE 可以给一个包进行注解
- ANNOTATION_TYPE 可以给一个注解进行注解
- 2.
@Retention
声明注解的保留周期,@Retention(RetentionPolicy.RUNTIME)
- 1.SOURCE 只作用在源码阶段,在编译器进行编译时它将被丢弃忽视 ,字节码文件中不存在
- 2.CLASS**(默认值)**保留到字节码文件阶段,它并不会被加载到 JVM 中,运行阶段不存在
- 3.RUNTIME**(开发常用)**一直保留到运行阶段,被加载到JVM,程序运行时可以获取它们
- 3.
@Documented
该注解和文档有关,它的作用是能够将注解中的元素包含到 Javadoc中去 - 4.
@Inherited
它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解 进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
- 注解A被@Inherited修饰,A修饰超类,B继承超类且没有其他注解,则B继承了超类的注解
- 5.
@Repeatable
是 JDK1.8中新增的一种注解,作用:允许是同一个程序元素多次使用相同的注解。
通过使用 @Repeatable 注解,我们可以在一个程序元素上多次使用相同的注解。
在使用 @Repeatable 注解时,我们需要同时提供一个容器注解(container annotation)来封装可重复注解的多个实例
- 一些约束:
- @Repeatable 所声明的注解,其元注解
@Target
的使用范围要比@Repeatable
的值声明的注解中的@Target
的范围要大或相同,否则编译器错误,显示@Repeatable
值所声明的注解的元注解@Target
不是@Repeatable
声明的注解的@Target
的子集 -
@Repeatable
注解声明的注解的元注解@Retention
的周期要比@Repeatable
的值指向的注解的@Retention
得周期要小或相同
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Roles.class)
public @interface Role {
String name();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
Role[] value();
}
public class RoleClass {
@Role(name = "李明")
@Role(name = "振华")
public void test(){
}
@Roles(value = {@Role(name = "老王"),@Role(name = "老刘")})
public void test1(){
}
}
public class RoleTest {
@SuppressWarnings("all") //内置注解,抑制编译器警告
public static void main(String[] args) {
Method[] methods = RoleClass.class.getDeclaredMethods();
try {
for (Method method : methods) {
Annotation[] annotations = method.getDeclaredAnnotations();
for (Annotation annotation : annotations) {
System.out.println(method.getName()+"---->注解:"+annotation);
}
}
System.out.println(1/0); //编译器 没有"黄色"⚠
}catch (Exception e){
e.printStackTrace();
}
}
}
结果:
test---->注解:@edu.ecjtu.d3_annotation.Roles(value=[@edu.ecjtu.d3_annotation.Role(name=李明), @edu.ecjtu.d3_annotation.Role(name=振华)])
test1---->注解:@edu.ecjtu.d3_annotation.Roles(value=[@edu.ecjtu.d3_annotation.Role(name=老王), @edu.ecjtu.d3_annotation.Role(name=老刘)])
- 2 内置注解
-
@Override
检测重写 -
@Deprecated
已过时,只是一个标志 -
@SuppressWarrings("all")
抑制编译器生成 警告⚠ 用于方法、类等上面,不能是具体语句上面
- 3 自定义注解
- 格式
- public @interface 注解名称 {
public 属性名称 属性名() default 默认值;
}
3.1 解析注解
什么是注解的解析?
- 就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来;
如何解析注解?
- 指导思想:要解析 谁上面的注解,就应该先拿到谁
- 比如要解析类上面的注解,则应该先获取该类的 Class对象,再通过Class对象解析其上面的注解
- 比如要解析成员方法上的注解,则应该先获取到该成员方法 的 Method对象,再通过Method对象再解析其上面的注解
-
Class
、Method
、Field
、Constructor
都实现了 AnnotatedElement接口,它们都拥有解析注解的能力
AnnotatedElement接口提供了解析注解的方法 | 说明 |
public Annotation[] getDeclaredAnnotations( ) | 获取当前对象上面的注解 |
public T getDeclaredAnnotation(Class < T > annotationClass) | 获取指定的注解对象 |
public boolean isAnnotationPresent( Class < Annotation > annotationClass ) | 判断当前对象上是否存在某个注解 |
/**
* 自定义注解
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {
String value();
double aaa() default 100;
String[] bbb();
}
@MyTest4(value = "aaa" , aaa = 100.0 , bbb = {"bbb","ccc"})
public class Demo {
@MyTest4(value = "hhh" , aaa = 105.0 , bbb = {"bbb","ccc"})
public void test1(){
}
}
/**
* 解析注释
*/
public class AnnotationTest3 {
@Test
public void testDemo() throws Exception {
//1.定义Class对象
Class c = Demo.class;
//2.解析类的注释对象
// Annotation demo = c.getDeclaredAnnotation(Demo.class);
if (c.isAnnotationPresent(MyTest4.class)){
MyTest4 demo = (MyTest4) c.getDeclaredAnnotation(MyTest4.class);
System.out.println(demo.value());
System.out.println(demo.aaa());
System.out.println(Arrays.toString(demo.bbb()));
}
//3.解析方法的注释对象
Method method = c.getDeclaredMethod("test1");
if (method.isAnnotationPresent(MyTest4.class)){
MyTest4 demo = (MyTest4) method.getDeclaredAnnotation(MyTest4.class);
System.out.println(demo.value());
System.out.println(demo.aaa());
System.out.println(Arrays.toString(demo.bbb()));
}
}
}
运行结果:
aaa
100.0
[bbb, ccc]
hhh
105.0
[bbb, ccc]
4 动态代理
动态代理
是基于 拦截器 和 反射 实现的,不需要第三方库支持,只需要 JDK 环境即可
- 必须实现 InvocationHandler 接口;
- 使用 Proxy.newProxyInstance 产生代理对象;
- 被代理的对象必须要实现接口;
- 内部采用asm技术动态生成字节码
如何为 Java 对象创建一个代理对象?
- java.lang.reflect.
Proxy
类:提供了为对象产生 代理对象的方法:
- public static Object
newProxyInstance
(ClassLoader loader,Class< ? > interfaces ,InvocationHandler h)
- ClassLoader loader 用于指定用哪个类加载器,去加载生成的代理类
- Class <?> interfaces 指定接口,这些接口用于指定生成的代理长什么样,也就有哪些方法
- InvocationHandler 用来指定生成的代理对象要 干什么事情(将用户业务对象,定位为代理对象)
/**
* 代理行为
*/
public interface Star {
String sing(String name);
void dance();
}
public class BigStar implements Star{
private String name;
public BigStar(){}
public BigStar(String name) {
this.name = name;
}
@Override
public String sing(String name) {
System.out.println(this.name + "正在唱" + name);
return "谢谢大家";
}
@Override
public void dance() {
System.out.println(this.name + "正在优美地跳舞~~~~");
}
}
/**
* 目标:实现一个代理工具类
*/
public class ProxyUtil {
/**
* 返回一个 代理人
* @param bigStar 大明星对象
* @return 明星代理人
*/
public static Star createProxy(BigStar bigStar){
// Object newProxyInstance(ClassLoader loader,
// Class<?>[] interfaces,
// InvocationHandler h)
/**
* 参数一:指定一个类的加载器 通常为本类的加载器
* 参数二:指定生成的代理长什么样子,也就是哪些方法 比较抽象
* 参数三:用来指定 生成的代理对象要干什么事情
*/
Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{Star.class},
new InvocationHandler() {
@Override
/**
* public Object invoke(Object proxy, Method method, Object[] args)
* 参数一:真代理对象
* 参数二:代理方法
* 参数三:代理方法的具体参数
*/
//回调方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理对象要做什么事情
if (method.getName().equals("sing")){
System.out.println("准备话筒,收款20W");
}else if (method.getName().equals("dance")){
System.out.println("准备场地,收款10W");
}
return method.invoke(bigStar,args); //对于没有返回值的方法调用,得到null
}
});
return starProxy;
}
}
public class Test {
public static void main(String[] args) {
BigStar s = new BigStar("杨超越");
//根据用户业务对象, !!!!定义一个代理对象
Star starProxy = ProxyUtil.createProxy(s);
//sing
String rs = starProxy.sing("恭喜发财");
System.out.println(rs);
//dance
starProxy.dance();
}
}
运行结果: