http://blog.csdn.net/yerenyuan_pku/article/details/69663779
Spring的bean管理(注解方式)
注解:代码中的特殊标记,注解可以使用在类、方法、属性上面,使用注解可实现一些基本的功能。注解的写法是@注解名称(属性=属性值)
。
使用注解创建对象
第一步,创建Web项目,引入Spring的开发包
除了导入Spring基本的Jar包外(可参考《Spring的概述》一文),还须导入Spring注解的Jar包,如下:
第二步,编写相关的类
在src目录下创建一个cn.itcast.anno包,并在该包下编写一个User类。
public class User {
public void add() { System.out.println("add...................."); } }
第三步,创建Spring配置文件
-
在Spring配置文件中引入约束,如下:
<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 http://www.springframework.org/schema/context/spring-context.xsd">
- 1
-
在Spring配置文件中做些事情,即开启注解扫描。
<!-- 开启注解的扫描。到配置的包里面扫描类、方法、属性上面是否有注解 --> <context:component-scan base-package="cn.itcast"></context:component-scan>
- 1
注意:也可以这样开启注解扫描,如下:
<context:annotation-config></context:annotation-config>
但是这种开启注解扫描的方式,只会扫描属性上面的注解。实际开发中用到的并不多!故不推荐使用。
第四步,在创建对象所在的类上面使用注解实现
@Component(value="user") // 类似于<bean id="user" class="..." />
public class User { public void add() { System.out.println("add...................."); } }
- 1
如若注解里面属性名称是value,则可以省略,所以上面的User类亦可这样写为:
@Component("user") // 类似于<bean id="user" class="..." />
public class User {
public void add() { System.out.println("add...................."); } }
- 1
第五步,编写测试类
在cn.itcast.anno包下编写一个TestDemo单元测试类。
public class TestDemo {
@Test
public void testUser() { ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); User user = (User) context.getBean("user"); System.out.println(user); user.add(); } }
- 1
测试即可。
Spring的bean管理中常用的注解
@Component(作用在类上)
创建对象的注解有四个:
@Component
@Repository
:用于对DAO实现类进行标注(持久层)。@Service
:用于对Service实现类进行标注(业务层)。@Controller
:用于对Controller实现类进行标注(WEB层)。
后三个注解是Spring中提供的@Component
的三个衍生注解(功能目前来讲是一样的),它们是为了让标注类本身的用途更清晰,Spring在后续的版本中会对其进行增强。
bean的作用范围的注解
@Scope
- singleton:单例,默认值
- prototype:多例
所以我们可通过注解设置创建对象是单例或者还是多实例的。这样User类的代码亦可写为:
@Service("user")
@Scope("singleton")
public class User {
public void add() { System.out.println("add...................."); } }
- 1
使用注解注入属性(对象)
这儿,我举个例子来说明如何使用注解注入(对象类型的)属性。
先创建业务层中的UserService类:
public class UserService {
public void add() { System.out.println("service..........."); } }
- 1
再创建持久层中的UserDao类:
public class UserDao {
public void add() { System.out.println("dao................"); } }
- 1
- 2
以上两个类都创建在cn.itcast.anno包中。我们要实现的目的是在UserService类里面调用UserDao类的方法,这样我们就要在UserService类里面得到UserDao类的对象。之前是采用xml配置文件的方式来注入属性的,本文将使用注解的方式完成注入属性的操作。
-
在UserService类里面定义UserDao类型属性
private UserDao userDao;
- 1
- 1
在UserService类里面定义UserDao类型的属性,由于是使用注解的方式,故不需要手动生成set方法。
-
进行注解方式实现属性注入
-
创建UserDao类的对象和UserService类的对象
@Service("userService") public class UserService { private UserDao userDao; public void add() { System.out.println("service..........."); userDao.add(); } }
- 1
@Repository("userDao") public class UserDao { public void add() { System.out.println("dao................"); } }
- 1
-
在UserService类里面注入UserDao类的对象,使用注解来实现。首先我使用
@Autowired
注解来实现。@Service("userService") public class UserService { @Autowired private UserDao userDao; public void add() { System.out.println("service..........."); userDao.add(); } }
- 1
注意:使用注解
@Autowired
,它不是根据名字去找Dao,而是默认按类型进行装配。
当然了,也可使用@Resource
注解来实现,如下:@Service("userService") public class UserService { @Resource(name="userDao") private UserDao userDao; public void add() { System.out.println("service..........."); userDao.add(); } }
- 1
注意,使用
@Resource
注解,它默认是按名称进行注入的。在实际开发中,我们也是使用@Resource
注解来注入属性的,注解@Autowired
用到的并不多。
-
AOP的概述
什么是AOP
Spring是用来解决实际开发中的一些问题的,AOP解决了OOP中遇到的一些问题,是OOP的延续和扩展。我们可从以下三个方面来理解AOP:
- 扩展功能不是通过修改源代码而实现的。
可通过Struts2框架中的拦截器来理解。 -
AOP采用横向抽取机制实现。
要理解横向抽取机制,就必须认识纵向抽取机制。例如有如下的一个类:public class User { public void add() { 添加的逻辑... } }
- 1
现在我们在add()方法中要扩展一个功能,即日志添加功能,添加完该功能之后,可记录在什么时候添加了哪个用户。想到的最原始的方法就是直接修改源代码。
public class User { public void add() { 添加的逻辑... 直接写添加日志记录的代码以实现... } }
- 1
很显然这是一种愚蠢的做法,并且这儿还有一个原则——修改功能一般不是直接修改源代码来实现的。顺其自然地就要来讲纵向抽取机制了,这时我们可编写一个BaseUser类。
public class BaseUser { public void wirtelog() { 记录日志的逻辑... } }
- 1
- 2
接下来让User类继承BaseUser类,如下:
public class User extends BaseUser { public void add() { 添加的逻辑... // 记录日志 super.wirtelog(); } }
- 1
这样是不是就万事大吉了呢?你懂的!因为当父类的方法名称变化时,子类调用的方法也必然要进行修改。最后终于要讲横向抽取机制了,横向抽取机制分为两种情况,下面分别加以简单阐述。
有接口情况的横向抽取机制:
例如有一个如下接口:public interface UserDao { public void add(); }
- 1
接口的一个实现类如下:
public class UserDaoImpl implements UserDao { public void add() { ... } }
我们现在即可使用动态代理技术增强类里面的方法,即创建接口的实现类代理对象,并增强UserDaoImpl类里面的add()方法。
无接口情况的横向抽取机制:
例如有一个如下User类:public class User { public void add() { ... } }
我们也可使用动态代理技术增强类里面的方法,即创建被增强方法所在类的子类代理对象,并增强User类里面的add()方法。
- AOP底层使用动态代理实现
- 有接口情况:创建接口实现类代理对象
- 没有接口情况:创建增强方法所在类的子类代理对象
为什么学习AOP
在不修改源代码的情况下,即可对程序进行增强。AOP可以进行权限校验、日志记录、性能监控和事务控制。
Spring的AOP的由来
AOP最早是由AOP联盟的组织提出的,他们制定了一套规范。Spring将AOP思想引入到框架中,且必须遵守AOP联盟的规范。
Spring的AOP的底层实现
Spring的AOP的底层用到了两种代理机制:
- JDK的动态代理:针对实现了接口的类产生代理。
- Cglib的动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强的技术,生成当前类的子类对象。
AOP开发中的相关术语
以下是比较专业的术语。
- Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。
- Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
- Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知分为前置通知、后置通知、异常通知、最终通知和环绕通知(切面要完成的功能)。
- Aspect(切面):是切入点和通知的结合。
- Target(目标对象):代理的目标对象(要增强的类)
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
- Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field。
例如有如下一个类:
public class User {
public void add() { } public void update() { } public void delete() { } }
- 1
下面是用比较通俗易懂的话来阐述AOP开发中的常见的相关术语:
- 连接点:在User类里面有3个方法,这3个方法都可以被增强,类里面的哪些方法可以被增强,这些方法就可被成为连接点。
- 切入点:在一个类中可以有很多的方法被增强,在实际操作中,如若只增强了类里面的add方法,则实际增强的方法被称为切入点。
-
增强/通知:比如增强User类里面的add方法,在add方法中添加了日志功能,这个日志功能就称为增强。
通知类型:- 前置通知:在增强的方法执行之前进行操作。
- 后置通知:在增强的方法执行之后进行操作。
- 环绕通知:在增强的方法执行之前和执行之后进行操作。
- 最终通知:增强了两个方法,执行第一个方法,执行第二个方法,在第二个方法执行之后进行操作。
也可理解为后置通知后面执行的通知或者无论目标方法是否出现异常,最终通知都会执行。 - 异常通知:程序出现异常之后执行的通知。
- 切面:把增强应用到切入点的过程。即把具体增强的逻辑用到具体的方法上面的过程。
- 目标对象:增强的方法所在的类,即要增强的类。
- Weaving(织入):是指把增强应用到目标对象的过程。即把把advice应用到target的过程。
Spring的基于AspectJ的AOP开发
@AspectJ的简介
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。AspectJ是一个基于Java语言的AOP框架。Spring2.0以后新增了对AspectJ切点表达式的支持。@AspectJ是AspectJ1.5新增的功能,通过JDK5注解技术,允许直接在Bean类中定义切面。新版本Spring框架,建议使用AspectJ方式来开发AOP,使用AspectJ需要导入Spring AOP和AspectJ相关的Jar包。
从上面的阐述中,我们应认识到AspectJ并不是Spring框架的一部分,而是一个单独的面向切面的框架,只不过它经常和Spring框架一起使用进行AOP的操作而已。
使用AspectJ方式来开发AOP共有两种方式:
- 基于AspectJ的xml配置文件的方式
- 基于AspectJ的注解的方式
只不过本文讲解的是基于AspectJ的xml配置文件的方式,下文再讲第二种方式。
Spring使用AspectJ进行AOP的开发:XML的方式
第一步,引入相应的Jar包
上面我说过,除了导入最基本的Jar包外,使用AspectJ还需要导入Spring AOP和AspectJ相关的Jar包。
- Spring的传统AOP的开发的包:
- spring-aop-4.2.4.RELEASE.jar
- aopalliance-1.0.jar
- AspectJ的开发包
- aspectjweaver-1.8.7.jar
- spring-aspects-4.2.4.RELEASE.jar
第二步,编写目标类
在src目录下创建一个cn.itcast.aop包,并在该包下编写一个目标类。
public class Book {
public void add() { System.out.println("book add................."); } }
- 1
第三步,创建增强的类以及增强的方法
public class MyBook {
// 前置通知
public void before1() { System.out.println("before........"); } }
- 1
- 2
我们现在要求在Book类里面的add方法之前执行MyBook类里面的before1的方法。
第四步,在Spring配置文件中进行配置
-
在Spring配置文件中引入aop约束
<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">
- 1
-
配置两个类对象
<!-- book对象 --> <bean id="book" class="cn.itcast.aop.Book"></bean> <bean id="myBook" class="cn.itcast.aop.MyBook"></bean>
-
配置AOP操作,即需要配置切入点和切面。
<!-- 配置AOP的操作 --> <aop:config> <!-- 配置切入点,对Book类里面的所有方法都增强 --> <aop:pointcut expression="execution(* cn.itcast.aop.Book.*(..))" id="pointcut1"></aop:pointcut> <!-- 配置切面 aop:aspect标签里面使用属性ref,ref属性值写增强类的bean的id值 --> <aop:aspect ref="myBook"> <!-- 增强类型 method属性:增强类的方法名称 pointcut-ref属性:切入点的id值 --> <!-- 前置通知 --> <aop:before method="before1" pointcut-ref="pointcut1"></aop:before> </aop:aspect> </aop:config>
- 1
- 2
第五步,编写一个单元测试类并进行测试
在cn.itcast.aop包下编写一个TestDemo单元测试类。
public class TestDemo {
@Test
public void testUser() { ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml"); Book book = (Book) context.getBean("book"); book.add(); } }
- 1
其实我们也可以整合Junit单元测试,Spring对Junit4进行了支持,可以通过注解方便的测试Spring程序,所以就不必写那么麻烦的单元测试类了。首先导入如下Jar包:
然后编写如下的单元测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/bean2.xml") // 或者也可写为:@ContextConfiguration("classpath:bean2.xml")
public class TestDemo { @Autowired private Book book; @Test public void demo1() { book.add(); } }
- 1
演示其他通知类型
先将MyBook增强类的代码修改为:
public class MyBook {
// 前置通知
public void before1() { System.out.println("before........"); } // 后置通知 public void after11() { System.out.println("after..........."); } // 环绕通知 public void around1(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("方法之前执行..........."); // 让被增强的方法执行 joinPoint.proceed(); System.out.println("方法之后执行..........."); } }
- 1
然后再在Spring配置文件中进行配置。
<!-- 配置AOP的操作 -->
<aop:config>
<!-- 配置切入点,对Book类里面的所有方法都增强 -->
<aop:pointcut expression="execution(* cn.itcast.aop.Book.*(..))" id="pointcut1"></aop:pointcut> <!-- 配置切面 aop:aspect标签里面使用属性ref,ref属性值写增强类的bean的id值 --> <aop:aspect ref="myBook"> <!-- 增强类型 method属性:增强类的方法名称 pointcut-ref属性:切入点的id值 --> <!-- 前置通知 --> <aop:before method="before1" pointcut-ref="pointcut1"></aop:before> <!-- 后置通知 --> <aop:after-returning method="after11" pointcut-ref="pointcut1"></aop:after-returning> <!-- 环绕通知 --> <aop:around method="around1" pointcut-ref="pointcut1"></aop:around> </aop:aspect> </aop:config>
- 1
切入点表达式
通过execution函数,可以定义切点的方法切入。
语法为:execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
。
例如:
- 匹配所有类的public方法:
execution(public *.*(..))
- 匹配指定包下所有类的方法:
execution(* cn.itcast.dao.*(..))
,但不包含子包 execution(* cn.itcast.dao..*(..))
,..*
表示包、子孙包下所有类。- 匹配指定类所有方法:
execution(* cn.itcast.service.UserService.*(..))
- 匹配实现特定接口的所有类的方法:
execution(* cn.itcast.dao.GenericDAO+.*(..))
- 匹配所有save开头的方法:
execution(* save*(..))
- 匹配所有类里面的所有的方法:
execution(* *.*(..))
Log4j操作
在项目开发中,我们通常会导入类似这样的日志Jar包:
关于这些日志Jar包之间的关系我也搞的不是很清楚,只能作简单的记录。
使用Log4j,可以查看到当前运行程序中对象创建的过程,也可以看到更详细的信息。Log4j适合使用在程序调试中。
例如,在本文中讲解上面的Web项目使用Log4j。
首先导入log4j的jar包,如下:
然后添加log4j配置文件——log4j.properties,添加到src目录下面,该文件内容如下:
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.err log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### direct messages to file mylog.log ### log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.File=d\:mylog.log log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### set log levels - for more verbose logging change 'info' to 'debug' ### log4j.rootLogger=info, stdout, file
- 1
粗略讲解如下图:
IoC配置文件和注解混合使用
实际开发中,我们都会混合使用IoC配置文件和注解,一般是使用配置文件方式创建对象,使用注解方式注入属性。当然了,你亦可另辟蹊径。下面我举个例子来演示。
在src目录下创建一个cn.itcast.xmlanno包,并在该包下编写一个BookDao类、PersonDao类、BookService类。
-
BookDao类
public class BookDao { public void update() { System.out.println("book dao............."); } }
-
PersonDao类
public class PersonDao { public void update() { System.out.println("person dao............."); } }
- 1
-
BookService类
public class BookService { public void update() { System.out.println("service............"); } }
- 1
接着创建对象,使用配置文件实现。
<bean id="bookService" class="cn.itcast.xmlanno.BookService"></bean> <bean id="bookDao" class="cn.itcast.xmlanno.BookDao"></bean> <bean id="personDao" class="cn.itcast.xmlanno.PersonDao"></bean>
也有人说创建对象,能用注解就用注解,而不要写上面这种乱七八糟的东西。我觉得也蛮有道理的。
然后在BookService类里面注入BookDao类以及PersonDao类的对象,使用注解方式。
public class BookService {
@Resource(name="bookDao")
private BookDao bookDao;
@Resource(name="personDao") private PersonDao personDao; public void update() { System.out.println("service............"); bookDao.update(); personDao.update(); } }
- 1
注意,不要忘了开启注解的扫描:
<context:component-scan base-package="cn.itcast"></context:component-scan>
- 1
- 1
最后再在cn.itcast.xmlanno包下编写一个TestDemo单元测试类。
public class TestDemo {
@Test
public void testBook() { ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml"); BookService bookService = (BookService) context.getBean("bookService"); bookService.update(); } }
- 1
测试即可。
Spring整合Web项目
现在我来介绍如何使用Spring整合Web项目。在真正的开发中,都会在action中调用service层的代码,service层再调用dao层的代码。下面我就举个例子来演示。
首先创建Web项目,引入Struts2的开发包,可参考struts-2.3.24中的apps下的一些示例代码,其中struts2-blank.war是一个Struts2的空的工程。我们只需要将struts2-blank.war解压后进入到WEB-INF下的lib中查看,即可知道要导入哪些Struts2的开发包。
然后在src目录下创建一个cn.itcast.action包,并在该包下编写一个Action类。
public class UserAction extends ActionSupport { @Override public String execute() throws Exception { System.out.println("action........."); return NONE; } }
- 1
接着就要完成该UserAction类的配置,在src目录下引入Strust2框架的核心配置文件——struts.xml,其内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<package name="demo" extends="struts-default" namespace="/"> <action name="user" class="cn.itcast.action.UserAction"></action> </package> </struts>
- 1
不要忘了配置核心过滤器,打开web.xml,在web.xml文件中添加如下配置:
<!-- 配置Struts2的过滤器 -->
<filter>
<filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
- 1
接着再在src目录下创建一个cn.itcast.service包,并在该包下编写一个UserService类。
public class UserService {
public void add() { System.out.println("service............"); } }
- 1
上面基本上将Struts2框架的开发环境搭建好了,接下来就要使用Spring整合该Web项目了。
首先导入Spring框架开发的Jar包:
既然上面已讲了log4j日志包的操作,这里还是在src目录下引入log4j.properties文件。
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.err log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### direct messages to file mylog.log ### log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.File=d\:mylog.log log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### set log levels - for more verbose logging change 'info' to 'debug' ### log4j.rootLogger=info, stdout, file
- 1
- 2
然后再在src目录下引入Spring的核心配置文件——bean1.xml,创建并管理UserService类的对象。
<?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 http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="userService" class="cn.itcast.service.UserService"></bean> </beans>
- 1
接着改写UserAction类的代码,让其调用service层的代码。
public class UserAction extends ActionSupport { @Override public String execute() throws Exception { System.out.println("action........."); ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); UserService userService = (UserService) context.getBean("userService"); userService.add(); return NONE; } }
- 1
最后我们在浏览器地址栏中输入地址http://localhost:8080/Spring_day02_web/user.action
进行访问,并多次刷新,在Eclipse控制台中可以看到如下信息:
这就产生了一个问题:由于action对象是一个多实例的对象,所以每次访问action都会创建一个action对象。虽然功能是可以实现,但是性能很低下。为了解决这个问题,需要使用到2个技术:
- ServletContext对象
- 监听器
在服务器启动的时候创建ServletContext对象,使用监听器可以知道ServletContext对象在什么时候创建,然后加载配置文件,创建配置的对象,把创建的对象放到ServletContext域里面。但在Spring里面不需要我们写这些操作的代码,因为它已经帮我们进行了封装,Spring里面封装了一个监听器,我们只需要配置监听器就可以了。使用Spring里面的监听器的时候,需要导入一个jar包,这个jar包用于整合Web项目。
接着在web.xml文件中配置Spring的监听器,这样在服务器启动的时候,就会加载Spring的配置文件了。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
这时我们启动服务器,会在Eclipse的控制台中看到如下异常信息:
这是因为服务器启动的时候,会默认到WEB-INF下面找寻名称是applicationContext.xml的配置文件。要解决这个异常,就要手动配置让服务器找寻哪个Spring配置文件,可在web.xml文件中添加如下配置:
<!-- 指定找到Spring配置文件的位置和名称 -->
<context-param>
<param-name>contextConfigLocation</param-name> <param-value>classpath:bean1.xml</param-value> </context-param>
- 1
这样问题就解决了。但我们还要明白Spring整合Web项目还没完,也不用急,下文我会接着介绍的。