Статьи

Mixin в Java с аспектами — для примера черт Scala

Черты Scala позволяют смешивать новое поведение в классе. Рассмотрим две особенности добавления полей аудита и версии к сущностям JPA:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package mvcsample.domain
 
import javax.persistence.Version
import scala.reflect.BeanProperty
import java.util.Date
 
trait Versionable {
  @Version
  @BeanProperty
  var version: Int = _
}
 
trait Auditable {
  @BeanProperty
  var createdAt: Date = _
 
  @BeanProperty
  var updatedAt: Date = _
}

Теперь смешаем «Versionable» и «Auditable» с их полями и поведением в элементе Member:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Entity
@Table(name = 'members')
class Member(f: String, l: String) extends BaseDomain with Auditable with Versionable {
 
  def this() = this(null, null)
 
  @BeanProperty
  var first: String = f
 
  @BeanProperty
  var last: String = l
 
  @OneToMany(fetch = FetchType.EAGER, mappedBy = 'member')
  @BeanProperty
  var addresses: java.util.List[Address] = _
}
 
trait BaseDomain {
  @BeanProperty
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = 'id')
  @Id
  var id: Long = 0
}

Приведенный выше класс Member теперь будет работать с классом BaseDomain и с характеристиками Versionable и Auditable. Этот вид смешивания невозможен с простой Java, так как эквивалент признаков с полями и поведением будет абстрактным (или конкретным) классом, а Java позволяет наследовать только от 1 базового класса. Однако с AspectJ можно достичь эквивалента mixin. Рассмотрим следующие аспекты, определенные с использованием языка Aspectj:

01
02
03
04
05
06
07
08
09
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
package mvcsample.aspect;
 
import javax.persistence.Column;
import javax.persistence.Version;
import mvcsample.annot.Versioned;
 
public interface Versionable {
 
    static aspect VersionableAspect {
        declare parents: @Versioned  mvcsample.domain.* implements Versionable;
 
        @Version
        @Column(name = 'version')
        private Integer Versionable.version;   
 
        public Integer Versionable.getVersion() {
        return this.version;
            }
 
        public void Versionable.setVersion(Integer version) {
        this.version = version;
        }
    }
}
 
package mvcsample.aspect;
 
import java.util.Date;
import javax.persistence.Column;
 
import mvcsample.annot.Audited;
public interface Auditable {
    static aspect AuditableAspect {
        declare parents: @Audited mvcsample.domain.* implements Auditable ;
 
        @Column(name='created_at')
        private Date Auditable.createdAt;
 
        @Column(name='updated_at')
        private Date Auditable.updatedAt;
 
        public Date Auditable.getCreatedAt(){
            return this.createdAt;
        }
 
        public void Auditable.setCreatedAt(Date createdAt) {
            this.createdAt = createdAt;
        }
 
        public Date Auditable.getUpdatedAt(){
            return this.updatedAt;
        }
 
        public void Auditable.setUpdatedAt(Date updatedAt) {
            this.updatedAt = updatedAt;
        }
    }
}

‘объявить родителей: @Versioned mvcsample.domain. * реализует Versionable;’ Конструкция aspectj добавляет интерфейс «Versionable» в качестве родительского для любого класса в пакете «mvcsampple.domain», аннотируемого @Versioned, аналогично тому, как для «Auditable». Затем аспект заключается в добавлении полей в интерфейс Versionable, который, в свою очередь, заканчивает добавлением (смешиванием) полей в целевые классы сущностей, таким образом поля и методы, связанные с аудитом и версией, смешиваются в классы сущностей. С этими двумя определенными аспектами целевой класс сущности будет выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Entity
@Table(name="members")
@Access(AccessType.FIELD)
@Versioned
@Audited
public class Member extends BaseDomain{
  
 public Member(){}
  
 public Member(String first, String last){
  this.first = first;
  this.last = last;
 }
  
 private String first;
  
 @Size(min=1)
 private String last;
  
 @OneToMany(fetch=FetchType.EAGER, mappedBy="member")
 private List<address>addresses = new ArrayList<>();
        .....
}
</address>

Поля и поведение, определенные в аспектах Versionable и Auditable, будут смешаны в эту сущность (в более общем случае — в любую сущность с аннотациями @Versioned и @Audited). Вероятно, не так чисто, как черты Scala, но работает хорошо

Ссылка: Mixin in Java с аспектами — для примера черт Scala от нашего партнера JCG Биджу Кунджуммена в блоге all and sundry.