Java Spring 基础

IOC(DI)

IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”,还有些书籍翻译成为“控制反向”或者“控制倒置”。
1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先提出了IOC 这个概念。简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。

IOC的别名:依赖注入(DI Dependency Injection),通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。类似于电脑主机的USB接口,遵循USB接口标准接入各种外设

IOC实现:反射(Reflection),就是根据给出的类名(字符串方式)来动态地生成对象。我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中或者扫描注解给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件或者注解来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。

bean概念:由IOC容器管理的那些组成你应用程序的对象我们就叫它Bean, Bean就是由Spring容器初始化、装配及管理的对象。Bean之间的依赖关系这就需要配置元数据,在Spring中由BeanDefinition代表

实现步骤:

  1. 加载并且保存Spring配置文件路径信息然后保存到configLocation中
  2. 刷新Spring上下文环境
  3. 创建并且载入DefaultListableBeanFactory(即BeanFactory)
  4. 根据DefaultListableBeanFactory创建XMLBeanDefinitionReader,用于后面读取xml配置文件信息
  5. 创建BeanDefinitionDelegate代理类,用于解析xml配置信息
  6. 解析xml中配置的等不同的标签信息,以便于可以使用不同的解析器进行解析
  7. 通过XMLBeanDefinitionReader结合location路径信息读取Resources资源信息
  8. 使用BeanDefinitionDelegate代理类解析BeanDefinition bean元素并且依次进行实例化操作,实例化完毕之后将Bean信息注册(put)到BeanDefinitionMap中以便于可以下次继续使用

各个原理概念简介:

  1. BeanDefinition 对依赖翻转模式中管理对象依赖关系的数据抽象
    • 实现依赖翻转功能的核心数据结构
    • 依赖翻转功能都是围绕对BeanDefinition 处理完成的
    • 有了这些BeanDefinition 基础数据结构,容器才能发挥作用
  2. BeanFactory 定义了IOC 容器的基本功能规范
    • IOC容器最基本形式
    • 遵守的基本契约
    • 最底层最基本编程规范
    • 仅仅是一个接口
      DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等都是附加了某种功能的具体实现
      DefaultListableBeanFactory 包含了ioc 的重要功能(容器系列中的一个基本产品)
  3. FactoryBean和BeanFactory
    • 前者是一个Bean
    • 后者是IOC容器(对象工厂),一个Factory
  4. XmlBeanFactory原理
    • 提供了最基本的ioc 容器的功能
    • 读取XML 形式 BeanDefinition 的ioc 容器
    • XMLDefinitionReader 处理xml 形式的BeanDefinition
    • 信息来源有 Resource类来给出
    • Resource 类是封装io操作的类
    • loadBeanDefinitions 方法:ioc 容器初始化重要方法
  5. BeanFactory 和ApplicationContext
    • BeanFactory 实现是ioc 容器基本形式
    • ApplicationContext 是高级形式(具有增强特性)
  6. IOC 容器初始化
    • BeanDefinition 的Resource定位、载入和注册
    • 这三个过程使用不同模块完成:
      • BeanDefinition 的资源定位由ResourceLoader通过统一形式Resource接口完成
        • BeanDefinition 存在形式:FileSystemResource、ClassPathResource等
      • BeanDefinition 载入:把用户定义好的Bean表示成IOC容器的数据结构,即BeanDefinition
        • POJO对象在IOC 容器内部的抽象
      • BeanDefinition 注册:BeanDefinitionRegistry 接口的实现类来完成的
        • IOC 容器内部是通过一个HashMap 持有这些对象的
      • IOC 容器初始化和上下文初始化一般不包括依赖注入
        • lazyInit属性,会在第一次getBean 时注入

beanFactory和ApplicationContext的区别:

  1. 通过ApplicationContext上下文容器:BeanFactory需要手动注册,而ApplicationContext则是自动注册,当在加载xml配置文件时,配置文件中的配置的bean已经被实例化
  2. BeanFactory:在加载配置文件时,配置文件中的bean不被实例化,只有当通过getBean(),获取bean实例的时候才被创建。BeanFactory提供了IOC容器最基本功能,而 ApplicationContext 则增加了更多支持企业级功能支持。ApplicationContext完全继承BeanFactory,因而BeanFactory所具有的语义也适用于ApplicationContext。

循环依赖处理

1
2
3
4
5
6
7
8
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
}
public interface BeanFactory {
Object getBean(String name) throws BeansException;
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
...
}

类图

BeanFactory
DefaultListableBeanFactory
ApplicationContext

BeanDefinition
RootBeanDefinition
XMLBeanDefinitionReader

容器初始化
XMLWebApplicationContext

AOP

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

单一职责原则

使用场景

  • 事务,通过抛出异常回滚
  • 日志
  • 权限
  • 错误处理
  • 处理缓存

相关概念

  • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice(增强,通知):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是环绕执行的代码。
  • Target(目标对象):织入 Advice 的目标对象。
  • AOP代理(AOP Proxy): AOP框架创建的对象,将通知应Advice用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象为目标对象的业务逻辑功能加上被切入的切面所形成的对象。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
  • Weaving(织入):将 Aspect切面应用到Target对象, 并创建 Adviced object 的过程

在 Spring AOP 中 Joint point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 通过 point cut, 我们就可以确定哪些 Joint point 可以被织入 Advice.

AOP中的Joinpoint可以有多种类型:构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化。也就是说在AOP的概念中我们可以在上面的这些Joinpoint上织入我们自定义的Advice,但是在Spring中却没有实现上面所有的joinpoint,确切的说,Spring只支持方法执行类型的Joinpoint。
具体说就是需要做某些处理(如打印日志、处理缓存等等)的连接点,AspectJ中的注解:

  • @annotation 限定有指定注解的连接点
  • execution() 用于匹配是连接点的执行方法
  • target 限定连接点匹配目标对象为指定类型

切面编程:
AOP切面概念
各个概念汇总:
AOP概念汇总
类图:
AOP类图
Advice 的类型

  • @Before ,before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
  • @AfterReturning, after return advice, 在一个 join point 正常返回后执行的 advice
  • @AfterThrowing, after throwing advice, 当一个 join point 抛出异常后执行的 advice
  • @After, after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
  • @Around, around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.
  • introduction

代理对象

Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。

CGLIB和Java动态代理的区别
Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效

CGLIB:
cglib概念

ASM框架

ASM框架是一个致力于字节码操作和分析的框架,它可以用来修改一个已存在的类或者动态产生一个新的类。ASM提供了一些通用的字节码转换和分析算法,通过这些算法可以定制更复杂的工具。ASM提供了其它字节码工具相同的功能,但是它更关注执行效率,它被设计的更小更快,它被用于以下项目:

  • openjdk,实现lambda表达式调用, Nashorn编译器
  • Groovy和Kotlin编译器
  • Cobertura 和Jacoco,测量代码范围
  • CGLIB动态代理类

生成代理对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
...
/**
*
* 获取代理类要实现的接口,除了Advised对象中配置的,还会加上SpringProxy, Advised
* 检查上面得到的接口中有没有定义 equals或者hashcode的接口
* 调用Proxy.newProxyInstance创建代理对象
*
*/
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
...
}

代理对象织入
我们知道InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
...
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;

TargetSource targetSource = this.advised.targetSource;
Object target = null;

try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
// eqauls()方法,具目标对象未实现此方法
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
// hashCode()方法,具目标对象未实现此方法
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {// Advised接口或者其父接口中定义的方法,直接反射调用,不应用通知
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}

Object retVal;

if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}

// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();// 获得目标对象的类
Class<?> targetClass = (target != null ? target.getClass() : null);

// Get the interception chain for this method.// 获取可以应用到此方法上的Interceptor列表
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {// 如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args)
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...// 创建MethodInvocation
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}

// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
}

获取可以应用到此方法上的通知链(Interceptor Chain),如果有,则应用通知,并执行joinpoint; 如果没有,则直接反射执行joinpoint。而这里的关键是通知链是如何获取的以及它又是如何执行的,下面逐一分析下。

从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
public class AdvisedSupport extends ProxyConfig implements Advised {
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(method);
List<Object> cached = this.methodCache.get(cacheKey);
if (cached == null) {
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.methodCache.put(cacheKey, cached);
}
return cached;
}
}

实际的获取工作其实是由AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice()这个方法来完成的,获取到的结果会被缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializable {
/**
* 从提供的配置实例config中获取advisor列表,遍历处理这些advisor.如果是IntroductionAdvisor,
* 则判断此Advisor能否应用到目标类targetClass上.如果是PointcutAdvisor,则判断
* 此Advisor能否应用到目标方法method上.将满足条件的Advisor通过AdvisorAdaptor转化成Interceptor列表返回.
*/
@Override
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, @Nullable Class<?> targetClass) {

// This is somewhat tricky... We have to process introductions first,
// but we need to preserve order in the ultimate list.
List<Object> interceptorList = new ArrayList<>(config.getAdvisors().length);
Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
// 查看是否包含IntroductionAdvisor
boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);
// 这里实际上注册一系列AdvisorAdapter,用于将Advisor转化成MethodInterceptor
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();

for (Advisor advisor : config.getAdvisors()) {
if (advisor instanceof PointcutAdvisor) {
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
// 将Advisor转化成Interceptor
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
// 检查当前advisor的pointcut是否可以匹配当前方法
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {
if (mm.isRuntime()) {
// Creating a new object instance in the getInterceptors() method
// isn't a problem as we normally cache created chains.
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
else if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
else {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}

return interceptorList;
}

/**
* Determine whether the Advisors contain matching introductions.
*/
private static boolean hasMatchingIntroductions(Advised config, Class<?> actualClass) {
for (int i = 0; i < config.getAdvisors().length; i++) {
Advisor advisor = config.getAdvisors()[i];
if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (ia.getClassFilter().matches(actualClass)) {
return true;
}
}
}
return false;
}

}

这个方法执行完成后,Advised中配置能够应用到连接点或者目标类的Advisor全部被转化成了MethodInterceptor

接下来我们再看下得到的拦截器链是怎么起作用的JdkDynamicAopProxy.java:

1
2
3
4
5
6
7
8
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
} else {
// 创建MethodInvocation
invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}

从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建MethodInvocation,调用其proceed方法,触发拦截器链的执行,具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
...
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// 如果Interceptor执行完了,则执行joinPoint
return invokeJoinpoint();
}

Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// 如果要动态匹配joinPoint
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;

// 动态匹配:运行时参数是否满足匹配条件
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {

// 执行当前Intercetpor
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.

// 动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
// 执行当前Intercetpor
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
...
}

应用实例

日志:

1
2
3
4
5
6
7
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {

String value() default "";
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Aspect
@Component
public class SysLogAspect {
@Autowired
private SysLogService sysLogService;

@Pointcut("@annotation(vip.infotech.common.annotation.SysLog)")
public void logPointCut() {

}

@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;

//保存日志
saveSysLog(point, time);

return result;
}

private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();

SysLogEntity sysLog = new SysLogEntity();
SysLog syslog = method.getAnnotation(SysLog.class);
if(syslog != null){
//注解上的描述
sysLog.setOperation(syslog.value());
}

//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");

//请求的参数
Object[] args = joinPoint.getArgs();
try{
String params = new Gson().toJson(args[0]);
sysLog.setParams(params);
}catch (Exception e){

}

//获取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
//设置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));

//用户名
String username = ((SysUserEntity) SecurityUtils.getSubject().getPrincipal()).getUserName();
sysLog.setUserName(username);

sysLog.setTime(time);
sysLog.setCreateTime(new Date());
//保存系统日志
sysLogService.insert(sysLog);
}
}

注解

  • Order 加载顺序调整

可能遇到问题

注入对象为空

可能是加载顺序导致的,可以用Order注解或者调整xml配置顺序。或者对象增加构造函数,在存在多层调用的情况在外层通过构造函数把对象传递进去。