Spring5

IOC

IOC本质

控制反转IoC(inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方式。

在没有IoC的程序中,对象的创建与对象之间的关系完全是由背后的程序控制,控制反转后会将对象的创建转移给用户。

之前在MyBatis的学习中,采用XML方式配置Bean,Bean的定义信息和实现是分离的,然而采用注解可以将实现整合到Bean中,这样就达到了零配置的目的。

IOC代码实现

源码下载:Spring-ioc.rar

非IOC思想

先在com.theo.dao下创建UserDao接口类

1
2
3
public interface UserDao {
List<User> GetUser();
}

对应com.theo.ropo下创建User的JavaBean

1
2
3
4
5
public class User {
int id;
String username;
String password;
}

这次不同于MyBatis的以配置文件方式实现接口类方法,我们创建实现类UserDaoimpldao包下:

1
2
3
4
5
6
public class UserDaoimpl implements UserDao{
public List<User> GetUser() {
System.out.println("start GetUser");
return null;
}
}

在测试类中:以传统方式我们需要UserDao user = new UserDaoimpl();后才能调用GetUser()。这或许看起来很合理,因为我们都一直是这么做的。但试想顾客要改变需求,我们需要新增UserDaoimpl2,然后在测试类中重新new对象再调用方法。这就造成了需要频繁更动测试类代码,主要是因为要new对象

Spring的Bean容器可以在配置文件帮你实例化各种类,然后测试类中就只需要从容器中取对象即可。

bean.xml

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

</beans>

resource下新建bean.xml(名字随意),先填写以上配置内容,说明该配置文件下的Bean都会存放在Spring容器中。

通过<bean>标签可以实例化各种类(不只是JavaBean,还可以是Dao层的实现类等等)

1
2
3
4
5
6
7
<bean id="User" class="com.theo.pojo.User">
<property name="id" value="1"/>
<property name="username" value="tzq"/>
<property name="password" value="1234567"/>
</bean>
<bean id="UserDaoimpl" class="com.theo.dao.UserDaoimpl"/>
<bean id="UserDaoimpl1" class="com.theo.dao.UserDaoimpl1"/>

id就是实例化类的对象名,class就是需要实例化的类名,需要填写全限域名。等于说一个bean标签就做了UserDao UserDaoimpl = new UserDaoimpl();这么一件事。

对于没有变量的类,bean标签只需要指明id和class即可。对于有变量的类,需要通过property标签一一赋值,特殊的:如果变量类型也是类,则需要通过ref="id"来赋值,其中id就是已经被实例化的类,如UserDaoimpl

配置完bean标签后,我们来看下相比传统方法,我们如何调用GetUser方法:

1
2
3
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDaoimpl = (UserDao) context.getBean("UserDaoimpl");
userDaoimpl.GetUser();

第一步是获取Bean容器,其中有各种已经实例化好的对象。第二步通过id直接获取对象,默认返回Object,需要强转类型。

可能现在并不能看出这种方式能有什么特别优秀的地方,如果项目中再新添Controller层(用来调用Dao层方法),这样测试类中只需要拿Controller层的对象,然后如果用户需要使用UserDaoimpl2的功能,就只需要在bean.xml的Controller的bean标签中修改ref值。这样测试类中的代码完全不需要动!!!

这样就展现出了与传统方法不同的点,也就是IoC的基本思想:对象由Spring来创建,管理,装配!

Spring配置

  • 别名
1
<bean id="UserDaoimpl" class="com.theo.dao.UserDaoimpl" name="UserDao"/>

通过取别名,getBean()就能通过别名来取,可以去多个别名,之间用逗号分隔。

  • import

如果多人开发不同类,分别写了不同bean.xml,整合这些配置文件时只需要在主配置文件applicationContext.xml<import resource="bean.xml">,同时创建Bean容器时改成applicationContext.xml即可获取所有子配置文件中的对象了。

依赖注入

我们创建一个拥有各种类型的属性的JavaBean:

1
2
3
4
5
6
7
8
9
public class Student {
int id;
String name;
Address address; //含有属性String Country;String Province;
String[] books;
List<String> hobbies;
Map<String,String> card;
Properties info;
}

注意一定要配有Setter方法和无参的构造方法,因为依赖注入是通过无参构造对象,Setter方法注入属性值

Set注入

这样对应不同的类型有不同的注入方式,Set注入参照以下:

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
<bean id="address" class="com.theo.pojo.Address">
<property name="country" value="China"/>
<property name="province" value="JiangXi"/>
</bean>
<bean id="Student" class="com.theo.pojo.Student">
<!-- 普通注入,属性类型为基本类型-->
<property name="name" value="TZQ"/>
<!-- Bean注入,属性类型为Java类-->
<property name="address" ref="address"/>
<!-- 数组注入-->
<property name="books">
<array>
<value>红楼梦</value>
<value>金瓶梅</value>
</array>
</property>
<!-- List注入-->
<property name="hobbies">
<list>
<value>看电影</value>
<value>听歌</value>
</list>
</property>
<!-- Map注入-->
<property name="card">
<map>
<entry key="身份证" value="1234567890"/>
<entry key="招商银行卡" value="0987654321"/>
</map>
</property>
<!-- properties注入-->
<property name="info">
<props>
<prop key="学号">201909</prop>
<prop key="email">123@qq.com</prop>
</props>
</property>
</bean>

构造器注入

如果实体类的属性没有配置Setter方法,配置属性是依靠有参构造方法实现的,Set注入将无法使用:

1
2
3
4
<bean id="address" class="com.theo.pojo.Address">
<constructor-arg index="0" value="China"/>
<constructor-arg index="1" value="JiangXi"/>
</bean>

c命名/p命名空间注入

  • c命名空间

先在beans标签头部导入xml约束。

1
xmlns:c="http://www.springframework.org/schema/c"

c命名空间适用于简单的属性注入:如基本类型,要求JavaBean中有无参构造方法

1
<bean id="Address" class="com.theo.pojo.Address" c:country="China" c:province="SiChuan"/>
  • p命名空间

先在beans标签头部导入xml约束。

1
xmlns:p="http://www.springframework.org/schema/p"

p命名空间适用于简单的属性注入,但必须要求JavaBean中有无参,有参构造方法!!

1
<bean id="Address" class="com.theo.pojo.Address" p:country="China" p:province="SiChuan"/>

Bean作用域

单例模式(默认模式)

1
2
3
4
<bean id="address" class="com.theo.pojo.Address" scope="singleton">
<property name="country" value="China"/>
<property name="province" value="JiangXi"/>
</bean>

这样重复通过getBean()获取的对象都是同一个对象。

原型模式

1
2
3
4
<bean id="address" class="com.theo.pojo.Address" scope="prototype">
<property name="country" value="China"/>
<property name="province" value="JiangXi"/>
</bean>

这样每次通过getBean()获取的对象都是不同对象,可能出现浪费资源的现象!!

Bean自动装配

Spring会根据上下文,自动为Bean装配属性值。

以之前装配Studentaddress为例,原先是通过<property name="address" ref="address"/>注入,因为上下文是有已经装配好的address对象的,我们就可以让Spring自动去寻找并自动装配。

  • byName
1
2
3
<bean id="Student" class="com.theo.pojo.Student" autowire="byName">
......
</bean>

自动装配的原理是利用了Student类中的Setter方法,对于address属性有

1
2
3
public void setAddress(Address address) {
this.address = address;
}

那么Spring就会在Bean配置文件中寻找有没有id=”address“的对象,有则自动注入。但是如果id取名为了addressTan或其他,这种自动装配就不起作用了!

  • byType
1
2
3
<bean id="Student" class="com.theo.pojo.Student" autowire="byType">
......
</bean>

这种会在上下文查找Address类的对象,但是如果有多个同类对象,这种方法就不生效

通过自动装配后,就不需要写<property name="address" ref="address"/>了。

注解实现自动装配

  • 导入xml约束
1
2
3
4
xmlns:context="http://www.springframework.org/schema/context"		<!--导入beans头标签内-->

http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd <!--这两个加入到xsi:schemaLocation的值中-->
  • 配置注解的支持
1
<context:annotation-config/>
  • @Autowired

@Autowired注解要添加在需要装配的实体类属性上,或该属性的Setter方法上。

1
2
@Autowired
Address address;
1
2
3
4
@Autowired
public void setAddress(Address address) {
this.address = address;
}

注意@Autowired默认通过byType装配,如果有多个同类型的Bean存在,就无法装配。这时再添加注解**@Qualifier(value=”xxx”)**去帮忙指定配置掉一个id="xxx"的Bean,相当于byName的Autowired。

  • @Resource

@Resource注解无需配置Spring即可使用,它会先通过byType查询,如果找到多个同类型的Bean就会从中找出id=“属性名”的,如果也没找到就会报错。有点像byName与byType的聚合。

Spring注解开发

  1. 使用注解需要在applicationContext.xml导入context的xml约束,同上。
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
  1. 指定需要扫描的包,该包下的注解才能生效
1
<context:component-scan base-package="com.theo.pojo"/>

装配Bean

pojo下新建实体类User.class,设置属性public String name = "TZQ";。在类上添加注解@Component,之后类旁就会出现Spring Bean装配成功的绿叶图标。

注意自动装配给该Bean分配的id=“user”,在测试类通过context.getBean()填入类名的小写user!!!!

注入Bean

通过xml注入是通过<property>来注入的,注解只需要@Value()即可。

1
2
3
4
5
@Component
public class User {
@Value("TZQ") //相当于<property name="name" value="TZQ"/>
String name;
}

但你会发现,这种会把代码写死。如果需要更改值就需要更改代码,而不是更改配置文件了。除非非常简单的注入或者不涉及更改值的注解(@Autowired),否则不建议使用。

衍生注解

@Component有多个衍生注解,对于Web应用的MVC三层都有其独特的注解,但功能一样!!

  • dao层:【@Repository】
  • service层:【@Service】
  • controller层:【@Controller】

这三个注解都实现了将被注解的类装配到IoC容器,前提是在applicationContext.xml中加上扫描包路径!!

JavaConfig代替配置文件

使用JavaConfig可以完全删除applicationContext.xml,装配Bean的任务全权由JavaConfig负责

@Bean

新建com.theo.javaconfig.JavaConfig.class:

1
2
3
4
5
6
7
@Configuration
public class JavaConfig {
@Bean //相当于<bean id="GetUser" class="com.theo.pojo.User">
public User GetUser() {
return new User();
}
}

@Configuration表明该配置类会被Spring托管(也会将该类作为Bean装配到IoC容器中)。

@Bean说明会把下面的方法装配成Bean放到IoC容器中!!方法名就是id,返回值类型就是class。获取该Bean有些许不同:

1
2
3
4
5
6
@Test
public void JavaConfigTest() {
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
User GetUser = (User) context.getBean("GetUser");
System.out.println(GetUser.name);
}

所以说,@Bean装配了一个方法,但你从容器取出来的是一个Bean(返回类的对象),可以换一种角度来看,User没有装配到IoC容器中,而是GetUser自己实例化了User类再放到IoC容器中。这样想的话,User类就不用使用注解@Component了。

但是JavaConfig也能发现一些缺点:

  • User没有装配到IoC容器,这样就意味着Spring不会去管理User了
  • JavaConfig只负责装配,但并不负责注入属性!!当需要注入时还是需要到User属性上使用@Value()的。你也可以设置有参构造方法,new的时候添上属性值,但这个方法不推荐。

如果你觉得GetUser作为id不适应,你也可以通过@Bean("user")来改变id。

@ComponentScan(“com.theo.pojo”)+@Component

那如果不使用@Bean,想像按照xml方式一样将User装配到IoC容器中呢?

1
2
3
4
5
6
7
8
@Configuration
@ComponentScan("com.theo.pojo") //相当于在applicationContext.xml中添加扫描包路径了
public class JavaConfig {
// @Bean
// public User GetUser() {
// return new User("nihao");
// }
}

这样我们再重新在User上添加注释@Component,User就被装配到IoC容器中了。

1
2
3
4
5
6
@Test
public void JavaConfigTest() {
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
User user = (User) context.getBean("user");
System.out.println(user.name);
}

照样能打印出内容。

综上,使用JavaConfig装配Bean有以下两种方式:

  • 只使用@Bean,通过getBean()来实例化User
  • 使用@ComponentScan(“com.theo.pojo”)+@Component,这样就直接将User装配到IoC

以上两种方式的效果都一样,也可以一起用,只是User会被实例化两次,但留在IoC的只是同一个对象,因为默认使用单例模式,故而getBean(“user”)和getBean(“GetUser”)获取的对象都是同一个,打印的hashcode()都一样。

代理

动态代理

实现动态代理的步骤

  • 创建接口,定义目标类要完成的功能
  • 创建目标类以实现接口
  • 创建InvocationHandler接口的实现类,在invoke方法中完成代理类要做的功能:1.调用目标方法 2.添加功能
  • 使用Proxy类的静态方法,创建代理对象,并把返回值强转为接口类型

场景呈现

租客租房

Client租房的时候如果不能直接与Host联系,就需要找到中介。首先,中介必须要提供Host提供的服务如:出租,允许租客看房等等。那么Host就要先通过一个接口来具体实现这些服务,中介就利用反射机制来调用这些服务(method.invoke),但中介也可以自己收取些小费,最后一同将服务提供给租客。

因而这一整套租房流程只按照实线流程进行,中途Host从没有参与。之后Host如要增添服务,只需要在功能接口增添并在目标类具体实现即可,那么代理就会自动拥有这些服务。

代码实现流程

源码下载: Spring-proxy.rar

  • 定义功能接口

首先Host只允许租房和看房的服务,在com.theo.Service下创建Rent接口:

1
2
3
4
public interface Rent {
public void rent();
public void check(int date);
}
  • 目标类实现接口

实例化一个Host,因为不同房主的租房规则不一样,我们就需要具体实现这些接口,在com.theo.Factory创建Host:

1
2
3
4
5
6
7
8
9
10
11
public class Host implements Rent {
@Override
public void rent() {
System.out.println("执行了目标类Host:房东要租房");
}

@Override
public void check(int date) {
System.out.println("执行了目标类Host:房东允许你在"+date+"看房");
}
}
  • 定义代理要提供的服务

InvocationHandler接口的实现类就是中介要实现的服务,附加收费和提供目标类服务都在invoke中实现。在com.theo.Handler下创建RentHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RentHandler implements InvocationHandler {
private Object target = null;

public RentHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args); //执行目标类服务

System.out.println("中介收取费用$20"); //代理的额外服务
return null;
}
}

因为执行目标服务是通过反射机制实现的,这样我们就需要获取目标类的Class,再利用Class的method域来调用目标类的具体服务。

通过反射的知识,我们知道invoke(target,args)是要传入具体对象(Host)的,考虑到具体情况会有多个Host,我们就不在这一层指定好Host,而是通过有参构造方法让真正的的代理传入需要代理的具体对象(Host1/Host2…),这样应用面更广。

  • 创建代理对象

之前的RentHandler并不是真正的代理,只是定义了一个代理需要做的事,你可以看作是中介公司,在这里的中介都要遵从公司定义的规则:1.提供Host的服务 2.收取中介费用$20

接下来我们创建一个实实在在的中介,在com.theo.Proxy下创建agency:

1
2
3
4
5
6
7
8
9
10
public class agency {
public static void main(String[] args) {
Rent factory = new Host();
InvocationHandler handler = new RentHandler(factory);
Rent proxyInstance = (Rent) Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), handler);

proxyInstance.rent();
proxyInstance.check(20210815);
}
}

首先明确我们要代理的对象是Host,将其创建出来:Rent factory = new Host();。其后传入代理对象到中介公司,让中介公司知道Host提供的服务有哪些:InvocationHandler handler = new RentHandler(factory);。之后通过这个中介公司创建出一个中介:Rent proxyInstance = (Rent) Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), handler);,注意这里的三个参数都是固定的,如果要换一个目标类,只需要改成new Host1()。最后代理就能直接调用服务了。

代理调用服务:proxyInstance你就可以直接想成是Host的对象,就可以直接调用Host实现的接口。

代码底层实现的原理:实际上proxyInstance.check(20210815)会去调用RentHandler的invoke方法:check就是参数method;20210815就是参数args,再利用反射机制,method.invoke(target, args)就是new Host().check(20210815)

目标服务执行完后就是中介方自己添加的服务了。

  • 执行agency

运行agency.class得到结果:

1
2
3
4
执行了目标类Host:房东要租房
中介收取费用$20
执行了目标类Host:房东允许你在20210815看房
中介收取费用$20

AOP

源码下载:Spring-AOP.rar

面向切面编程

AOP

通常情况下我们都是纵向开发,就像之前的租客与房主的例子,写完房主的相关逻辑代码后,租客就可以直接调用了,如果房主的某一服务需要做细微的调改,比如租房的金额增加,那么房主的相关代码就要做更改。这相当于更改了底层代码。如果中间加了个中介,加钱的事由中介负责,那么房主的代码就无需更改。

这就是面向切面编程的雏形:不更改业务逻辑的情况下,细微调整服务逻辑。

不知道有没有注意到动态代理的一个问题:通过代理调用目标类哪种方法,代理额外做的工作都相同,即收取$20费用。如果代理租房业务收取30,而代理看房业务只收20呢?这就是AOP要解决的问题了。

自定义类实现AOP

现在pom.xml中导入依赖:

1
2
3
4
5
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>

为实现以上的不同收费方式,在com.theo.Advice下新建fares:

1
2
3
4
5
6
7
8
public class fares implements Advice {
public void rent_fare() {
System.out.println("中介收取租房费用$30");
}
public void check_fare() {
System.out.println("中介收取查房费用$10");
}
}

以查房服务为例,当调用目标类的方法check()时,我们需要切入一个方法check_fare()。因此,在check()中制造切点(applicationContext.xml中配置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<bean id="host" class="com.theo.Factory.Host"/>
<bean id="fares" class="com.theo.Advise.fares"/>

<aop:config>
<aop:pointcut id="check_pointcut" expression="execution(* com.theo.Factory.Host.check(int))"/>
<aop:pointcut id="rent_pointcut" expression="execution(* com.theo.Factory.Host.rent(..))"/>

<aop:aspect ref="fares">
<aop:before method="check_fare" pointcut-ref="check_pointcut"/>
<aop:before method="rent_fare" pointcut-ref="rent_pointcut"/>
</aop:aspect>
</aop:config>
</beans>

先导入xml约束xmlns:aop="http://www.springframework.org/schema/aop"以及xsi:schemaLocation

切点

<aop:pointcut id="check_pointcut" expression="execution(* com.theo.Factory.Host.check(int))"/>

这就是一个切点,表明将会在这个点插入方法,说白了切点就是一个方法。

id="check_pointcut"指明切入点的名称,expression="execution()"指示切入点的具体位置

execution表达式

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。

在上面的例子中,第一个*表示匹配所有返回类型,第二部分com.theo.Factory.Host.check表明切点在这个方法中,以后执行该方法时就会附加上切面方法。第三部分check(int)精准匹配到参数为int类型的方法,这可以用来区分重载方法;如果想囊括所有重载方法,int就可以用..替代。

通配符 *****

如果我想在目标类中所有方法中加入相同切点,就需要使用com.theo.Factory.Host.*(..)。同样的,使用com.theo.Factory.*.*(..)就是在所有目标类的所有方法中加入同一切点。

com.theo.Factory.*.*com.theo.Factory..*同一作用,表示包、子孙包下的所有类

切面与通知

如上,com.theo.Advise.fares就是一个切面,面上有多个方法rent_fare()check_fare(),每一方法称为通知

标记切面

先将切面实例化并交予IoC容器,再标记为切面:

1
<bean id="fares" class="com.theo.Advise.fares"/>
1
2
3
<aop:aspect ref="fares">
//通知 Advice
</aop:aspect>

在切点上接入通知

1
2
<aop:before method="check_fare" pointcut-ref="check_pointcut"/>
<aop:before method="rent_fare" pointcut-ref="rent_pointcut"/>

之前创建了check_pointcutrent_pointcut两个切点,分别接入check_farerent_fare两个通知,这样执行Host.check(int date)会附加执行fares.check_pointcut()Host.rent()同理。

<aop:before 是指在执行完目标类方法前再执行通知。<aop:after 是指在执行完目标类方法后再执行通知

运行

编写测试类,以getBean("host")获取Host对象,执行host.check(20210815)执行结果:

中介收取查房费用$10
执行了目标类Host:房东允许你在20210815看房

Spring API实现AOP

Spring API常见切面有日志和验证。创建com.theo.Advice.log

1
2
3
4
5
6
7
8
9
10
11
public class log implements MethodBeforeAdvice {
/**
* @param method 反射的目标类方法
* @param objects 参数,也就是args
* @param o 目标对象
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前日志:执行了方法"+method.getName()+";目标类:"+o.getClass());
}
}

这里选择前日志,它会在目标类方法执行前打印日志。我们打算对所有方法都加入日志:

1
<bean id="log" class="com.theo.Advice.log"/>
1
2
3
<aop:pointcut id="all_pointcut" expression="execution(* com.theo.Factory.Host.*(..))"/>

<aop:advisor advice-ref="log" pointcut-ref="all_pointcut"/>

注解实现AOP

注解实现是基于自定义类实现改进的。也需要经历标记切面、创建切点、接入通知

创建com.theo.Advice.Annotation:

1
2
3
4
5
6
7
8
9
10
11
@Aspect
public class Annotation {
@Before("execution(* com.theo.Factory.Host.*(..))")
public void before_annotation() {
System.out.println("===目标类方法执行开始====");
}
@After("execution(* com.theo.Factory.Host.*(..))")
public void after_annotation() {
System.out.println("===目标类方法执行完成===");
}
}

@Aspect表明该类为一切面,等价于:<aop:aspect ref="fares">

@Before("execution(* com.theo.Factory.Host.*(..))")创建切点,同时声明执行先后。等价于:<aop:before method="check_fare" pointcut-ref="check_pointcut"/>

最后需要在applicationContext.xml开启注解支持和Bean装配。

1
2
<bean id="annotation" class="com.theo.Advice.Annotation"/>
<aop:aspectj-autoproxy/>

至此,注解实现完成。

Mybatis-Spring


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!