分类 未分类 下的文章

当Service方法被内部调用时,Spring注解会失效。就是Spring的Service类,如果public方法加上注解,类内部的其它方法使用this调用该方法,会导致注解失效。

例如Spring的Service实现类如下:

@Service("userService")
class UserServiceImpl implements UserService{
    
    @Override
    @Cacheable(CacheNames="USER_CACHE", key="#userId")
    public User getUser(String userId){
        // do something
    }

    @Override
    public String getUserName(String userId) {
        User u = this.getUser(userId); // getUser方法的@Cacheable注解失效了
        return u == null ? "" : u.getName();
    }
}

原因是this引用的对象没有被Spring代理,调用该对象的public方法时,Spring不能处理相关注解。

解决方法很简单,就是使用Spring代理过的对象,代替this。然后只需解决如果获取该Service类被Spring代理过的对象。


1 循环依赖

就是自己注入自己。在Service类定义一个自身对象的属性,让Spring装配时把自己注入到自己。虽然Spring 5(具体版本待查证)声称解决了循环依赖的问题,但是Spring Boot 2.6.0开始默认设置不允许循环依赖。循环依赖是一个古老的问题,一样认为要避免。所以此方法不推荐

1)先设置

# Spring Boot 2.6.0之后,允许循环依赖
spring.main.allow-circular-references = true

2)上面的例子改为

@Service("userService")
class UserServiceImpl implements UserService{
    
    @Autowired
    private UserService self; // 自己注入自己
    
    @Override
    @Cacheable(CacheNames="USER_CACHE", key="#userId")
    public User getUser(String userId){
        // do something
    }

    @Override
    public String getUserName(String userId) {
        User u = self.getUser(userId); // 用self代替this,注解生效
        return u == null ? "" : u.getName();
    }
}

2 获取装配后的自己

避免Bean的循环依赖,主要思路是,在Bean装配完成后,再获取被Spring代理的自己。至于怎样获取,实现方法是多种多样的。

方法1,从Bean容器中获取自己。即:

UserService self = applicationContext.getBean("userService");

至于怎么获取Bean容器applicationContext,方法也是多样的。

方法2,开启AspectJ自动代理来获取自己。

详细参考:一个Spring AOP的坑!很多人都犯过!

要注意,AspectJ自动代理不只是解决本文档的问题,需考虑是否会带来未知的问题。

开启AspectJ自动代理的方法有多种,这里列出三种:

1)在启动类添加注解:

@EnableAspectJAutoProxy(proxyTargetClass=true, exposeProxy=true)

2)Spring增加配置:

<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true" />

3)Spring Boot的配置文件增加配置:

# 开启AspectJ自动代理
spring.aop.auto=true
# 开启CGLIB代理
spring.aop.proxy-target-class=true

然后就可以在当前Service类的方法中,通过类似的代码调用自身的方法,且能保证该方法的注解正常执行:

User u = ((UserService) AopContext.currentProxy()).getUser(userId);

方法3,延迟执行自己注入自己。

很简单,就是使用@Lazy注解,达到Bean初始化不执行自己注入自己,避免循环依赖的错误。我记得解决Spring 2.x循环依赖的问题时,也是采用延迟注入的配置。此方法写的代码最少,目前倾向采用这种方法。于是,上面的代码改为:

@Service("userService")
class UserServiceImpl implements UserService{
    
    @Lazy
    @Autowired
    private UserService self; // 延迟自己注入自己
    
    @Override
    @Cacheable(CacheNames="USER_CACHE", key="#userId")
    public User getUser(String userId){
        // do something
    }

    @Override
    public String getUserName(String userId) {
        User u = self.getUser(userId); // 用self代替this,注解生效
        return u == null ? "" : u.getName();
    }
}

孩子在国庆长假报了魔方培训班(练字班送的课程)。为了辅导孩子,到B站自学了魔方的入门教程。这个教程不是最快的,也没有说明原理,但是比较容易操作和记忆。整理下来,方便以后再用吧。

教程视频:

【三阶魔方】记忆量最少、方法最简单的入门教程
https://www.bilibili.com/video/BV15i4y187qU?p=1

魔方示意图:

左面左边中间右边右面
3LT3MT3RT 上面(黄色面)
3LL3L3M3R3RR正面,第3层
2LL2L2M2R2RR正面,第2层
1LL1L1M1R1RR正面,第1层
1LB1MB1RB 底面(白色面)

基本定位:

第2层中间黄色,朝上。同时,第2层中间白色,朝底。

基本操作:

  1. 推:右手把正面3个层的右边往上面推,或者左手把正面3个层的左边往上面推。
  2. 回:右手把正面3个层的右边往底面推,或者左手把正面3个层的左边往底面推。
  3. 拨:右手把正面第3层往左面拨,或者左手把正面第3层往右面拨。
  4. 扭:右手把正面整个面往左面扭,或者左手把正面整个面往右面扭。

操作步骤:

1)基础操作:
1.1)黄色面造白色兰花。把黄色面3M、2L、2R、1M,都弄成白色,且不用考虑是否对齐。
1.2)转成白色面十字架。黄色面朝上,随便找一面为正面,拨动第3层,使3M与2M同色,左手或右手“扭”两次。

2)还原第1层(包括底层,白色面)
2.1)此时所有正面的2M与1M同色。
2.2)若3RR白色,拨动第3层成3R与2M同色,然后右手“推拨回”。3L的同理。
2.3)若3RT白色,拨动第3层成3R与2M同色,然后右手“推拨拨回”,再重复2.3。3L的同理。

3)还原第2层
3.1)此时所有正面的2M与第1层同色。
3.2)拨动第3层,找3M、2M、第1层同色(呈倒T)且3M的上面不是黄色。若3M要到2R去,右手“拨推拨回”。若3M要到2L去,左手“拨推拨回”。再按2.2操作。

4)还原上面(只是黄色面)
4.1)此时所有正面的第2层与第1层同色。以下操作需注意方向,但不用考虑正面第3层的颜色。
4.2)上面(黄色面)出现以下黄色组合时,操作:左扭,右推拨回,左拨,右扭。直到出现4.3的图案,再按4.3操作。

  • 反L:3M,2L,2M
  • 一横:2L,2M,2R

4.3)上面(黄色面)出现以下黄色组合时,注意方向但不用考虑正面第3层的颜色,右手“推拨回拨推拨拨回”。不断重复此步骤,直到上面全为黄色。

  • 十字:3M,2L,2M,2R,1M
  • 金鱼:3M,2L,2M,2R,1L,1M
  • 坦克:3M,3R,2L,2M,2R,1M,1R
  • 蝴蝶:3M,3R,2L,2M,2R,1L,1M

5)还原第3层
5.1)拨动第3层,找到一个正面只有3M不同色的,放在左面,执行操作:右推拨回,左拨,右回,左扭,右推推,左拨,右回,左拨,右推拨回,右扭。
5.2)此时有一面完整的,作为背面。左手“推拨回拨推拨拨回”,再执行4.3,完成。

以前一直用的Email客户端是Foxmail,但是某客服邮箱的邮件太多(几千封),一接收,Foxmail就挂了。后来换上Thunderbird就没事了,一直用了几年也没什么问题。但是公司内的同事基本都是用Foxmail,一回复邮件就显示我很异类(Thunderbird回复邮件的默认格式确实也太单调了)。于是找了找解决方案,设置回复邮件的模板。最后虽然不能完全模仿Foxmail的格式,但看着还行,也就这样吧。

首先设置帐户的签名如下,其中·xxx@abc.com·要替换为对应的Email地址:

<hr style="width:210px;height:1px;" color="#b5c4d" size="1" align="left" />
<div style="margin:10px;font-size:10pt">
<div>xxx@abc.com</div>
</div>

然后安装两个“扩展”:ReFwdFormatterSmartTemplate4,并重启Thunder。ReFwdFormatter是用于删除回复邮件时引用原文出现的蓝色竖线。SmartTemplate4就是设置回复邮件的模板。设置步骤如下:

1)打开ReFwdFormatter,取消全部勾选,并只勾选“Remove the '|' prefix from quote in html mail.”,点“Save”按钮保存退出。

2)打开SmartTemplate4,选择对应的帐户。
2.1)点“回复”标签,勾选“将以下模板应用于回复消息”、“替换标准引用头文件”、“使用HTML(例如,bold)”,并在“模板”填入以下内容:

%sig%
<br>
<div style="border:none; border-top:solid #B5C4DF 1.0pt; padding:3.0pt 0cm 0cm 0cm">
<div style="padding-right: 8px; padding-left: 8px; font-size: 12px; font-family: tahoma; color: #000000; background: #efefef; padding-bottom: 8px; padding-top: 8px">
<div><b>发件人:</b> <a href="mailto:%from%" moz-do-not-send="true">%from%</a></div>
<div><b>发送时间:</b> %X:=sent% %Y%-%m%-%d% %H%:%M%:%S%</div>
[[<div><b>收件人:</b> %to(name, bracketMail(angle))%</div>]]
[[<div><b>抄送:</b> %cc(name, bracketMail(angle))%</div>]]
<div><b>主题:</b> %subject%</div>
</div>
</div>

2.2)点“高级” -> “全局设置”。在“邮件内容” -> “顶部换行符数量”,设为“0”。在“高级功能”,取消勾选“插入空格到高亮的光标”、“强制段落模式”。
2.3)点“确定”保存退出。

回复时,除了“收件人”和“抄送”不能定制显示格式,其它都跟Foxmail的一样了。