内容介绍:
- 自定义Junit框架
- 山寨JPA
自定义Junit框架
请大家始终牢记,用到注解的地方,必然存在三角关系,,并且别忘了设置保留策略为RetentionPolicy.RUNTIME。
代码结构
MyBefore注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyBefore {
}
MyTest注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {
}
MyAfter注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAfter {
}
EmployeeDAOTest
//EmployeeDAO的测试类
public class EmployeeDAOTest {
@MyBefore
public void init() {
System.out.println("初始化...");
}
@MyAfter
public void destroy() {
System.out.println("销毁...");
}
@MyTest
public void testSave() {
System.out.println("save...");
}
@MyTest
public void testDelete() {
System.out.println("delete...");
}
}
MyJunitFrameWork
public class MyJunitFrameWork {
public static void main(String[] args) throws Exception {
// 1.先找到测试类的字节码:EmployeeDAOTest
Class clazz = EmployeeDAOTest.class;
Object obj = clazz.newInstance();
// 2.获取EmployeeDAOTest类中所有的公共方法
Method[] methods = clazz.getMethods();
/* 3.迭代出每一个Method对象
判断哪些方法上使用了@MyBefore/@MyAfter/@MyTest注解
*/
List<Method> mybeforeList = new ArrayList<>();
List<Method> myAfterList = new ArrayList<>();
List<Method> myTestList = new ArrayList<>();
for (Method method : methods) {
if(method.isAnnotationPresent(MyBefore.class)){
//存储使用了@MyBefore注解的方法对象
mybeforeList.add(method);
}else if(method.isAnnotationPresent(MyTest.class)){
//存储使用了@MyTest注解的方法对象
myTestList.add(method);
}else if(method.isAnnotationPresent(MyAfter.class)){
//存储使用了@MyAfter注解的方法对象
myAfterList.add(method);
}
}
// 执行方法测试
for (Method testMethod : myTestList) {
// 先执行@MyBefore的方法
for (Method beforeMethod : mybeforeList) {
beforeMethod.invoke(obj);
}
// 测试方法
testMethod.invoke(obj);
// 最后执行@MyAfter的方法
for (Method afterMethod : myAfterList) {
afterMethod.invoke(obj);
}
}
}
}
执行结果:
山寨JPA
要写山寨JPA需要两个技能:注解+反射。
注解已经学过了,反射还有一个进阶内容,之前那篇反射文章里没有提到。至于是什么内容,一两句话说不清楚。慢慢来吧。
首先,要跟大家介绍泛型中几个定义(记住最后一个):
ArrayList<E>
中的E称为类型参数变量ArrayList<Integer>
中的Integer称为实际类型参数- 整个
ArrayList<E>
称为泛型类型 - 整个
ArrayList<Integer>
称为参数化的类型ParameterizedType
好,接下来看这个问题:
class A<T>{
public A(){
/*
我想在这里获得子类B、C传递的实际类型参数的Class对象
class java.lang.String/class java.lang.Integer
*/
}
}
class B extends A<String>{
}
class C extends A<Integer>{
}
我先帮大家排除一个错误答案:直接T.class是错误的。
所以,你还有别的想法吗?
我觉得大部分人可能都想不到,这不是技术水平高低的问题,而是知不知道API的问题。知道就简单,不知道想破脑袋也没辙。
我们先不直接说怎么做,一步步慢慢来。
请先看下面代码:
public class Test {
public static void main(String[] args) {
new B();
}
}
class A<T>{
public A(){
//this是谁?A还是B?
Class clazz = this.getClass();
System.out.println(clazz.getName());
}
}
class B extends A<String>{
}
请问,clazz.getName()打印的是A还是B?
答案是:B。因为从头到尾,我们new的是B,这个Demo里至始至终只初始化了一个对象,所以this指向B。
好的,到了这里,我们迈出了第一步:在泛型父类中得到了子类的Class对象!
我们再来分析:
class A<T>{
public A(){
//clazz是B.class
Class clazz = this.getClass();
}
}
class B extends A<String>{
}
现在我们已经在class A<T>中得到B类的Class对象。而我们想要得到的是父类A<T>中泛型的Class对象。且先不说泛型的Class对象,我们先考虑,如何获得通过子类Class对象获得父类Class对象?
查阅API文档,我们发现有这么个方法:
Generic Super Class,直译就是“带泛型的父类”。也就是说调用getGenericSuperclass()就会返回泛型父类的Class对象。这非常符合我们的情况。试着打印一下:
打印发现,A<T>的Class对象是ParameterizedType的类型的。
这里我们不去关心Type、ParameterizedType还有Class之间的继承关系,总之以我们多年的编码经验,子类对象的方法总是更多。所以毫不犹豫地向下转型:
成了!现在我们能在父类中得到子类继承时传来的泛型的Class对象。接下来正式开始编写山寨JPA。
User
package myJPA;
public class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
BaseDao<T>
package myJPA;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
public class BaseDao<T> {
private static BasicDataSource datasource = new BasicDataSource();
//静态代码块,设置连接数据库的参数
static{
datasource.setDriverClassName("com.mysql.jdbc.Driver");
datasource.setUrl("jdbc:mysql://localhost:3306/test");
datasource.setUsername("root");
datasource.setPassword("123456");
}
//得到jdbcTemplate
private JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource);
//泛型参数的Class对象
private Class<T> beanClass;
public BaseDao() {
/*this指代子类
通过子类得到子类传给父类的泛型Class对象,假设是User.class
*/
beanClass = (Class) ((ParameterizedType) this.getClass()
.getGenericSuperclass())
.getActualTypeArguments()[0];
}
public void add(T bean) {
//得到User对象的所有字段
Field[] declaredFields = beanClass.getDeclaredFields();
//拼接sql语句,表名直接用POJO的类名
//所以创建表时,请注意写成User,而不是t_user
String sql = "insert into "
+ beanClass.getSimpleName() + " values(";
for (int i = 0; i < declaredFields.length; i++) {
sql += "?";
if (i < declaredFields.length - 1) {
sql += ",";
}
}
sql += ")";
//获得bean字段的值(要插入的记录)
ArrayList<Object> paramList = new ArrayList<>();
for (int i = 0; i < declaredFields.length; i++) {
try {
declaredFields[i].setAccessible(true);
Object o = declaredFields[i].get(bean);
paramList.add(o);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
int size = paramList.size();
Object[] params = paramList.toArray(new Object[size]);
//传入sql语句模板和模板所需的参数,插入User
int num = jdbcTemplate.update(sql, params);
System.out.println(num);
}
}
UserDao
package myJPA;
public class UserDao extends BaseDao<User> {
@Override
public void add(User bean) {
super.add(bean);
}
}
测试类
package myJPA;
public class TestUserDao {
public static void main(String[] args) {
UserDao userDao = new UserDao();
User user = new User("hst", 20);
userDao.add(user);
}
}
测试结果
细心的朋友肯定已经发现,我的代码实现虽然不够完美,但是最让人蛋疼的还是:要求数据库表名和POJO的类名一致,不能忍...
于是,我决定抄袭一下JPA的思路,给我们的User类加一个Table注解,用来告诉程序这个POJO和数据库哪张表对应:
@Table注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
String value();
}
新的User类(类名加了@Table注解)
package myJPA;
@Table("t_user")
public class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
新的测试类
package myJPA;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
public class BaseDao<T> {
private static BasicDataSource datasource = new BasicDataSource();
//静态代码块,设置连接数据库的参数
static{
datasource.setDriverClassName("com.mysql.jdbc.Driver");
datasource.setUrl("jdbc:mysql://localhost:3306/test");
datasource.setUsername("root");
datasource.setPassword("123456");
}
//得到jdbcTemplate
private JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource);
//泛型参数的Class对象
private Class<T> beanClass;
public BaseDao() {
//得到泛型参数的Class对象,假设是User.class
beanClass = (Class) ((ParameterizedType) this.getClass()
.getGenericSuperclass())
.getActualTypeArguments()[0];
}
public void add(T bean) {
//得到User对象的所有字段
Field[] declaredFields = beanClass.getDeclaredFields();
//拼接sql语句,【表名从User类Table注解中获取】
String sql = "insert into "
+ beanClass.getAnnotation(Table.class).value()
+ " values(";
for (int i = 0; i < declaredFields.length; i++) {
sql += "?";
if (i < declaredFields.length - 1) {
sql += ",";
}
}
sql += ")";
//获得bean字段的值(要插入的记录)
ArrayList<Object> paramList = new ArrayList<>();
for (int i = 0; i < declaredFields.length; i++) {
try {
declaredFields[i].setAccessible(true);
Object o = declaredFields[i].get(bean);
paramList.add(o);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
int size = paramList.size();
Object[] params = paramList.toArray(new Object[size]);
//传入sql语句模板和模板所需的参数,插入User
int num = jdbcTemplate.update(sql, params);
System.out.println(num);
}
}
这下真的是山寨JPA了~