Spring通過AOP在Service層注入使用者資訊
使用基於註解形式的AOP配置,在Service層或其他層,注入使用者登入資訊,這樣就不需要在用到使用者登入資訊時總是注入Http相關物件,不用手動setter使用者資訊,且使用者登入資訊會隨著session失效而自動登出。
.
相關類如下:
相關類說明:
- AppUserContextConfig:模組的配置類,負責掃描模組的Bean,當需要使用此模組時,用Import(AppUserContextConfig.class)的形式引入。
- UserEnv :自定義註解,使用時標識在屬性或者getter方法上,value指明要注入的使用者資訊的名稱,未指定則使用屬性名稱首字母小寫作為使用者資訊屬性的名稱。
- UserEnvAnnotationProcessor:@UserEnv註解的解析類,負責解析@UserEnv上的資訊並從使用者登入資訊中獲取相應的屬性值。
- AopArchitecture:AOP結構類,定義了Spring容器內所有的切入點
- AspectAdviceConfig:切面通知定義類,引用了AopArchitecture的切入點,定義了Advice,即通知方法
- LoginUser:儲存使用者登入資訊的Model,使用者登入資訊類是其屬性的一個子集,使用者登入後將登入資訊拷貝到此Model中,@UserEnv的屬性值就是從此Model中獲取
- AppUser:工具類,負責提供靜態儲存使用者登入資訊及清空登入資訊的方法,和靜態的獲取登入資訊的方法
下面先看使用方法:
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/login", method = RequestMethod.POST)
public boolean login(@RequestBody User user, HttpServletRequest request) {
user = userService.login(user);
if (user == null) {
return false;
}
//拷貝使用者登入資訊
AppUser.resideUserEnv(user);
//設定session有效期,當失效時,LoginUser會自動銷燬
request.getSession().setMaxInactiveInterval(30);
return true;
}
@RequestMapping(method = RequestMethod.PUT)
//測試注入的方法
public boolean testUserEnvInjection(@RequestBody User user) {
return userService.modifyUser(user);
}
@RequestMapping(value = "/logout", method = RequestMethod.DELETE)
public void logout() {
System.out.println(AppUser.isLogin());
//清空使用者登入資訊
AppUser.clearUserEnv();
System.out.println(AppUser.isLogin());
}
}
Service層很簡單,僅僅是做一個登入驗證:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User login(User user) {
String userName = user.getUserName();
String password = user.getPassword();
User result = null;
if (Character.isDigit(userName.charAt(0))) {
result = userMapper.loginByTele(userName, password);
} else {
result = userMapper.loginByName(userName, password);
}
return result;
}
@Override
public boolean modifyUser(@UserEnv User user) {
return false;
}
}
以上就是使用此模組的方法,很簡單。
下面就開始看模組內各類的程式碼及註釋:
先看模組引入類: AppUserContextConfig
@Configuration
@ComponentScan() // 當此註解內不指定掃描包路徑時,會使用此類的包名作為掃描的路徑,即"com.bob.config.mvc.userenv"
public class AppUserContextConfig {
}
當需要使用此模組時,Import(AppUserContextConfig .class)就能使用,切記不適用時不要掃描到這些類.
接下來看AOP結構定義類,此類定義了框架內所有的切入點,這麼做僅僅是為了讓所有的切入點配置在一起,不是強制的。
@Order(1)
@Aspect
@Component
public class AopArchitecture {
/**
* 面向Service層的切入點
*/
@Pointcut("execution(public * com.bob.config.mvc.service.*.*(..))")
public void serviceMethod() {
}
/**
* 面向{@code UserEnv }註解的切入點
*/
@Pointcut("@args(com.bob.config.mvc.userenv.ann.UserEnv)")
public void injectMethod() {
}
}
接下來看Advice定義類,此類定義了在AOP切入點執行的方法,我們在此使用註解的形式注入使用者登入資訊
/**
* 面向切面的功能實現,對@UserEnv註解的屬性使用使用者資訊填充
*
* @since 2016年12月6日 下午4:52:22
*
*/
@Order(2)
@Aspect
@Component
public class AspectAdviceConfig {
final static Logger LOGGER = LoggerFactory.getLogger(AspectAdviceConfig.class);
@Autowired
private UserEnvAnnotationProcessor userEnvAnnotationProcessor;
/**
* Service層注入使用者登入資訊
*
* {@link com.bob.config.mvc.userenv.aspect.AopArchitecture#serviceMethod}
*
* @param joinpoint
* @throws Exception
*/
@Before("com.bob.config.mvc.userenv.aspect.AopArchitecture.serviceMethod()")
public void beforeServiceCall(JoinPoint joinpoint) throws Exception {
Object[] args = joinpoint.getArgs();
Signature sig = joinpoint.getSignature();
MethodSignature msig = (MethodSignature) sig;
Method method = msig.getMethod();
boolean debug = LOGGER.isDebugEnabled();
if (args != null && args.length != 0) {
long t1 = System.nanoTime(), t2 = 0;
boolean injected = false;
StringBuilder sb = null;
if (debug) {
sb = new StringBuilder(1024);
sb.append("觸發Service方法執行前注入, 方法名稱: [").append(sig.toLongString()).append("], ");
sb.append("注入引數列表: ");
}
Annotation[][] annotations = method.getParameterAnnotations();
int i, j;
for (i = 0; i < annotations.length; i++) {
Object bean = args[i];
if (bean == null) {
continue;
}
Annotation[] pa = annotations[i];
if (pa.length == 0) {
continue;
}
if (UserEnv.class.isInstance(pa[0])) {
injected = populateEnv(bean, debug, sb);
continue;
}
for (j = 1; j < pa.length; j++) {
if (UserEnv.class.isInstance(pa[j])) {
injected = populateEnv(bean, debug, sb);
break;
}
}
}
if (debug && injected) {
t2 = System.nanoTime();
sb.append("\n完成注入共計耗時: ").append((t2 - t1) / 1000000.0).append("ms");
LOGGER.debug(sb.toString());
}
}
}
/**
* Service層方法執行過完之後
*
* {@link com.bob.config.mvc.userenv.aspect.AopArchitecture#serviceMethod}
*
* @param joinpoint
* @param retVal
*/
@AfterReturning(pointcut = "com.bob.config.mvc.userenv.aspect.AopArchitecture.serviceMethod()", returning = "retVal")
public void afterServiceCall(JoinPoint joinpoint, Object retVal) {
}
/**
* 填充@UserEnv標識的變數
*
* @param bean
* @param debug
* @param sb
* @throws Exception
* @return
*/
private boolean populateEnv(Object bean, boolean debug, StringBuilder sb) throws Exception {
boolean result = false;
try {
result = userEnvAnnotationProcessor.process(bean, sb);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw e;
}
return result;
}
}
接下來看自定義註解,通過註解標識在屬性上或者getter方法上,來指定待注入的屬性。通過註解的value值,或者是待注入屬性的名稱,優先value,來從使用者登入資訊擇取想要的資訊。
/**
* @since 2016年12月7日 下午2:01:13
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface UserEnv {
/**
* 環境變數的名稱
*
* @return
*/
String value() default "";
}
接下來看註解的解析類,解析結果將被快取,設定快取的大小,使用LinkedHashMap進行快取淘汰,注入前驗證型別符合,
/**
* {@link @UserEnv}註解的解析器
*
* @since 2016年12月7日 下午5:03:15
* @version $Id$
* @author JiangJibo
*
*/
@Component
public class UserEnvAnnotationProcessor {
final static Logger LOGGER = LoggerFactory.getLogger(UserEnvAnnotationProcessor.class);
private static final int CACHE_LIMIT = 256;
private static final Object INJECTION_LOCK = new Object();
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>();
private final Set<Class<?>> nonAnnotatedClass = new HashSet<Class<?>>();
/**
* 解析Class後生成的InjectionElement快取
*/
@SuppressWarnings("serial")
private volatile Map<Class<?>, Set<InjectionElement>> injectionMetadataCache =
new LinkedHashMap<Class<?>, Set<InjectionElement>>(64, 0.75f, true) {
/* (non-Javadoc)
* @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
*/
@Override
protected boolean removeEldestEntry(Entry<Class<?>, Set<InjectionElement>> eldest) {
return size() > getCacheLimit();
}
};
/**
* 預設注入的註解為{@link @UserEnv}
*/
public UserEnvAnnotationProcessor() {
autowiredAnnotationTypes.add(UserEnv.class);
}
/**
* 處理Bean,注入{@link @UserEnv}註解
*
* @param bean
* @param sb
* @return
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public boolean process(Object bean, StringBuilder sb) throws IllegalArgumentException, IllegalAccessException {
Assert.notNull(bean, "被注入物件不能為空");
Class<?> beanClass = bean.getClass();
if (!beanClass.isAnnotationPresent(UserEnv.class)) {
LOGGER.warn("[{}]未標識有[{}]註解", beanClass.getSimpleName(), UserEnv.class.getSimpleName());
return false;
}
if (nonAnnotatedClass.contains(beanClass)) {
LOGGER.warn("[{}]內部不含指定的[{}]註解屬性或方法", beanClass.getSimpleName(), UserEnv.class.getSimpleName());
return false;
}
Set<InjectionElement> injectionElements = injectionMetadataCache.get(beanClass);
if (injectionElements == null) {
synchronized (INJECTION_LOCK) {
injectionElements = injectionMetadataCache.get(beanClass);
if (injectionElements == null) {
injectionElements = buildAutowiredMetadata(beanClass);
if (injectionElements.isEmpty()) {
LOGGER.warn("未找到標註[{}]註解的屬性或方法", autowiredAnnotationTypes.toString());
nonAnnotatedClass.add(beanClass);
return false;
}
injectionMetadataCache.put(beanClass, injectionElements);
}
}
}
return injectByField(bean, injectionElements, sb) | injectByMethod(bean, injectionElements, sb);
}
/**
* 根據屬性注入
*
* @param bean
* @param sb
* @return
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
private boolean injectByField(Object bean, Set<InjectionElement> injectionElements, StringBuilder sb)
throws IllegalArgumentException, IllegalAccessException {
boolean success = false;
for (InjectionElement injectionElement : injectionElements) {
if (!injectionElement.isField) {
continue;
}
Field field = (Field) injectionElement.member;
ReflectionUtils.makeAccessible(field);
String fieldName = field.getName();
String key = injectionElement.key;
key = StringUtils.hasText(key) ? key : fieldName;
if (!isTypeMatch(field.getType(), key, fieldName)) {
continue;
}
Object value = AppUser.getUserEnv(key);
field.set(bean, value);
sb.append("[" + fieldName + "] = [" + value + "],");
success = true;
}
return success;
}
/**
* 根據getter()方法注入
*
* @param bean
* @param sb
* @return
* @throws SecurityException
* @throws NoSuchMethodException
*/
private boolean injectByMethod(Object bean, Set<InjectionElement> injectionElements, StringBuilder sb) {
boolean success = false;
for (InjectionElement injectionElement : injectionElements) {
if (injectionElement.isField) {
continue;
}
Method method = (Method) injectionElement.member;
PropertyDescriptor proDesc = BeanUtils.findPropertyForMethod(method);
String fieldName = proDesc.getName();
String key = injectionElement.key;
key = StringUtils.hasText(key) ? key : fieldName;
if (!isTypeMatch(method.getReturnType(), key, fieldName)) {
continue;
}
Object value = AppUser.getUserEnv(key);
ReflectionUtils.invokeMethod(proDesc.getWriteMethod(), bean, value);
sb.append("[" + fieldName + "] = [" + value + "],");
success = true;
}
return success;
}
/**
* 校驗待注入的成員的型別
*
* @param fieldType
* @param key
* @param fieldName
* @return
*/
private boolean isTypeMatch(Class<?> fieldType, String key, String fieldName) {
if (!fieldType.isAssignableFrom(String.class) && !fieldType.isAssignableFrom(Integer.class)) {
LOGGER.warn("環境變數[{}]注入失敗,暫時只支援String和int型別的注入", fieldName);
return false;
}
Object value = AppUser.getUserEnv(key);
if (null == value) {
LOGGER.warn("環境變數[{}]注入失敗,未查詢到指定名稱的環境變數", fieldName);
return false;
}
if (!value.getClass().isAssignableFrom(fieldType)) {
LOGGER.warn("環境變數:[{}]的型別不匹配待注入的屬性:[{}]", key.getClass().getName(), fieldName);
return false;
}
return true;
}
/**
* 獲取類上標註了{@link @UserEnv}註解的成員資訊
*
* @param clazz
* @return
*/
private Set<InjectionElement> buildAutowiredMetadata(final Class<?> clazz) {
final Set<InjectionElement> injectionElements = new HashSet<InjectionElement>();
ReflectionUtils.doWithLocalFields(clazz, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
AnnotationAttributes ann = findAutowiredAnnotation(field);
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
LOGGER.warn("[{}]註解不適用於靜態方法:[{}]", autowiredAnnotationTypes.toString(), field);
return;
}
injectionElements.add(new InjectionElement(field, ann.getString("value")));
}
}
});
ReflectionUtils.doWithLocalMethods(clazz, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
AnnotationAttributes ann = findAutowiredAnnotation(method);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
LOGGER.warn("註解不適用於靜態方法: [{}]", autowiredAnnotationTypes.toString(), method);
return;
}
if (!method.getName().startsWith("get") || method.getParameterTypes().length > 0) {
LOGGER.warn("[{}]註解只適用於getter方法,而非: [{}]方法", autowiredAnnotationTypes.toString(), method);
return;
}
injectionElements.add(new InjectionElement(method, ann.getString("value")));
}
}
});
return injectionElements;
}
/**
* 獲取一個成員成標註了{@link @UserEnv}註解(或元註解)的註解資料
*
* @param ao
* @return
*/
private AnnotationAttributes findAutowiredAnnotation(AnnotatedElement ao) {
if (ao.getAnnotations().length > 0) {
for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ao, type);
if (attributes != null) {
return attributes;
}
}
}
return null;
}
/**
* 獲取快取容量
*
* @return
*/
public int getCacheLimit() {
return CACHE_LIMIT;
}
/**
* 清空快取
*/
public void clearCache() {
this.injectionMetadataCache.clear();
}
/**
* 待注入的成員資訊
*
* @since 2017年1月22日 上午9:12:19
* @version $Id$
* @author JiangJibo
*
*/
private static class InjectionElement {
private Member member;
private boolean isField;
private String key;
/**
* @param member
* @param key
*/
public InjectionElement(Member member, String key) {
this.member = member;
this.isField = (member instanceof Field);
this.key = key;
}
}
}
接下來看儲存使用者登入資訊的LoginUser類,我們將此類定義成一個Bean,作用域是session,因此此Bean到時候會儲存在HttpSession中,會隨著session的失效而銷燬。且對生成這個Bean的建構函式做了限制,例項化此Bean是一定要將使用者的原始資訊物件作為引數,LoginUser將會拷貝使用者的原始登入資訊。
@Component()
@Scope("session")
public class LoginUser implements Serializable {
private static final long serialVersionUID = 8555472524102629901L;
private volatile Integer id;
private volatile String userName;
private volatile String password;
private volatile Integer age;
private volatile String telephone;
private volatile String adress;
@Autowired()
public LoginUser(Object userInfo) {
BeanUtils.copyProperties(userInfo, this);
}
setter,getter ....
}
最後看工具類AppUser,此類負責將登入資訊存入LoginUser,及從LoginUser從獲取指定的資訊,從Spring容器中獲取LoginUser Bean。
@Component
public class AppUser implements BeanFactoryAware {
final static Logger LOGGER = LoggerFactory.getLogger(AppUser.class);
private static BeanFactory beanFactory;
public static final String USER_ENV_BEAN_NAME = ClassUtils.getShortNameAsProperty(LoginUser.class);
/**
* 獲取當前Bean例項
*
* @return
*/
public static AppUser getAppUser() {
return beanFactory.getBean("appUser", AppUser.class);
}
/**
* 根據使用者屬性名稱獲取登入使用者資訊
*
* @param name
* @return
*/
public static Object getUserEnv(String name) {
LoginUser loginUser = beanFactory.getBean(USER_ENV_BEAN_NAME, LoginUser.class);
try {
Field field = ReflectionUtils.findField(LoginUser.class, name);
field.setAccessible(true);
return field.get(loginUser);
} catch (Exception e) {
LOGGER.error("嘗試注入屬性:[{}]時出現異常", name, e);
throw new CustomizedException(e);
}
}
/**
* 將使用者的登入資訊寄存在應用(session)中
*
* @param user
*/
public static void resideUserEnv(Object user) {
beanFactory.getBean(LoginUser.class, user);
}
/**
* 清除使用者登入資訊
*/
public static void clearUserEnv() {
((DefaultListableBeanFactory) beanFactory).destroyScopedBean(USER_ENV_BEAN_NAME);
}
/**
* 驗證是否有使用者登入
*
* @return
*/
public static boolean isLogin() {
try {
beanFactory.getBean(USER_ENV_BEAN_NAME);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
AppUser.beanFactory = beanFactory;
}
}