反射机制
Java反射机制就是在运行状态中,对于任意一个类(class文件),都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
对于这种动态获取的信息以及动态调用对象的方法和功能称为Java语言的反射机制。
简述:动态获取类中的信息,就是Java反射
反射机制实现
要想对字节码文件进行解剖,必须要有字节码文件对象
-
获取字节码对象的方式一:
-
Object类中的getClass方法:
Person p = new Person(); Class clazz = p.getClass()
-
-
获取字节码对象的方式二:
-
任何数据类型都具备一个静态的属性.class来获取Class对象:
Class clazz = Person.class
-
-
获取字节码对象的方式三:
-
通过forName()方法实现:
String className = "com.zimo.Person"; Class clazz =Class.forName(className)
-
public static void main(String args[]){
// 无参构造
// 原方法--
com.zimo.Person p = new com.zimo.Person();
// 反射--
String className = "com.zimo.Person";
Class clazz =Class.forName(className);
clazz.newInstance();
--------------------------------------------------------------------------------------------
// 有参构造
// 原方法--
com.zimo.Person p = new com.zimo.Person("zimo", 18);
// 反射--
String className = "com.zimo.Person";
Class clazz =Class.forName(className);
// 先获取构造函数列表
Constructor c = clazz.getConstructor(String.class, int.class);
// 通过构造器的newInstance方法进行对象的初始化
Object obj = c.newInstance("zimo", 18);
--------------------------------------------------------------------------------------------
// 获取字节码文件中的字段
Class clazz =Class.forName("com.zimo.Person");
Filed filed = clazz.getFiled("age"); // 只能获取公共,私有的无法获取
filed = clazz.getDeclaredFiled("age"); // 只能获取本类,但包含私有
// 对私有字段的访问取消权限检查:暴力访问
filed.setAccessible(true);
Object o = clazz.newInstance();
filed.set(o, 86);
Object obj = filed.get(o);
--------------------------------------------------------------------------------------------
// 获取字节码文件中的公共方法
Class clazz =Class.forName("com.zimo.Person");
Method[] me = clazz.getMethods(); // 获取的都是共有的方法
Method[] me1 = clazz.getDeclaredMethods(); // 获取本类中私有方法
Method me2 = clazz.getMethod("show", null); // 获取person类中的show方法,空参数列表
Method me3 = clazz.getMethod("show1", String.class, int.class); // 获取person类中的show方法,有参函数
// 运行show方法
Object obj = clazz.newInstance();
me2.invoke(obj, null);
me3.invoke(obj, "zimo", 18);
}
反射练习
- 电脑运行
package com.zimo.ComputerRun;
// 主板
public class MainBoard{
public void run(){
System.out.println("MainBoard Run ...........");
}
public void usePCI(PCI p){
if(p != null){
p.open();
p.close();
}
}
}
package com.zimo.ComputerRun;
/**
* 电脑运行
*/
public class ReflectTest{
public static void main(String args[]){
MainBoard mb = new MainBoard();
mb.run();
// mb.usePCI(null);
// mb.usePCI(new SourceCard()); // 每次添加一个设备,就需要修改代码传递一个新创建的对象
// 不用new对象,只获取其class文件,内部创建对象:使用配置文件读取
File configFile = new File("pci.properties");
Properties prop = new Properties();
FileInputStream fis = new FileInputStream(configFile);
prop.load(fis);
for(int i= 0;i < prop.size(); i++){
String pciName = prop.getProperty("pci" + (i + 1));
Class clazz = Class.forName(pciName); // 用Class去加载这个pci子类
PCI p = (PCI)clazz.newInstance();
mb.usePCI(p);
}
}
}
package com.zimo.ComputerRun;
public interface PCI{
public void open();
public void close();
}
#pci.properties
# 配置声卡
pci1=com.zimo.Computer.SoundCard
# 配置网卡
pci2=com.zimo.Computer.NetCard
package com.zimo.ComputerRun;
// 声卡
public class SourceCard implements PCI{
public void open(){
System.out.println("sound open...");
}
public void close(){
System.out.println("sound close...");
}
}
package com.zimo.ComputerRun;
// 网卡
public class NetCard {
public void open(){
System.out.println("NetCard open...");
}
public void close(){
System.out.println("NetCard close...");
}
}
- 模拟MyBatis存储任何对象数据
public class Mybatis{
// 提供一个方法:可以保存任何对象数据的字段和具体值
public static void save(Object obj) throws Exception{
PrintStream ps = new PrintStream(new FileOutputStream("./data.txt", true));
// 解析对象的字段和每个字段的值存储起来 obj = Student/Pig/Teacher....
// 1.先得到对象的Class文件对象
Class c = obj.getClass();
// 2.定位它的全部成员变量
Field[] fields = c.getDeclaredFields();
// 3.遍历这些字段并取值
for(Field field : fields){
// 取字段名
String name = field.getName();
// 取字段值
field.setAccessible(true); // 暴力反射 取消访问权限
String value = field.get(obj) + "";
ps.println(name + "=" + value);
}
ps.close();
}
}
public static void main(String args[]){
Student s = new Student(1, "zimo", "18", "秋名山", "10086");
Mybatis.save(s);
Pig p = new Pig("佩奇", 500.0, "粉色", "小红", "母猪");
Mybatis.save(p);
}
反射适合做通用技术框架的底层实现,在框架底层源码我们经常可以看到反射的影子
正则表达式
本身也是一个对象,java.util.regex包中(Pattern类),主要用于操作字符串,通过特定的符号来体现的。
特点:
- 传统QQ号检测方法:
// 需求:定义一个功能对QQ号进行校验
// 要求:长度5-15,只能是数字,0不能开头
public static void checkQQ(String qq){
int len = qq,length();
if(len >=5 && len <= 15){
if(!qq.startsWith("0")){
try{
long l = Long.parseLong(qq);
System.out.println(l + ":正确");
}catch(NumberFormatException e){
System.out.println(qq + ":含有非法字符");
}
}else{
System.out.println(qq + ":开头不能为0");
}
}else{
System.out.println(qq + ":长度错误");
}
}
- 使用正则表达式检测QQ方法:
public static boolean checkQQByRegex(String qq){
String regex = "[1-9][0-9]{4,14}";
boolean b = qq.matches(regex);
System.out.println(b);
return b;
}
public static void main(String args[]){
String QQ = "123456";
checkQQ(QQ);
checkQQByRegex(QQ);
}
正则表达式对字符串常见操作
- 匹配:
- 其实使用的就是String类中的matches()方法
- 切割:
- 其实使用的就是String类中的split()方法
- 替换:
- 其实使用的就是String类中的replaceAll()方法
- 获取:
- 其实使用的就是String类中的get()方法
/**
* 演示匹配
*/
public static void matchesRegex(){
// 匹配手机号码是否正确
String tel = "15800001111";
String regex = "1[3578][0-9]{9}"; // 等价于:"1[3578]\\d{9}"
boolean b = tel.matches(regex);
System.out.println(tel + ":" + b);
}
/**
* 演示切割
*/
public static void sqlitRegex(){
// 切割叠词姓名
String names = "zimo---zhangsan@@@@lisi######wangwu";
// String[] names = str.split(" "); // 只能切割单个空格
String[] names = names.split("(.)\\1+"); // (.)\\1 封装成第一组
System.out.println(tel + ":" + b);
}
/**
* 演示替换
*/
public static void replaceAllRegex(){
String str = "";
str = str.replaceAll("(.)\\1+","$1"); // 用$引用前一个组中的数据 zimo-zhangsan@lisi#wangwu
System.out.println(tel + ":" + b);
// 隐藏手机号158****1111
String tel = "15800001111";
tel = tel.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); // 158****1111
}
/**
* 演示获取
* 正则封装成对象,创建一个匹配器对象,按照表达式匹配字符序列,将结果留在匹配器中
*/
public static void sqlitRegex(){
String str = "da jia hao, ming tian bu fang jia!";
String regex = "\\b[a-z]{3}\\b";
// 将正则规则进行封装
Pattern p = Pattern.compile(regex);
// 通过正则对象的matcher方法字符串相关联。获取要对字符串操作的匹配器对象Matcher
Matcher m = p.matcher(p);
// 通过Matcher匹配器的对象方法对字符串进行操作
while(m.find()){
System.out.println(m.group()); // 获取匹配的子序列
System.out.println(m.start() + ":" + m.end()); // 获取匹配的子序列脚标
}
}
- 练习:
// 治疗口吃:我我...我我要..要要要要...要要学学学..学学学学编编编编...编编程程程程....程程程程程程程程
// ip地址排序
// 对邮件地址校验
public static void main(String args[]){
fun1();
}
// 治疗口吃
public static void fun1(){
String str = "我我...我我要..要要要要...要要学学学..学学学学编编编编...编编程程程程....程程程程程程程程";
// 将字符串中点去掉,替换
str = str.replaceAll("\\.+", ""); // 我我我我要要要要要要要学学学学学学学编编编编编编程程程程程程程程程程程程
// 替换叠词
str = str.replaceAll("(.)\\1+", "$1"); // 我要学编程
}
// ip地址排序 192.168.1.82 127.0.0.1 3.3.3.3 105.70.11.55
public static void fun2(){
String ip_str = "192.168.1.82 127.0.0.1 3.3.3.3 105.70.11.55";
// 为了让ip可以按字符串比较,只要让ip每段的数位相同,补两个0操作
ip_str = ip_str.replaceAll("(\\d+)","00$1");
// 每一段保留三位
ip_str = ip_str.replaceAll("0*(\\d{3})","$1");
// 将ip地址切出
String[] ips = ip_str.split(" +");
// 排序
TreeSet ts = new TreeSet();
for(String ip : ips){
ts.add(ip);
}
for(String ip : ts){
System.out.println(ip.replaceAll("0*(\\d+)", "$1"));
}
}
// 对邮件地址校验
public static void fun3(){
String mail = "[email protected]";
String regex = "[a-zA-Z0-9_]+@[a-zA-Z0-9]+(\\.[a-zA-Z]{1,3}){1,3}";
regex = "\\w+@\\w+(\\.\\w+)+"; // 可以匹配 [email protected]
boolean b = mail.matches(regex);
System.out.println(mail + ":" + b);
}
网页爬虫
用于在互联网中获取符合指定规则的数据
// 爬取邮箱地址
public static void main(String args[]){
List list = getMails();
for(String l : list){
System.out.println(l);
}
}
// 爬取本地数据
public static List getMails(){
// 读取源文件
BufferedReader bufr = new BufferedReader(new FileReader("D:\\mail.html"));
// 对读取的数据进行规则的匹配,从中获取符合规则的数据
String mail_regex = "\\w+@\\w+(\\.\\w+)+";
List list = new ArrayList();
Pattern p = Pattern.compile(mail_regex);
String line = null;
while((line = bufr.readLine())!=null){
Matcher m = p.matcher(line);
while(m.find()){
// 将符合规则的数据存储到集合中
list.add(m.group());
}
}
return list;
}
// 爬取本地数据
public static List getMailsByWeb(){
// 读取源文件
// BufferedReader bufr = new BufferedReader(new FileReader("D:\\mail.html"));
URL url = new URL("http://192.168.1.82:8080/myweb/mail.html");
BufferedReader bufr = new BufferedReader(new InputStreamReader(url.openStream()));
// 对读取的数据进行规则的匹配,从中获取符合规则的数据
String mail_regex = "\\w+@\\w+(\\.\\w+)+";
List list = new ArrayList();
Pattern p = Pattern.compile(mail_regex);
String line = null;
while((line = bufr.readLine())!=null){
Matcher m = p.matcher(line);
while(m.find()){
// 将符合规则的数据存储到集合中
list.add(m.group());
}
}
return list;
}
单元测试框架
org.junit测试框架的使用
package SectionTestDemo;
import org.junit.Assert;
import org.junit.Test;
public class UserServiceTest {
/**
* 测试方法的要求:
* 1. public 修饰
* 2. 没有返回值没有参数
* 3. 必须使用@Test修饰
*/
@Test
public void testLogin(){
UserService userService = new UserService();
String rs = userService.login("admin", "123456");
// 预期断言结果正确性
Assert.assertEquals("err", "success", rs);
}
}
/**
* org.junit.ComparisonFailure: err
* 期望:success
* 实际:error
* 红色:err 绿色:ok
*/
package SectionTestDemo;
public class UserService {
public String login(String name, String pass){
if ("admin".equals(name) && "1234567".equals(pass)){
return "success";
}else {
return "error";
}
}
}
注解说明:
- @Test:测试方法
- @Before:测试实例方法之前 Junit 5:使用的是BeforeEach
- @After:测试实例方法之后 Junit 5:使用的是AfterEach
- @BeforeClass:测试静态方法之前 Junit 5:使用的是BeforeAll
- @AfterClass:测试静态方法之后 Junit 5:使用的是AfterAll
注解
- 用在类上,方法上,成员变量,构造器,.... 上对成分进行编译约束,标记等操作的。
- 注解是JDK1.5的新特性。
- 注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
- 注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。
注解作用
- 标记。
- 方法重写约束@Override
- 函数式接口约束。@FunctionalInterface.
- 现今最牛逼的框架技术多半都是在使用注解和反射。都是属于框架的基础技术。
自定义注解
-
自定义注解格式:
修饰符 @interface 注解名{
注解属性
}
-
小结:
- 自定义注解使用@interface关键字
- 注解默认可以标记很多地方
package com.zimo._01selfNote;
@interface Book{
}
@interface MyTest{
}
@Book
public class MyBook {
@Book
@MyTest
public static void main(@MyTest String[] args) {
@MyTest
int age = 12;
}
}
-
注解属性格式:
- 格式1:
数据类型 属性名()
- 格式2:
数据类型 属性名() default 默认值
- 格式1:
-
属性使用的数据类型:
八种基础数据类型(int,short,long,double,byte,char,boolean,float)
String,Class
以上类型都支持
-
小结:
- 注解可以有属性,属性名必须带()
- 在用注解的时候,属性必须赋值,除非这个属性有默认值!!
package com.zimo._01selfNote;
@interface Book{
// 不写默认public
String name();
String[] authors();
double price();
String address() default "中国-北京";
}
@Book(name = "《精通Java基础》",authors = {"zimo","mike"}, price = 59.9)
public class MyBook {
@Book(name = "《SQL从入门到放弃》",authors = {"小白","递归"}, price = 9.99, address = "中国-上海")
public static void main(String[] args) {
}
}
- 注解的特殊属性名称:value
- value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写!!
- 如果有多个属性,且多个属性没有默认值,那么value是不能省略的
package com.zimo._02zhujie;
@interface Book{
String value();
}
@interface Book1{
String value();
int age();
}
@interface Book2{
String value();
int age() default 18;
}
//@Book(value = "delete.action")
@Book("delete.action") // 可省略value不写
public class MyBook {
}
@Book1(value = "delete.action", age = 12) // 有多个必须全写
@Book2("delete.action") // 若多个值都有默认值
class MyBook1 {
}
元注解
元注解是sun公司提供的,是用在自定义注解上的注解,是用来注解自定义注解的
-
元注解有两个:
-
@Target:约束自定义注解只能在那些地方使用
但是默认的注解可以在类,方法,构造器,成员变量,......使用
ElementType:
- TYPE // 类、接口、枚举类
- FIELD // 成员变量(包括:枚举常量)
- METHOD // 成员方法
- PARAMETER // 方法参数
- CONSTRUCTOR // 构造方法
- LOCAL_VARIABLE // 局部变量
- ANNOTATION_TYPE // 注解类
- PACKAGE // 可用于修饰:包
- TYPE_PARAMETER // 类型参数,JDK 1.8 新增
- TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
-
@Retention:声明注解的生命周期
声明注解的作用范围:编译时,运行时
RetentionPolicy:
- SOURCE // 源文件保留
- CLASS // 编译期保留,默认值
- RUNTIME // 运行期保留,可通过反射去获取注解信息,开发常用
-
注解的解析
package com.zimo._04_ReNote;
import org.junit.Test;
import java.lang.annotation.*;
import java.lang.reflect.Method;
import java.util.Arrays;
@Target({ElementType.TYPE,ElementType.METHOD}) // 类和方法使用
@Retention(RetentionPolicy.RUNTIME) // 永久存活
@interface Book{
String value();
double price() default 100;
String[] author();
}
@Book(value = "《Java基础》",price = 99.9, author = {"zimo","mike"})
class BookStore{
@Book(value = "《MyBatis持久层框架》",price = 199.9, author = {"zimo","jack"})
public void run(){
}
}
public class ReNote {
@Test
public void parseClass(){
// 解析注解
// 1.定位Class类对象
Class c = BookStore.class;
// 2.判断类上是否使用了某个注解
if (c.isAnnotationPresent(Book.class)){
// 3.获取注解对象
Book book = (Book) c.getDeclaredAnnotation(Book.class);
System.out.println(book.value() + book.price() + Arrays.toString(book.author())); // java基础
}
// 1.1 定位方法对象
Method run = c.getDeclaredMethod("run");
// 1.2
if (run.isAnnotationPresent(Book.class)){
// 1.3 获取注解对象
Book book = (Book) c.getDeclaredAnnotation(Book.class);
System.out.println(book.value() + book.price() + Arrays.toString(book.author())); // mybatis
}
}
}
模拟Junt.Test方法
package com.zimo._05_MyTest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
@Target(ElementType.METHOD) // 方法使用
@Retention(RetentionPolicy.RUNTIME)
@interface MyTest{
}
public class MyTestDemo {
@MyTest
public void test01(){
System.out.println("1111");
}
public void test02(){
System.out.println("2222");
}
@MyTest
public void test03(){
System.out.println("3333");
}
public static void main(String[] args) throws Exception{
// 模拟测试类启动按钮
MyTestDemo myTestDemo = new MyTestDemo();
// 1.得到类对象
Class c = MyTestDemo.class;
// 2.获取类全部方法对象
Method[] methods = c.getDeclaredMethods();
// 3.遍历全部方法
for (Method method : methods) {
if (method.isAnnotationPresent(MyTest.class)){
// 运行此方法
method.invoke(myTestDemo);
}
}
}
}
动态代理
动态代理设计模式(让别人替我们去做某些事)
动态代理只能为实现接口的实现类对象做代理(也可以只为接口做代理对象)
- 开发步骤:
- 必须有接口
- 实现类要实现接口,定义自己的业务功能代码
- 为业务功能做代理对象(动态代理)
- 小结:
- 动态代理非常灵活,可以被任意的接口实现类对象做代理
- 动态代理可以为被代理对象的所有接口的所有方法做代理,动态代理可以在不改变方法源码的情况下,实现对方法功能的增强
- 动态代理类的字节码(代理对象)在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码
- 动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java反射机制可以生成任意类型的动态代理类
- 动态代理同时也提高了开发效率
- 缺点:只针对接口或者接口的实现类对象做代理对象,普通类是不能做代理对象的
package com.zimo._06_ActiveDemo;
/**
* 业务接口
*/
public interface UserService {
String login(String admin, String s);
void deleteAll();
}
package com.zimo._06_ActiveDemo;
/**
* 业务实现类
*/
public class UserServiceImpl implements UserService {
@Override
public String login(String admin, String s) {
if ("admin".equals(admin) && "123456".equals(s))return "success";
return "error";
}
@Override
public void deleteAll() {
System.out.println("del success");
}
}
package com.zimo._06_ActiveDemo;
public class TestDemo {
public static void main(String[] args) {
// 1.创建一个业务对象
//UserService userService = new UserServiceImpl();
// 代理模式
UserService userService = ProxyUtil.getProxy(new UserServiceImpl());
String res = userService.login("admin", "123456"); // 走代理,不走原来的UserServiceImpl
System.out.println(res);
userService.deleteAll(); // 走代理
}
}
package com.zimo._06_ActiveDemo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理对象:帮助我们做一个被代理的业务对象返回
* java.lang.reflect.Proxy:这是Java动态代理机制的主类
* 它提供了一个静态方法来为一组接口的实现类动态地生成代理类及其对象(newProxyInstance)
* 参数一:类加载器:负责加载到时候做好的业务代理对象
* 参数二:被代理业务对象的全部实现的接口,以便代理对象可以知道哪些方法做代理
* 参数三:代理正真的执行方法,也就是代理的处理逻辑
*/
public class ProxyUtil {
/**
* 做一个被代理的业务对象返回
* @param userService
* @return
*/
public static UserService getProxy(UserServiceImpl userService){
return (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() {
@Override
// 类加载器,类实现的接口,具体实现方法逻辑
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// o:业务代理对象本身,一般用不到 method:当前正在被代理执行的方法 objects:执行方法的参数
// 添加时间性能分析,不用在每个方法中使用
long startTime = System.currentTimeMillis();
// 真正触发方法执行
Object re = method.invoke(o, objects);
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
return re;
}
});
}
}