背景介绍
某个应用,订单数据和用户数据分库,不支持关联查询,订单数据中存储了用户id,其他报表需求中需要查询订单数据,并同时显示订单用户名。或其他需求需要显示用户表其他信息。
常规方式
项目结构如下:
新建常规spring-boot项目,pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.study</groupId>
<artifactId>component</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>component</name>
<description>java自定义组件</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
其中2.2.4.RELEASE,使用2.2.5版本尝试了很多次更新不到spring-boot-starter-aop,老师视频演示的是2.2.5,(很是无解)spring-boot-starter-aop是为了后面使用Spring AOP技术添加的依赖项。常规模式不需要。
首先定义User类对象
package com.study.component.model;
/**
* @ClassName: User
* @Author: liuzz
* @Date: 2020-03-20 10:25
* @Version: 1.0
**/
public class User {
public User(String userid, String userName) {
this.userid = userid;
this.userName = userName;
}
private String userid;//用戶id
private String userName;//用戶名
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
然后定义Order对象
package com.study.component.model;
import com.study.component.service.OrderService;
import com.study.component.service.UserService;
import com.study.component.value_set_component.SetValue;
import java.util.List;
/**
* @ClassName: Order
* @Author: liuzz
* @Date: 2020-03-20 10:25
* @Version: 1.0
**/
public class Order {
private String id;//id
private int orderId;//订单id
private String ownerID;//订单所属用户
//@SetValue(valueFromClass = UserService.class,valueFromMethod = "getUser",paramField = "ownerID",targetFiled = "userName")
private String ownerName;//订单用户名
public Order(String id, int orderId, String ownerID) {
this.id = id;
this.orderId = orderId;
this.ownerID = ownerID;
}
public String getOwnerName() {
return ownerName;
}
public void setOwnerName(String ownerName) {
this.ownerName = ownerName;
}
@Override
public String toString() {
return String.format("%s,%d,%s,%s",id,orderId,ownerID,ownerName);
}
public static void main(String[] args) {
OrderService service = new OrderService();
List<Order> orderList = service.getOrderList("aaa");
for (Order order:orderList) {
System.out.println(order);
}
}
}
分别定义sevice,用来获取order、user对象,因为本地没有安装数据库,故省去了相关dao的定义以及自动注入。OderService初始化时,生成一个订单列表
package com.study.component.service;
import com.study.component.model.Order;
import com.study.component.value_set_component.NeedSetFieldValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: OrderService
* @Author: liuzz
* @Date: 2020-03-20 13:51
* @Version: 1.0
**/
@Service("orderService")
public class OrderService {
//此处省去dao的自动注入
// @Autowired
// private xxxDao dao;
private List<Order> orderList = new ArrayList<Order>();
{
//初始化构造10个对象
for(int i=0;i<10;i++){
Order order = new Order("ID0000"+i,100+i,"ownerID");
orderList.add(order);
}
}
//@NeedSetFieldValue
public List<Order> getOrderList(String userId)
{
//此处orderlist中订单用户名为空
//常规方式,循环遍历orderList,然后请求Userservice中的获取user对象方法,然后设置order对象中的ownername,1侵入时编程,如果涉及到多次查询数据库效率不高
//使用aop+注解+反射方式完成
return orderList;
}
}
package com.study.component.service;
import com.study.component.model.User;
import org.springframework.stereotype.Service;
/**
* @ClassName: UserService
* @Author: liuzz
* @Date: 2020-03-20 16:35
* @Version: 1.0
**/
@Service
public class UserService {
public User getUser(String userId){
//此处省去查询dao方法
User user = new User("ownerID","用户A");
return user;
}
}
定义BeanUtils用来获取spring bean对象。获取spring对象只需要实现ApplicationContextAware类,重写setApplicationContext方法即可
package com.study.component.service;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @ClassName: BeanUtils
* @Author: liuzz
* @Date: 2020-03-20 16:47
* @Version: 1.0
**/
@Component
public class BeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(BeanUtils.applicationContext == null){
BeanUtils.applicationContext = applicationContext;
}
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//根据beanName获取Bean
public static Object getBean(String beanName){
return applicationContext.getBean(beanName);
}
//通过class获取bean
public static <T> T getBean(Class<T> cla){
return applicationContext.getBean(cla);
}
//通过name+class获取bean
public static <T> T getBean(Class<T> tClass, String beanName){
return applicationContext.getBean(beanName,tClass);
}
}
定义ComponentCtroller,用来拦截浏览器请求信息,并返回给浏览器,代码中\r\n只为了后台查看结果更直观,
为了浏览器显示更直观
package com.study.component.controller;
import com.study.component.model.Order;
import com.study.component.service.BeanUtils;
import com.study.component.service.OrderService;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @ClassName: ComponentCtroller
* @Author: liuzz
* @Date: 2020-03-20 16:40
* @Version: 1.0
**/
@RestController
public class ComponentCtroller {
@GetMapping("/hello")
public String hello(@RequestParam(value = "name",defaultValue = "world") String name){
// return String.format("hello:%s",name);
OrderService orderService = (OrderService)BeanUtils.getBean("orderService");
List<Order> orderList = orderService.getOrderList("adasb");
StringBuffer sb = new StringBuffer();
for (Order order:orderList) {
sb.append( order.toString()).append("\r\n").append("<br>");
}
System.out.println(sb.toString());
return sb.toString();
}
}
传统方式获取订单信息为请求OrderService.getOrderList来获取用户id所有订单,然后循环遍历,调用UserService获取User对象,然后将遍历的order对象的所属用户名赋值。或者修改OrderService中方法,遍历赋值,传统方法首先不够通用,如果后面用户表增加一个字段,或者其他调用者需要其他字段,比如邮箱,又要改动Service。侵入性比较强,spring号称无侵入,使用AOP技术,可以实现无侵入编程。
使用注解+反射+AOP实现通用组件功能
新的项目结构如下:
首先定义注解SetValue,用来表示bean中某个属性的值,为下面方式获得,指定注解适用范围为对象的域,运行时生效。注解中定义被注解标识的域(Field)1:由某个对象(spring中称为bean,此处为valueFromClass())2:由1对象的某个方法(valueFromMethod),3:2方法的入参为(paramField)
4:由1对象2方法3参数执行后返回的对象中的哪个域(targetFiled,此示例中的User对象的userName域)
package com.study.component.value_set_component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @AnnotationName: NeedSetFieldValue
* @Author: liuzz
* @Date: 2020-03-20 16:27
* @Version: 1.0
**/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SetValue {
//需要设置值的字段用注解标识
//获取字段值的对象
Class<?> valueFromClass();
//获取字段值的对象方法
String valueFromMethod();
//上送的参数
String paramField();
//获取返回对象的那个属性
String targetFiled();
}
SetValue注解定义完成之后,需要将Order对象中ownerName域添加此注解,指定了owerName是要调用UserService.getUser方法,入参为ownerId返回的User对象中的userName域赋值如下图:
下面需要定义注解NeedSetFieldValue,为下面使用AOP(切面)提供切点。此注解的目标指定为方法
package com.study.component.value_set_component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @AnnotationName: NeedSetFieldValue
* @Author: liuzz
* @Date: 2020-03-23 09:29
* @Version: 1.0
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedSetFieldValue {
}
注解NeedSetFiledValue定义完成后,将OrderService的getOrderList方法添加注解,将此方法添加一个标签,如下图
最后定义切面处理类,切面处理NeedSetFiledValue注解的方法,基本处理逻辑为:
1定义切面,处理NeedSetFiledValue注解的方法,
2反射找到方法执行后返回的对象的类型找到类的所有域,
3遍历类的域,找到SetValue注解的域。
4找到注解的域后,找到此域指定的bean对象、方法、参数对应的域(Order.ownerId)
5遍历2中返回的对象,根据4中参数对应的域找到4中参数对应的值
6反射执行注解中的方法
7找到6中返回的对象中的3中注解的需要获取返回对象的域(User.userName)
8反射将3中注解的域赋值,值为7
package com.study.component.value_set_component;
import com.study.component.service.BeanUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.io.FileDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @ClassName: SetFiledValueAspect
* @Author: liuzz
* @Date: 2020-03-23 09:31
* @Version: 1.0
**/
@Component
@Aspect
public class SetFiledValueAspect{
@Around("@annotation(com.study.component.value_set_component.NeedSetFieldValue)")
public Object doSetFieldValue(ProceedingJoinPoint pjp) throws Throwable {
//执行目标方法
Object ret = pjp.proceed();
//对返回结果值进行属性设置
if(ret instanceof Collection){
setFieldValueForCollection((Collection) ret);
}else{
List<Object> list = new ArrayList<>(1);
list.add(ret);
setFieldValueForCollection(list);
}
return ret;
}
//属性设置方法
private void setFieldValueForCollection(Collection col) {
if(CollectionUtils.isEmpty(col)){
return;
}
//设置值
//1获取到集合元素的class对象反射获取到返回结果的类型
Class<?> clazz = col.iterator().next().getClass();
//2获取返回结果对象里面的字段
Field[] fields = clazz.getDeclaredFields();
// 3遍历处理带有指定注解的属性字段 @SetValue
for (Field field: fields) {
SetValue sv = field.getAnnotation(SetValue.class);
if(sv ==null){
continue;
}
field.setAccessible(true);
//3.1 获取Bean
Object bean =BeanUtils.getBean(sv.valueFromClass());
//3.2 获取要调用的方法
try {
Method method = sv.valueFromClass().
getMethod(sv.valueFromMethod(),clazz.getDeclaredField(sv.paramField()).getType());
//获取入参的值
Field paramField = clazz.getDeclaredField(sv.paramField());
paramField.setAccessible(true);//private get 设置暴力访问权限
Field targetField = null;
//遍历对象设置该字段的值
for(Object object:col){
//获取入参的值
Object paramValue = paramField.get(object);
if(paramValue == null){
continue;
}
Object value = null;
//调用bean方法获取值
value = method.invoke(bean,paramValue);
if(value != null){
//找到获取的对象的目标字段
if(targetField == null){
targetField = value.getClass().getDeclaredField(sv.targetFiled());
targetField.setAccessible(true);
}
//获取字段的值
value = targetField.get(value);
}
//设置值
field.set(object,value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
启动类:
package com.study.component;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ComponentApplication {
public static void main(String[] args) {
SpringApplication.run(ComponentApplication.class, args);
}
}
执行结果: