Spring注解开发

配置的三种写法:XML-based configuration 基于 XML 文件,Annotation-based configuration 基于 Annotation ,以及 Java-based configuration 基于 Java 代码配置。

DI 注解(Autowired)

需要配置 DI 注解解析器,Spring3.0之前,需要手动配置 Autowired 注解的解析程序,Web开发中必须配置。<context:annotation-config/>

Autowired 注解和 Resource 注解


Autowired 注解和 Resource 注解都可以完成相同功能的操作,Autowired 是 Spring 提高,Resource 是 JavaEE 提供。(spring-context 包下)

相同点:

  1. 自动的把属性需要的对象找出来,自动注入
  2. 贴在字段或者 setter 方法上面,一般贴在字段上,贴字段上不需要使用set方法。

可以注入一些 Spring 内置的重要对象,甚至是 Servlet 的API,比如:BeanFactory、ApplicationContext、ServletContext等

不同点:

Autowired 和 Resource 注解都必须要能找到对应的对象,否则报错。Autowired 注解可以通过 required=false 来避免这个问题(required=false)

Autowired 按照类型找,找不到按名字找,可以配合 @qualifier限定 注解使用,表示直接查找这个名字的对象

1
2
3
4
5
public class Somebean {
@Autowired
@Qualifier("otherbean")
private Otherbean other;
}

Reource 按名字找,找不到按类型找。

1
2
3
4
public class Somebean {
@Resource(name = "otherbean")
private Otherbean other;
}

原来的方式:

1
2
3
4
5
<context:annotation-config/>
<bean id="otherbean" class="cn.lizhaoloveit.annotation.Otherbean"/>
<bean id="somebean" class="cn.lizhaoloveit.annotation.Somebean">
<property name="other" ref="otherbean"></property>
</bean>

现在的方式:

1
2
<bean id="otherbean" class="cn.lizhaoloveit.annotation.Otherbean"/>
<bean id="somebean" class="cn.lizhaoloveit.annotation.Somebean"/>

需求

把 OtherBean 对象设置给 SomeBean 对象的 other 属性。

Value注解


Autoweired 和 Resource 注解用于注入对象,Value注解用于注入常量数据。

server.properties 文件

1
2
server.port=8888
server.path=/

Java 代码:

1
2
@Value("${server.port}")
private int port;

引入配置文件

1
<context:property-placeholder location="classpath:db.properties,classpath:server.properties"/>

或者

1
2
<context:property-placeholder location="classpath:db.properties" ignore-unresolvable="true"/>
<context:property-placeholder location="classpath:server.properties" ignore-unresolvable="true"/>

IoC 注解(bean)


在xml文件配置了<context:component-scan>标签后,spring容器可以自动去扫描base-pack所指定的包或其子包下面的java类文件,如果扫描到有@Component、@Controller、@Service 、@Repository等注解修饰的Java类,则将这些类注册为spring容器中的bean。

IoC 的注解解释器配置

1
2
3
4
<!-- DI 注解解释器 -->
<context:annotation-config/>
<!-- IoC注解解释器 去哪些包中及其子包中去扫描组件注解 -->
<context:component-scan base-package="cn.lizhaoloveit.ioc"/>
  • @Repository 用于标注数据访问组件,即 DAO 组件。
  • @Service 用于标注业务层组件,即 Service 组件
  • @Controller 只用于标注控制层组件(Spring MVC 的 Controller)
  • @Component 泛指组件,当组件不好归类的时候,可以使用跟这个注解。

默认 id 是把被标注类名的首字母小写后的类名。

需要配置 IoC 注解的解析器:
context:component-scan base-package=""表示去哪个包中及其子包中扫描组件注解

代码:

1
2
3
4
5
6
7
8
9
10
11
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class App
{
@Autowired
private Somebean somebean;
@Test
public void testName() throws Exception {
System.out.println(somebean);
}
}
1
2
3
4
5
6
7
8
9
10
11
@ToString
@Component
public class Somebean {
@Autowired
@Qualifier("other")
private Otherbean other;
}

@Component("other")
public class Otherbean {
}

最后输出:Somebean(other=cn.lizhaoloveit.ioc.Otherbean@1d76aeea)
原理如下:

在类型上贴注解 @Componenet("other") 相当在配置文件中加入如下配置
<bean id="other" class="全限定名"

在字段上贴注解,@Autowired,默认会先找 Otherbean 类型的值注入,如果找不到会找 otherbean id 名字的值注入,如果都没有会报错。如果加入注解 @Qulifier("other"),表示会加载指定 id为 other 的bean 对象注入。

作用域注解(scope)

1
2
3
4
@Component("other")
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class Otherbean {
}

相当于:<bean id="other" class="...Otherbean" scope="singleton"/>

初始化和销毁注解

1
2
3
4
5
6
7
8
9
10
11
12
@Component("other")
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class Otherbean {
@PostConstruct
public void open() {
System.out.println("初始化方法");
}
@PreDestroy
public void close() {
System.out.println("关闭");
}
}
1
2
3
<bean id="other" class="...Otherbean" scope="singleton"
init-method="open" destro-method="close"
/>

AOP注解


AOP的注解解释器:

1
2
3
4
5
6
<!-- DI 注解解释器 -->
<context:annotation-config/>
<!-- IoC注解解释器 去哪些包中及其子包中去扫描组件注解 -->
<context:component-scan base-package="cn.lizhaoloveit.ioc"/>
<!-- AOP 注解解释器 -->
<aop:aspectj-autoproxy/>

以前基于 xml 的做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<import resource="classpath:applicationContext.xml" />

<!-- DI 注解解释器 -->
<context:annotation-config/>
<!-- IoC注解解释器 去哪些包中及其子包中去扫描组件注解 -->
<context:component-scan base-package="cn.lizhaoloveit.aopjdkproxy"/>
<!-- AOP 注解解释器 -->
<aop:aspectj-autoproxy proxy-target-class="false"/>

<!-- 创建增强模块类对象 -->
<bean id="txManager" class="cn.lizhaoloveit.aopjdkproxy.TransactionManager"/>

<aop:config proxy-target-class="false">
<aop:aspect ref="transactionManager">
<aop:pointcut expression="execution(* cn.lizhaoloveit.aop.service.*Service.*(..))"
id="txPointcut"/>
<aop:before method="begin" pointcut-ref="txPointcut"/>
<aop:after-returning method="commit" pointcut-ref="txPointcut"/>
<aop:after-throwing method="rollback" pointcut-ref="txPointcut" throwing="exception"/>
<aop:after method="close" pointcut-ref="txPointcut"/>
<aop:around method="around" pointcut-ref="txPointcut"/>
</aop:aspect>
</aop:config>

<aop:aspect> 标签的作用是关联项目中的增强模块,这时只需要在增强类上贴上注解即可。@Aspect 默认是按类型查找。

1
2
3
@Aspect
@Component
public class TransactionManager

<aop:pointcut expression="execution(* cn.lizhaoloveit.aop.service.*Service.*(..))" id="txPointcut"/> 标签的作用是找到业务流的切入点,我们可以直接在增强模块类(TransactionManager)中使用注解来表示

1
2
3
4
@Pointcut("execution(* cn.lizhaoloveit.aop.service.*Service.*(..))")
public void txPointcut() {

}

上面的代码表示的含义是,拦截 execution 中的所有方法调用,并且给该 execution 设置一个 id值为 方法名 txPointcut。

最后只要规定切入点的位置和增强方法即可:

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
@Aspect
@Component
public class TransactionManager {
@Pointcut("execution(* cn.lizhaoloveit.aop.service.*Service.*(..))")
public void txPointcut() {
}
@Before("txPointcut()")
public void begin(JoinPoint jp) {
System.out.println("事务开始...");
}
@AfterReturning("txPointcut()")
public void commit(JoinPoint jp) {
System.out.println("当前连接点签名 :" + jp.getSignature());
System.out.println("提交事务...");
}
@AfterThrowing("txPointcut()")
public void rollback(JoinPoint jp, Throwable exception) {
System.out.println("当前连接点签名 :" + jp.getSignature());
System.out.println("回滚..." + exception.getMessage());
}
@After("txPointcut()")
public void close() {
System.out.println("关闭资源...");
}
@Around("txPointcut()")
public Object around(ProceedingJoinPoint pjp) {
Object res = null;
begin(pjp);
try {
System.out.println("执行目标方法");
// 执行目标方法
res = pjp.proceed();
commit(pjp);
} catch (Throwable e) {
e.printStackTrace();
rollback(pjp, e);
} finally {
close();
}
return res;
}
}

ProxyApp-context.xml

1
2
3
4
<import resource="classpath:applicationContext.xml" />

<context:component-scan base-package="cn.lizhaoloveit.aopjdkproxy"/>
<aop:aspectj-autoproxy proxy-target-class="false"/>

总结:xml 配置文件,通过 IoC 注解解析器告诉 Spring 容器去扫描 base-package 包下的 java 类文件,<aop:aspctj-autoproxy> 配置 aop 注解解释器,扫描时,发现了 TransactionManager,带有 @Aspect,并且扫描到 @Component,就会创建 Transaction对象,并且将其作为切面模块,插入哪个业务流程呢?由 @Pointcut 告诉动态代理对象去拦截哪些方法,去在合适的时机执行增强方法。

Tx注解


一般用于 list、get、query 开头的 Service 方法使用 事务的 read-only 模式。

Transactional 注解

具体使用:

1
2
3
4
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="txManager" />

Transactional 贴在业务类上,也可以贴在业务类方法之上:

  • 贴在类上:表示该类中所有的方法都使用 Transactional 注解的属性配置
  • 贴在方法上:只针对被贴的这一个方法,一般用于做单独配置

一般的,我们可以在业务类上直接贴该注解,并在查询方法上设置 readOnly 属性为 true,一定要开启 Tx 注解的解释器

配置 JDBC 事务管理器,使用 CGLIB

1
2
3
4
5
6
7
8
9
10
11
12
13
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
<property name="dataSource" ref="dataSource"/>
</bean>

<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
1
2
3
4
5
6
7
8
9
10
11
@Service
@Transactional
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDAO dao;
public void trans(Long outId, Long inId, int money) {
dao.transOut(outId, money);
int a = 1 / 0;
dao.transIn(inId, money);
}
}

SpringMVC 注解(Controller、RequestMapping)


一个简单的基于注解开发的 SpringMVC,

  • 需要在 web.xml 中设置一个调度器 dispatcherServlet。

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<servlet>
<!-- 名字可以任意写 -->
<servlet-name>SpringMVC</servlet-name>
<!-- 真实类型 -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<!--
初始化参数:加载本地 mvc 配置,告诉调度器
1. 处理器的映射器是哪个,作用:将 XxxController 和 请求url 通过 id 或者 name 关联
2. 处理器的适配器是哪个,作用:告诉调度器调用 XxxController 的哪个方法
3. 视图解析器是谁,作用:告诉调度器使用哪个解析器解析视图。
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc2.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
  • 配置mvc2.xml
1
2
3
4
<!-- IoC 注解解释器 -->
<context:component-scan base-package="cn.lizhaoloveit.hello"/>
<!-- mvc 注解解释器 -->
<mvc:annotation-driven/>

IoC 注解解释器,会扫描其下面的注解标签,遇到 @Component、@Controller、@Service 、@Repository,会生成对应的 <bean> 实例对象,

  • 处理器,UserController
1
2
3
4
5
6
7
8
9
10
11
12
@Controller
public class UserController {

@RequestMapping("/abc")
public ModelAndView sayHello() {
System.out.println("...hello...");
ModelAndView mv = new ModelAndView();
mv.addObject("username", "will");
mv.setViewName("WEB-INF/view/hello/hello.jsp");
return mv;
}
}

@RequestMapping(“/abc”),与url做映射,处理器中可以写多个。

问题,理论上讲,mvc.xml 需要配置4个东西

  1. 处理器映射器 BeanNameUrlHandlerMapping
  2. 处理器适配器 SimpleControllerHandlerAdapter
  3. 视图解析器 InternalResourceViewResolver
  4. 处理器 handler
    为什么使用注解后,我们就创建了 Controller,就可以使用了呢?

因为在 spring-webmvc.jar 包中,有一个 properties 文件,
/org/springframework/web/servlet/DispatcherServlet.properties

2019-08-05 at 6.59 P

Mybatis-Spring 注解


创建 Mapper 代理对象,以前的做法:

1
2
3
4
5
6
<bean id="userMapper"
class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="sqlSessionFactory" ref="mySqlSession" />
<property name="mapperInterface"
value="cn.lizhaoloveit.ssm.mapper.UserMapper" />
</bean>

使用注解后的做法:

1
2
3
4
<!-- 扫描指定路径下的的包,创建 Mapper 动态代理对象  -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.lizhaoloveit.ssm.mapper"></property>
</bean>

并且可以直接把 SQL 卸载 Mapper 接口对应的方法上,不再使用 Mapper 映射文件。

UserMapper.java

1
2
@Select("select id, name, age from t_user")
List<User> selectAll();

总结:


IoC 注解

作用:被贴的类,交给 Spring 管理(会被创建对象,存储在 Spring 容器中)
注解:

  • Repository(DAO)
  • Service(一般贴 Service 类)
  • Controller(只能贴 Controller 类)
  • Component(非上述所有类,默认 id 为类名首字母小写)

XML:

1
2
<bean id="someBean" class="cn.lizhaoloveit.SomeBean" scope="singleton" 
init-method="open" destroy-method="close">

解析器:<context:component-scan basePackage="cn.lizhaoloveit.ssm">
使用注解操作如下:

1
2
3
4
5
6
7
8
9
10
@Component("someBean")
@Scope("singleton")
public class SomeBean {
@PostConstruct
public void open() {
}
@PreDestroy
public void close() {
}
}

DI 注解

作用:从容器中找到指定的 bean 对象,并设置给当前被贴标签的字段

注解:Autoweired、Resource 功能一样

XML:

1
2
3
4
<bean id="otherBean" class="...OtherBean"/>
<bean id="someBean" class="...SomeBean">
<property name="other" ref="otherBean"/>
</bean>

使用注解操作如下:
解析器:<context:annotation-config/>

1
2
3
4
5
6
7
8
9
@Component
public class OtherBean {

}
@Component
public class SomeBean {
@Autowired
private OtherBean other;
}

Tx注解

作用:让 Service 组件实现数据库事务管理操作。
注解:@Transactional
创建事务功能类对象

1
2
3
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
<property name="dataSource" ref="dataSource"/>
</bean>

XML 配置

1
2
3
4
5
6
7
8
9
10
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<!-- proxy-target-class属性的意义:是否基于接口创建动态代理(是否是JDK创建动态代理实例) -->
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>

使用注解操作如下:
解析器:<tx:annotation-driven transaction-manager="txManager"/>

在 Service 实现类上,使用 Transactional 注解

MVC注解

XML

1
2
3
4
5
6
7
<!-- 
1. 配置前端控制器 web.xml
2. 配置处理器映射器(处理器的id、name=url) mvc.xml
3. 配置处理器适配器(处理器中的什么方法) mvc.xml
4. 配置视图解析器
5. 创建处理器对象,并实现 Controller 接口。
-->

@Controller:声明当前类为控制器,让 Spring 容器创建对象
@RequestMapping: 贴在类和方法上,表示访问当前方法的 url (“/类url + /方法url”)

1
2
3
4
5
6
7
8
@Controller
@RequestMapping("/xxx")
public class HelloController {
@RequestMapping("/abc")
public ModelAndView sayHello() {
return null;
}
}

此时访问:localhost/xxx/abc

AOP注解

1
2
3
4
5
public class LoginAdvice {
public void writeLog() {
syso("记录日志")
}
}

XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- execution(* cn.lizhaoloveit.aop.service.*Service.*(..)) -->
<!-- 1. what -->
<bean id="transactionManager" class="cn.lizhaoloveit.aopjdkproxy.TransactionManager"/>

<!-- 2. where 哪些包 哪些类 哪些方法 -->
<!-- 3. when -->
<!-- Aop 配置 proxy-target-class 默认是 false 使用 JDK动态代理,改为 true 使用CGlib动态代理 -->
<aop:config proxy-target-class="false">
<!-- 切面:切面内部有 where 和 when 需要和 what 联系起来
意思就是 在 Service 类中的所有方法调用前插入事务管理类的 begin 方法 -->
<aop:aspect ref="transactionManager">
<!-- where -->
<aop:pointcut expression="execution(* cn.lizhaoloveit.aop.service.*Service.*(..))" id="txPointcut"/>
<aop:before method="begin" pointcut-ref="txPointcut"/>
<aop:after-returning method="commit" pointcut-ref="txPointcut"/>
<aop:after-throwing method="rollback" pointcut-ref="txPointcut" throwing="exception"/>
</aop:aspect>
</aop:config>

注解:

  • Aspect 贴在增强类之上,表示一个切面 what
  • Pointcut 编写切入点表达式 where
  • Before 切入时机 when

解析器:
<context:component-scan basePackage="..."> 解析 Component
<aop:aspectj-autoproxy>

1
2
3
4
5
6
7
8
9
10
11
@Component
@Aspect
public class LoginAdvice {
@Pointcut("execution(* cn.lizhaoloveit.ssm.*Service.*(..))")
public void Xxx() {
}
@Before("Xxx()")
public void writeLog() {
syso("日志记录");
}
}
文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2019/08/04/Spring%E6%B3%A8%E8%A7%A3%E5%BC%80%E5%8F%91/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论